fix: Dynamic drop down bugs in firefox
All checks were successful
Deploy to Staging / Build Images (push) Successful in 3m39s
Deploy to Staging / Deploy to Staging (push) Successful in 36s
Deploy to Staging / Verify Staging (push) Successful in 5s
Deploy to Staging / Notify Staging Ready (push) Successful in 5s
Deploy to Staging / Notify Staging Failure (push) Has been skipped

This commit is contained in:
Eric Gullickson
2025-12-31 12:43:22 -06:00
parent c57a05daa5
commit 7e606df012
2 changed files with 51 additions and 51 deletions

View File

@@ -22,11 +22,13 @@ You are a senior software engineer specializsing in NodeJS, Typescript, front en
- Make no assumptions. - Make no assumptions.
- Ask clarifying questions. - Ask clarifying questions.
- Ultrathink - Ultrathink
- You will be syncing the desktop and mobile versions of the homepage - Troubleshoot UX problems when using Firefox
*** CONTEXT *** *** CONTEXT ***
- This is a modern web app for managing a vehicle fleet. It has both a desktop and mobile versions of the site that both need to maintain feature parity. It's currently deployed via docker compose but in the future will be deployed via k8s. - This is a modern web app for managing a vehicle fleet. It has both a desktop and mobile versions of the site that both need to maintain feature parity. It's currently deployed via docker compose but in the future will be deployed via k8s.
- Read README.md CLAUDE.md and AI-INDEX.md and follow relevant instructions to understand this code repository in the context of this change. - Read README.md CLAUDE.md and AI-INDEX.md and follow relevant instructions to understand this code repository in the context of this change.
- The vehicles dynamic drop downs are broken on Firefox. The dropdowns don't populate immediately and require selecting and unselecting options to work.
- There is a console error "Error: Can't find the actor ID for objects-manager from root or target actor's form. types.js:559:11"
*** CHANGES TO IMPLEMENT *** *** CHANGES TO IMPLEMENT ***

View File

