Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
115 lines
3.4 KiB
TypeScript
115 lines
3.4 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
|
|
/**
|
|
* Hook for syncing state with localStorage
|
|
*
|
|
* Automatically persists state to localStorage and syncs across tabs.
|
|
* Handles serialization/deserialization and provides type safety.
|
|
*
|
|
* @param key - localStorage key
|
|
* @param initialValue - Initial value if key doesn't exist
|
|
* @returns Tuple of [value, setValue, removeValue]
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* function UserPreferences() {
|
|
* const [theme, setTheme, removeTheme] = useLocalStorage('theme', 'dark');
|
|
*
|
|
* return (
|
|
* <div>
|
|
* <p>Current theme: {theme}</p>
|
|
* <button onClick={() => setTheme('light')}>Light Mode</button>
|
|
* <button onClick={() => setTheme('dark')}>Dark Mode</button>
|
|
* <button onClick={removeTheme}>Reset</button>
|
|
* </div>
|
|
* );
|
|
* }
|
|
* ```
|
|
*/
|
|
export function useLocalStorage<T>(
|
|
key: string,
|
|
initialValue: T
|
|
): [T, (value: T | ((prevValue: T) => T)) => void, () => void] {
|
|
// Get initial value from localStorage or use provided initial value
|
|
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
if (typeof window === 'undefined') {
|
|
return initialValue;
|
|
}
|
|
|
|
try {
|
|
const item = window.localStorage.getItem(key);
|
|
return item ? (JSON.parse(item) as T) : initialValue;
|
|
} catch (error) {
|
|
console.error(`Error reading localStorage key "${key}":`, error);
|
|
return initialValue;
|
|
}
|
|
});
|
|
|
|
// Update localStorage when value changes
|
|
const setValue = useCallback(
|
|
(value: T | ((prevValue: T) => T)) => {
|
|
try {
|
|
// Allow value to be a function for functional updates
|
|
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
|
|
setStoredValue(valueToStore);
|
|
|
|
if (typeof window !== 'undefined') {
|
|
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
// Dispatch storage event for cross-tab sync
|
|
window.dispatchEvent(
|
|
new StorageEvent('storage', {
|
|
key,
|
|
newValue: JSON.stringify(valueToStore),
|
|
})
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error setting localStorage key "${key}":`, error);
|
|
}
|
|
},
|
|
[key, storedValue]
|
|
);
|
|
|
|
// Remove value from localStorage
|
|
const removeValue = useCallback(() => {
|
|
try {
|
|
setStoredValue(initialValue);
|
|
if (typeof window !== 'undefined') {
|
|
window.localStorage.removeItem(key);
|
|
window.dispatchEvent(
|
|
new StorageEvent('storage', {
|
|
key,
|
|
newValue: null,
|
|
})
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error removing localStorage key "${key}":`, error);
|
|
}
|
|
}, [key, initialValue]);
|
|
|
|
// Listen for changes in other tabs/windows
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
const handleStorageChange = (e: StorageEvent) => {
|
|
if (e.key === key && e.newValue !== null) {
|
|
try {
|
|
setStoredValue(JSON.parse(e.newValue) as T);
|
|
} catch (error) {
|
|
console.error(`Error parsing storage event for key "${key}":`, error);
|
|
}
|
|
} else if (e.key === key && e.newValue === null) {
|
|
setStoredValue(initialValue);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('storage', handleStorageChange);
|
|
return () => window.removeEventListener('storage', handleStorageChange);
|
|
}, [key, initialValue]);
|
|
|
|
return [storedValue, setValue, removeValue];
|
|
}
|