183 lines
6.3 KiB
TypeScript
183 lines
6.3 KiB
TypeScript
/**
|
|
* @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<UnitsContextType | undefined>(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<UnitsProviderProps> = ({
|
|
children,
|
|
initialSystem = 'imperial'
|
|
}) => {
|
|
const [unitSystem, setUnitSystemState] = useState<UnitSystem>(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 (
|
|
<UnitsContext.Provider value={value}>
|
|
{children}
|
|
</UnitsContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useUnits = (): UnitsContextType => {
|
|
const context = useContext(UnitsContext);
|
|
if (context === undefined) {
|
|
throw new Error('useUnits must be used within a UnitsProvider');
|
|
}
|
|
return context;
|
|
};
|