/** * @ai-summary React context for unit system preferences with backend sync * @ai-context Provides unit preferences, conversion functions, and currency symbol throughout the app */ import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'; import { UnitSystem, UnitPreferences } from './units.types'; import { safeStorage } from '../utils/safe-storage'; import { formatDistanceBySystem, formatVolumeBySystem, formatFuelEfficiencyBySystem, formatPriceBySystem, convertDistanceBySystem, convertVolumeBySystem, convertFuelEfficiencyBySystem, getDistanceUnit, getVolumeUnit, getFuelEfficiencyUnit } from './units.utils'; import { usePreferences, useUpdatePreferences } from '../../features/settings/hooks/usePreferences'; import { useAuth0 } from '@auth0/auth0-react'; interface UnitsContextType { unitSystem: UnitSystem; setUnitSystem: (system: UnitSystem) => void; preferences: UnitPreferences; currencySymbol: string; currencyCode: string; isLoading: boolean; // Conversion functions convertDistance: (miles: number) => number; convertVolume: (gallons: number) => number; convertFuelEfficiency: (mpg: number) => number; // Formatting functions formatDistance: (miles: number, precision?: number) => string; formatVolume: (gallons: number, precision?: number) => string; formatFuelEfficiency: (mpg: number, precision?: number) => string; formatPrice: (pricePerGallon: number, precision?: number) => string; } const UnitsContext = createContext(undefined); interface UnitsProviderProps { children: ReactNode; initialSystem?: UnitSystem; } // Currency symbol mapping const getCurrencySymbol = (system: UnitSystem): string => { return system === 'metric' ? '\u20AC' : '$'; // EUR or USD }; const getCurrencyCode = (system: UnitSystem): string => { return system === 'metric' ? 'EUR' : 'USD'; }; export const UnitsProvider: React.FC = ({ children, initialSystem = 'imperial' }) => { const [unitSystem, setUnitSystemState] = useState(initialSystem); const [hasInitialized, setHasInitialized] = useState(false); const { isAuthenticated } = useAuth0(); const { data: backendPreferences, isLoading: preferencesLoading } = usePreferences(); const updatePreferencesMutation = useUpdatePreferences(); // Load unit preference from localStorage on mount (fallback) useEffect(() => { if (!hasInitialized) { try { const stored = safeStorage.getItem('motovaultpro-unit-system'); if (stored === 'imperial' || stored === 'metric') { setUnitSystemState(stored); } } catch (error) { console.warn('[Units] Failed to load unit system preference from storage:', error); } } }, [hasInitialized]); // Sync with backend preferences when they load (takes priority over localStorage) useEffect(() => { if (backendPreferences?.unitSystem && !hasInitialized) { setUnitSystemState(backendPreferences.unitSystem); // Also update localStorage to keep them in sync try { safeStorage.setItem('motovaultpro-unit-system', backendPreferences.unitSystem); } catch (error) { console.warn('[Units] Failed to sync unit system to storage:', error); } setHasInitialized(true); } }, [backendPreferences, hasInitialized]); // Mark as initialized when not authenticated (localStorage is the source of truth) useEffect(() => { if (!isAuthenticated && !hasInitialized) { setHasInitialized(true); } }, [isAuthenticated, hasInitialized]); // Handle unit system change with backend sync const handleSetUnitSystem = useCallback((system: UnitSystem) => { setUnitSystemState(system); // Always save to localStorage (offline fallback) try { safeStorage.setItem('motovaultpro-unit-system', system); } catch (error) { console.warn('[Units] Failed to save unit system preference:', error); } // Sync to backend if authenticated if (isAuthenticated) { updatePreferencesMutation.mutate({ unitSystem: system }); } }, [isAuthenticated, updatePreferencesMutation]); // Generate preferences object based on current system const preferences: UnitPreferences = { system: unitSystem, distance: getDistanceUnit(unitSystem), volume: getVolumeUnit(unitSystem), fuelEfficiency: getFuelEfficiencyUnit(unitSystem), }; // Currency based on unit system const currencySymbol = getCurrencySymbol(unitSystem); const currencyCode = getCurrencyCode(unitSystem); // Conversion functions using current unit system const convertDistance = useCallback((miles: number) => convertDistanceBySystem(miles, unitSystem), [unitSystem]); const convertVolume = useCallback((gallons: number) => convertVolumeBySystem(gallons, unitSystem), [unitSystem]); const convertFuelEfficiency = useCallback((mpg: number) => convertFuelEfficiencyBySystem(mpg, unitSystem), [unitSystem]); // Formatting functions using current unit system const formatDistance = useCallback((miles: number, precision?: number) => formatDistanceBySystem(miles, unitSystem, precision), [unitSystem]); const formatVolume = useCallback((gallons: number, precision?: number) => formatVolumeBySystem(gallons, unitSystem, precision), [unitSystem]); const formatFuelEfficiency = useCallback((mpg: number, precision?: number) => formatFuelEfficiencyBySystem(mpg, unitSystem, precision), [unitSystem]); const formatPrice = useCallback((pricePerGallon: number, precision?: number) => formatPriceBySystem(pricePerGallon, unitSystem, currencyCode, precision), [unitSystem, currencyCode]); const value: UnitsContextType = { unitSystem, setUnitSystem: handleSetUnitSystem, preferences, currencySymbol, currencyCode, isLoading: preferencesLoading && isAuthenticated, convertDistance, convertVolume, convertFuelEfficiency, formatDistance, formatVolume, formatFuelEfficiency, formatPrice, }; return ( {children} ); }; export const useUnits = (): UnitsContextType => { const context = useContext(UnitsContext); if (context === undefined) { throw new Error('useUnits must be used within a UnitsProvider'); } return context; };