131 lines
3.1 KiB
TypeScript
131 lines
3.1 KiB
TypeScript
|
|
/**
|
||
|
|
* 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,
|
||
|
|
};
|
||
|
|
}
|