import { useCallback, useEffect, useState } from 'react'; import { decodeState, encodeState } from './url-state'; import { reduceProject } from './project-state'; import type { ProjectAction } from './project-state'; export const PROJECT_STORAGE_KEY = 'floor-planner.project'; function readLocation() { const link = window.location.search; if (link && link !== '?' && link !== '?planner') return decodeState(link); try { const saved = decodeState(window.localStorage.getItem(PROJECT_STORAGE_KEY) ?? ''); if (!saved.error) return saved; } catch { /* Storage may be unavailable. */ } return decodeState(''); } /** React owns live state. Persistence is an output between navigations. */ export function useProject() { const [loaded, setLoaded] = useState(() => ({ ...readLocation(), milestone: false })); useEffect(() => { const restore = () => setLoaded({ ...readLocation(), milestone: false }); window.addEventListener('popstate', restore); return () => window.removeEventListener('popstate', restore); }, []); useEffect(() => { if (loaded.error) return; try { const query = encodeState(loaded.state); if (query !== window.location.search || window.location.hash) window.history[loaded.milestone ? 'pushState' : 'replaceState']( null, '', `${window.location.pathname}${query}`, ); try { window.localStorage.setItem(PROJECT_STORAGE_KEY, query); } catch { /* The URL remains available if storage is blocked or full. */ } } catch (error) { // Keep edits usable even if the browser cannot save this link. setLoaded((current) => current === loaded ? { ...current, error: error instanceof Error ? error.message : 'This plan could not be saved.', } : current, ); } }, [loaded]); const dispatch = useCallback((action: ProjectAction) => { setLoaded((current) => ({ state: reduceProject(current.state, action), error: null, milestone: action.type === 'addRoom' || action.type === 'removeRoom' || action.type === 'visitRoom', })); }, []); return { ...loaded, dispatch }; }