remove localstorage dependency for wizard

This commit is contained in:
Mayur Shinde
2026-06-10 13:26:10 +05:30
parent 081bf10558
commit a139516d7f
4 changed files with 67 additions and 80 deletions
@@ -4,8 +4,8 @@
* This is the client-side stand-in for the spec's backend entities (§12):
* estimate_sessions, estimate_inputs, hail_risk_snapshots, recommendations,
* customer_reports, estimate_events.
* For the demo it persists to localStorage so an abandoned session can be resumed
* (spec §13: "auto-save every screen to recover abandoned sessions").
* Session state is held in React memory only — like the rest of the CRM demo store,
* nothing is written to localStorage, so a full page reload starts a fresh session.
*
* Kept separate from the global MockStoreProvider on purpose — this is a new,
* isolated module. A Phase 5 adapter can mirror sessions into the CRM mock store.
@@ -15,8 +15,6 @@ import React, { createContext, useContext, useState, useEffect, useCallback } fr
import { STEP_FLOW } from './data/questions';
import { getTenantConfig, CONFIG_CHANGE_EVENT } from './data/tenantConfig';
const STORAGE_KEY = 'lup_estimate_wizard_session_v1';
// One-click demo seed (spec demo support). Every value matches a valid choice in
// questions.js so the wizard renders selected cards correctly. Address is a real
// Plano, TX location so the Nominatim geocoder + storm-history lookup resolve.
@@ -80,43 +78,21 @@ function freshSession(source = 'public') {
};
}
function loadSession() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw);
// Basic shape guard so a stale/partial blob can't crash the wizard.
if (parsed && parsed.id && parsed.inputs) return parsed;
} catch { /* ignore corrupt storage */ }
return null;
}
export function EstimateWizardProvider({ children, source = 'public' }) {
// Snapshot tenant config at mount, but stay reactive: the admin can publish flags
// (kill-switch, AI copy, conservative mode), pricing, and package content while a
// wizard is open. Refresh on same-tab publish, cross-tab storage writes, and on
// tab focus so toggles take effect without a hard reload (spec §14/§18).
// wizard is open. Refresh on the same-tab publish event so toggles take effect
// without a hard reload (spec §14/§18).
const [tenantConfig, setTenantConfig] = useState(getTenantConfig);
const [session, setSession] = useState(() => loadSession() || freshSession(source));
const [resumed] = useState(() => !!loadSession());
const [session, setSession] = useState(() => freshSession(source));
const [resumed] = useState(false); // no persistence → never a resumed session
useEffect(() => {
const refresh = () => setTenantConfig(getTenantConfig());
window.addEventListener(CONFIG_CHANGE_EVENT, refresh); // same-tab admin publish
window.addEventListener('storage', refresh); // another tab published
window.addEventListener('focus', refresh); // returning to this tab
return () => {
window.removeEventListener(CONFIG_CHANGE_EVENT, refresh);
window.removeEventListener('storage', refresh);
window.removeEventListener('focus', refresh);
};
window.addEventListener(CONFIG_CHANGE_EVENT, refresh); // admin publish
return () => window.removeEventListener(CONFIG_CHANGE_EVENT, refresh);
}, []);
// Autosave on every change (debounced via microtask coalescing not needed at this scale).
useEffect(() => {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(session)); } catch { /* quota */ }
}, [session]);
const logEvent = useCallback((eventType, payload = {}) => {
setSession(s => ({
...s,
@@ -158,9 +134,7 @@ export function EstimateWizardProvider({ children, source = 'public' }) {
}, []);
const reset = useCallback(() => {
const fresh = freshSession(source);
setSession(fresh);
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(fresh)); } catch { /* quota */ }
setSession(freshSession(source));
}, [source]);
// Fill every input with a valid demo answer so the wizard can be exercised fast.
+10 -10
View File
@@ -1,13 +1,16 @@
/**
* crmHandoff.js — completion handoff to CRM + nurture (spec §11, §13, §17 E2).
*
* The wizard is a self-contained module, so instead of mutating the global CRM
* store it writes a CRM-ready submission to localStorage. An internal page (or a
* future adapter) can read this list to create the contact/lead and fire the
* nurture playbook. Also returns the immutable compliance record (spec §18 G1).
* The wizard is a self-contained module. On completion it records a CRM-ready
* submission (read by the admin funnel analytics) and the real lead is added to the
* global mock store separately. Submissions are held in memory only — like the rest
* of the CRM demo store, nothing is written to localStorage, so the list resets on a
* full page reload. Also returns the immutable compliance record (spec §18 G1).
*/
const SUBMISSIONS_KEY = 'lup_estimate_wizard_submissions_v1';
// Module-scoped so submissions survive route changes within a session, with no
// persistence (matches the in-memory CRM store).
let submissionsStore = [];
const PKG_LABEL = { GOOD: 'Good', BETTER: 'Better', BEST: 'Best' };
@@ -91,14 +94,11 @@ export function buildLeadFromSession(session, tenantConfig, reportMeta) {
}
export function loadSubmissions() {
try {
const raw = localStorage.getItem(SUBMISSIONS_KEY);
return raw ? JSON.parse(raw) : [];
} catch { return []; }
return submissionsStore;
}
function persist(list) {
try { localStorage.setItem(SUBMISSIONS_KEY, JSON.stringify(list)); } catch { /* quota */ }
submissionsStore = list;
}
/**
+11 -12
View File
@@ -179,13 +179,15 @@ export const DISCLAIMER_TEMPLATE =
export const TENANT_CONFIG_VERSION = 'tenant-config-v1';
export const DISCLAIMER_VERSION = 'disclaimer-v1';
// Admin-editable overrides persist here (spec §14). The wizard merges them over
// the defaults so pricing/branding/feature flags are tenant-controlled, not code.
const CONFIG_STORAGE_KEY = 'lup_estimate_wizard_config_v1';
// Admin-editable overrides (spec §14). Held in memory only — like the rest of the
// CRM demo store, nothing is written to localStorage, so this resets on a full page
// reload. The wizard merges these over the defaults so pricing/branding/feature
// flags are tenant-controlled, not code. Module-scoped so the value survives route
// changes within a session (admin → wizard → leads) without persistence.
let configOverrides = {};
// Same-tab change signal. The public wizard snapshots config at mount, so when the
// admin publishes in the SAME tab/SPA there's no `storage` event to react to — we
// dispatch this so an open wizard can refresh without a hard reload.
// Same-tab change signal so an open wizard refreshes when the admin publishes,
// without a hard reload (the in-memory store has no `storage` event to lean on).
export const CONFIG_CHANGE_EVENT = 'lup-estimate-wizard-config-changed';
function notifyConfigChanged() {
@@ -212,19 +214,16 @@ function defaults() {
}
export function loadConfigOverrides() {
try {
const raw = localStorage.getItem(CONFIG_STORAGE_KEY);
return raw ? JSON.parse(raw) : {};
} catch { return {}; }
return configOverrides;
}
export function saveConfigOverrides(overrides) {
try { localStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify(overrides || {})); } catch { /* quota */ }
configOverrides = overrides || {};
notifyConfigChanged();
}
export function resetConfigOverrides() {
try { localStorage.removeItem(CONFIG_STORAGE_KEY); } catch { /* ignore */ }
configOverrides = {};
notifyConfigChanged();
}