@@ -78,13 +78,14 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
const [engines, setEngines] = useState<string[]>([]); const [engines, setEngines] = useState<string[]>([]);
const [trims, setTrims] = useState<string[]>([]); const [trims, setTrims] = useState<string[]>([]);
const [transmissions, setTransmissions] = useState<string[]>([]); const [transmissions, setTransmissions] = useState<string[]>([]);
const [selectedYear, setSelectedYear] = useState<number | undefined>();
const [selectedMake, setSelectedMake] = useState<string>('');
const [selectedModel, setSelectedModel] = useState<string>('');
const [selectedTrim, setSelectedTrim] = useState<string>('');
const [loadingDropdowns, setLoadingDropdowns] = useState(false); const [loadingDropdowns, setLoadingDropdowns] = useState(false);
const hasInitialized = useRef(false); const hasInitialized = useRef(false);
const isInitializing = useRef(false); const isInitializing = useRef(false);
// Track previous values for cascade change detection (replaces useState)
const prevYear = useRef<number | undefined>(undefined);
const prevMake = useRef<string>('');
const prevModel = useRef<string>('');
const prevTrim = useRef<string>('');
const [currentImageUrl, setCurrentImageUrl] = useState<string | undefined>(initialData?.imageUrl); const [currentImageUrl, setCurrentImageUrl] = useState<string | undefined>(initialData?.imageUrl);
const [previewUrl, setPreviewUrl] = useState<string | null>(null); const [previewUrl, setPreviewUrl] = useState<string | null>(null);
@@ -106,6 +107,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
const watchedYear = watch('year'); const watchedYear = watch('year');
const watchedMake = watch('make'); const watchedMake = watch('make');
const watchedModel = watch('model'); const watchedModel = watch('model');
const watchedTrim = watch('trimLevel');
// Load years on component mount // Load years on component mount
useEffect(() => { useEffect(() => {
@@ -133,7 +135,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
setLoadingDropdowns(true); setLoadingDropdowns(true);
// Step 1: Set year and load makes // Step 1: Set year and load makes
setSelectedYear(initialData.year); prevYear.current = initialData.year;
const makesData = await vehiclesApi.getMakes(initialData.year); const makesData = await vehiclesApi.getMakes(initialData.year);
setMakes(makesData); setMakes(makesData);
@@ -143,7 +145,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
} }
// Step 2: Set make and load models // Step 2: Set make and load models
setSelectedMake(initialData.make); prevMake.current = initialData.make;
const modelsData = await vehiclesApi.getModels(initialData.year, initialData.make); const modelsData = await vehiclesApi.getModels(initialData.year, initialData.make);
setModels(modelsData); setModels(modelsData);
@@ -153,13 +155,13 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
} }
// Step 3: Set model and load trims (transmissions loaded after trim selected) // Step 3: Set model and load trims (transmissions loaded after trim selected)
setSelectedModel(initialData.model); prevModel.current = initialData.model;
const trimsData = await vehiclesApi.getTrims(initialData.year, initialData.make, initialData.model); const trimsData = await vehiclesApi.getTrims(initialData.year, initialData.make, initialData.model);
setTrims(trimsData); setTrims(trimsData);
if (initialData.trimLevel) { if (initialData.trimLevel) {
// Step 4: Set trim and load engines + transmissions // Step 4: Set trim and load engines + transmissions
setSelectedTrim(initialData.trimLevel); prevTrim.current = initialData.trimLevel;
const [enginesData, transmissionsData] = await Promise.all([ const [enginesData, transmissionsData] = await Promise.all([
vehiclesApi.getEngines( vehiclesApi.getEngines(
initialData.year, initialData.year,
@@ -202,18 +204,18 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
// Skip during initialization // Skip during initialization
if (isInitializing.current) return; if (isInitializing.current) return;
if (watchedYear && watchedYear !== selectedYear) { if (watchedYear && watchedYear !== prevYear.current) {
const loadMakes = async () => { const loadMakes = async () => {
setLoadingDropdowns(true); setLoadingDropdowns(true);
try { try {
const makesData = await vehiclesApi.getMakes(watchedYear); const makesData = await vehiclesApi.getMakes(watchedYear);
setMakes(makesData); setMakes(makesData);
setSelectedYear(watchedYear); prevYear.current = watchedYear;
// Clear dependent selections // Clear dependent selections
setSelectedMake(''); prevMake.current = '';
setSelectedModel(''); prevModel.current = '';
setSelectedTrim(''); prevTrim.current = '';
setModels([]); setModels([]);
setTrims([]); setTrims([]);
setEngines([]); setEngines([]);
@@ -233,24 +235,24 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
loadMakes(); loadMakes();
} }
}, [watchedYear, selectedYear, setValue]); }, [watchedYear, setValue]);
// Load models when make changes // Load models when make changes
useEffect(() => { useEffect(() => {
// Skip during initialization // Skip during initialization
if (isInitializing.current) return; if (isInitializing.current) return;
if (watchedMake && watchedYear && watchedMake !== selectedMake) { if (watchedMake && watchedYear && watchedMake !== prevMake.current) {
const loadModels = async () => { const loadModels = async () => {
setLoadingDropdowns(true); setLoadingDropdowns(true);
try { try {
const modelsData = await vehiclesApi.getModels(watchedYear, watchedMake); const modelsData = await vehiclesApi.getModels(watchedYear, watchedMake);
setModels(modelsData); setModels(modelsData);
setSelectedMake(watchedMake); prevMake.current = watchedMake;
// Clear dependent selections // Clear dependent selections
setSelectedModel(''); prevModel.current = '';
setSelectedTrim(''); prevTrim.current = '';
setTrims([]); setTrims([]);
setEngines([]); setEngines([]);
setTransmissions([]); setTransmissions([]);
@@ -268,23 +270,23 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
loadModels(); loadModels();
} }
}, [watchedMake, watchedYear, selectedMake, setValue]); }, [watchedMake, watchedYear, setValue]);
// Load trims when model changes // Load trims when model changes
useEffect(() => { useEffect(() => {
// Skip during initialization // Skip during initialization
if (isInitializing.current) return; if (isInitializing.current) return;
if (watchedModel && watchedYear && selectedMake && watchedModel !== selectedModel) { if (watchedModel && watchedYear && watchedMake && watchedModel !== prevModel.current) {
const loadTrims = async () => { const loadTrims = async () => {
setLoadingDropdowns(true); setLoadingDropdowns(true);
try { try {
const trimsData = await vehiclesApi.getTrims(watchedYear, selectedMake, watchedModel); const trimsData = await vehiclesApi.getTrims(watchedYear, watchedMake, watchedModel);
setTrims(trimsData); setTrims(trimsData);
setSelectedModel(watchedModel); prevModel.current = watchedModel;
// Clear deeper selections (trims, transmissions, engines) // Clear deeper selections (engines, transmissions)
setSelectedTrim(''); prevTrim.current = '';
setTransmissions([]); setTransmissions([]);
setEngines([]); setEngines([]);
setValue('trimLevel', ''); setValue('trimLevel', '');
@@ -300,35 +302,34 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
loadTrims(); loadTrims();
} }
}, [watchedModel, watchedYear, selectedMake, selectedModel, setValue]); }, [watchedModel, watchedYear, watchedMake, setValue]);
// Load engines and transmissions when trim changes // Load engines and transmissions when trim changes
useEffect(() => { useEffect(() => {
// Skip during initialization // Skip during initialization
if (isInitializing.current) return; if (isInitializing.current) return;
const trimName = watch('trimLevel'); if (watchedTrim && watchedYear && watchedMake && watchedModel && watchedTrim !== prevTrim.current) {
if (trimName && watchedYear && selectedMake && selectedModel) {
const loadEnginesAndTransmissions = async () => { const loadEnginesAndTransmissions = async () => {
setLoadingDropdowns(true); setLoadingDropdowns(true);
try { try {
const [enginesData, transmissionsData] = await Promise.all([ const [enginesData, transmissionsData] = await Promise.all([
vehiclesApi.getEngines( vehiclesApi.getEngines(
watchedYear, watchedYear,
selectedMake, watchedMake,
selectedModel, watchedModel,
trimName watchedTrim
), ),
vehiclesApi.getTransmissions( vehiclesApi.getTransmissions(
watchedYear, watchedYear,
selectedMake, watchedMake,
selectedModel, watchedModel,
trimName watchedTrim
) )
]); ]);
setEngines(enginesData); setEngines(enginesData);
setTransmissions(transmissionsData); setTransmissions(transmissionsData);
setSelectedTrim(trimName); prevTrim.current = watchedTrim;
} catch (error) { } catch (error) {
console.error('Failed to load engines and transmissions:', error); console.error('Failed to load engines and transmissions:', error);
setEngines([]); setEngines([]);
@@ -340,7 +341,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
loadEnginesAndTransmissions(); loadEnginesAndTransmissions();
} }
}, [watchedYear, selectedMake, selectedModel, watch('trimLevel')]); }, [watchedYear, watchedMake, watchedModel, watchedTrim, setValue]);
const handleImageUpload = async (file: File) => { const handleImageUpload = async (file: File) => {
if (isEditMode && vehicleId) { if (isEditMode && vehicleId) {
@@ -433,7 +434,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
</label> </label>
<select <select
{...register('year', { valueAsNumber: true })} {...register('year', { valueAsNumber: true })}
value={selectedYear || ''} value={watchedYear || ''}
onChange={(e) => { onChange={(e) => {
const year = parseInt(e.target.value); const year = parseInt(e.target.value);
setValue('year', year); setValue('year', year);
@@ -456,10 +457,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
</label> </label>
<select <select
{...register('make')} {...register('make')}
value={selectedMake} value={watchedMake || ''}
onChange={(e) => { onChange={(e) => {
const make = e.target.value; setValue('make', e.target.value);
setValue('make', make);
}} }}
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi" className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
disabled={loadingDropdowns || !watchedYear || makes.length === 0} disabled={loadingDropdowns || !watchedYear || makes.length === 0}
@@ -488,10 +488,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
</label> </label>
<select <select
{...register('model')} {...register('model')}
value={selectedModel} value={watchedModel || ''}
onChange={(e) => { onChange={(e) => {
const model = e.target.value; setValue('model', e.target.value);
setValue('model', model);
}} }}
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi" className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
disabled={loadingDropdowns || !watchedMake || models.length === 0} disabled={loadingDropdowns || !watchedMake || models.length === 0}
@@ -523,10 +522,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
</label> </label>
<select <select
{...register('trimLevel')} {...register('trimLevel')}
value={selectedTrim} value={watchedTrim || ''}
onChange={(e) => { onChange={(e) => {
const trim = e.target.value; setValue('trimLevel', e.target.value);
setValue('trimLevel', trim);
}} }}
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi" className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
disabled={loadingDropdowns || !watchedModel || trims.length === 0} disabled={loadingDropdowns || !watchedModel || trims.length === 0}
@@ -557,13 +555,13 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
<select <select
{...register('engine')} {...register('engine')}
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi" className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
disabled={loadingDropdowns || !selectedTrim || engines.length === 0} disabled={loadingDropdowns || !watchedTrim || engines.length === 0}
style={{ fontSize: '16px' }} style={{ fontSize: '16px' }}
> >
<option value=""> <option value="">
{loadingDropdowns {loadingDropdowns
? 'Loading...' ? 'Loading...'
: !selectedTrim : !watchedTrim
? 'Select trim first' ? 'Select trim first'
: engines.length === 0 : engines.length === 0
? 'N/A (Electric)' ? 'N/A (Electric)'
@@ -585,13 +583,13 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
<select <select
{...register('transmission')} {...register('transmission')}
className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi" className="w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi"
disabled={loadingDropdowns || !selectedTrim || transmissions.length === 0} disabled={loadingDropdowns || !watchedTrim || transmissions.length === 0}
style={{ fontSize: '16px' }} style={{ fontSize: '16px' }}
> >
<option value=""> <option value="">
{loadingDropdowns {loadingDropdowns
? 'Loading...' ? 'Loading...'
: !selectedTrim : !watchedTrim
? 'Select trim first' ? 'Select trim first'
: transmissions.length === 0 : transmissions.length === 0
? 'No transmissions available' ? 'No transmissions available'