feat(demo): add Demo Guide (8-step guided tour) and scenario overview
Adds a compact "Probeer een demonstratiescenario" page listing the 5 named scenarios with live readiness from the manifest, plus a Demo Guide side panel (bottom sheet on mobile) that walks an Operations Manager through all 8 steps with per-step context, live-resolved routes, sessionStorage progress, and a "Demo opnieuw voorbereiden" restart. Login's guided-demo CTA now actually opens the guide. Fixes a real mobile topbar overflow the new guide trigger introduced.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from "react";
|
||||
import { DEMO_GUIDE_STEPS } from "../data/demoGuideSteps";
|
||||
|
||||
const STORAGE_KEY = "mobilityops.demo-guide-progress";
|
||||
|
||||
interface StoredProgress {
|
||||
currentIndex: number;
|
||||
completed: string[];
|
||||
}
|
||||
|
||||
function readProgress(): StoredProgress {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return { currentIndex: 0, completed: [] };
|
||||
const parsed = JSON.parse(raw) as StoredProgress;
|
||||
return { currentIndex: parsed.currentIndex ?? 0, completed: parsed.completed ?? [] };
|
||||
} catch {
|
||||
return { currentIndex: 0, completed: [] };
|
||||
}
|
||||
}
|
||||
|
||||
function writeProgress(progress: StoredProgress) {
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(progress));
|
||||
}
|
||||
|
||||
interface DemoGuideState {
|
||||
open: boolean;
|
||||
currentIndex: number;
|
||||
completed: Set<string>;
|
||||
totalSteps: number;
|
||||
openGuide: () => void;
|
||||
closeGuide: () => void;
|
||||
toggleGuide: () => void;
|
||||
goToStep: (index: number) => void;
|
||||
completeAndAdvance: () => void;
|
||||
restart: () => void;
|
||||
}
|
||||
|
||||
const DemoGuideContext = createContext<DemoGuideState | undefined>(undefined);
|
||||
|
||||
export function DemoGuideProvider({ children }: { children: ReactNode }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [completed, setCompleted] = useState<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
const stored = readProgress();
|
||||
setCurrentIndex(stored.currentIndex);
|
||||
setCompleted(new Set(stored.completed));
|
||||
}, []);
|
||||
|
||||
const persist = useCallback((index: number, done: Set<string>) => {
|
||||
writeProgress({ currentIndex: index, completed: Array.from(done) });
|
||||
}, []);
|
||||
|
||||
const goToStep = useCallback(
|
||||
(index: number) => {
|
||||
const clamped = Math.max(0, Math.min(index, DEMO_GUIDE_STEPS.length - 1));
|
||||
setCurrentIndex(clamped);
|
||||
persist(clamped, completed);
|
||||
},
|
||||
[completed, persist],
|
||||
);
|
||||
|
||||
const completeAndAdvance = useCallback(() => {
|
||||
setCompleted((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(DEMO_GUIDE_STEPS[currentIndex].id);
|
||||
const nextIndex = Math.min(currentIndex + 1, DEMO_GUIDE_STEPS.length - 1);
|
||||
persist(nextIndex, next);
|
||||
setCurrentIndex(nextIndex);
|
||||
return next;
|
||||
});
|
||||
}, [currentIndex, persist]);
|
||||
|
||||
const restart = useCallback(() => {
|
||||
setCurrentIndex(0);
|
||||
setCompleted(new Set());
|
||||
writeProgress({ currentIndex: 0, completed: [] });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DemoGuideContext.Provider
|
||||
value={{
|
||||
open,
|
||||
currentIndex,
|
||||
completed,
|
||||
totalSteps: DEMO_GUIDE_STEPS.length,
|
||||
openGuide: () => setOpen(true),
|
||||
closeGuide: () => setOpen(false),
|
||||
toggleGuide: () => setOpen((v) => !v),
|
||||
goToStep,
|
||||
completeAndAdvance,
|
||||
restart,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DemoGuideContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useDemoGuide(): DemoGuideState {
|
||||
const ctx = useContext(DemoGuideContext);
|
||||
if (!ctx) throw new Error("useDemoGuide must be used within DemoGuideProvider");
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user