puffin-app/src/hooks/useCalculatorState.ts

131 lines
3.1 KiB
TypeScript
Raw Normal View History

/**
* useCalculatorState Hook
*
* Persists calculator state in localStorage to survive page reloads,
* browser navigation, and Stripe checkout redirects.
*
* State automatically expires after 1 hour to prevent stale data.
*/
import { useState, useEffect } from 'react';
export interface CalculatorState {
// Calculation type
calculationType: 'fuel' | 'distance' | 'custom';
// Distance-based inputs
distance: string;
speed: string;
fuelRate: string;
// Fuel-based inputs
fuelAmount: string;
fuelUnit: 'liters' | 'gallons';
// Custom amount input
customAmount: string;
// Offset order settings
offsetPercentage: number;
portfolioId?: number;
// Metadata
timestamp: number; // Used for auto-expiry
}
const STORAGE_KEY = 'puffin_calculator_state';
const EXPIRY_MS = 60 * 60 * 1000; // 1 hour (per user preference)
/**
* Custom hook for calculator state persistence
*
* @returns Object with state, saveState, and clearState functions
*/
export function useCalculatorState() {
const [state, setState] = useState<CalculatorState | null>(null);
const [isLoaded, setIsLoaded] = useState(false);
// Load from localStorage on mount
useEffect(() => {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
try {
const parsed = JSON.parse(stored) as CalculatorState;
const age = Date.now() - parsed.timestamp;
// Check if state has expired
if (age < EXPIRY_MS) {
setState(parsed);
} else {
// State expired, clean up
localStorage.removeItem(STORAGE_KEY);
}
} catch (err) {
// Corrupted data, clean up
console.warn('Failed to parse calculator state from localStorage:', err);
localStorage.removeItem(STORAGE_KEY);
}
}
setIsLoaded(true);
}, []);
/**
* Save partial or complete state to localStorage
* Merges with existing state
*/
const saveState = (newState: Partial<CalculatorState>) => {
const updated: CalculatorState = {
...(state || {
calculationType: 'fuel',
distance: '',
speed: '12',
fuelRate: '100',
fuelAmount: '',
fuelUnit: 'liters',
customAmount: '',
offsetPercentage: 100,
}),
...newState,
timestamp: Date.now(), // Always update timestamp
};
setState(updated);
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
} catch (err) {
console.error('Failed to save calculator state to localStorage:', err);
}
};
/**
* Clear calculator state from memory and localStorage
* Call this after successful payment completion
*/
const clearState = () => {
setState(null);
try {
localStorage.removeItem(STORAGE_KEY);
} catch (err) {
console.error('Failed to clear calculator state from localStorage:', err);
}
};
return {
/** Current calculator state (null if none saved or expired) */
state,
/** Whether state has been loaded from localStorage */
isLoaded,
/** Save new or partial state */
saveState,
/** Clear all saved state */
clearState,
};
}