# Phase 4 — Demo Mode Build a fully sandboxed demo experience with realistic mock data and no backend dependency. ## Checklist - [ ] Mock data seeds (2 residences, 15 tasks across columns, 5 contractors, 8 documents) - [ ] DemoDataProvider implementing same interface as real API client - [ ] Session-scoped Zustand store (resets on close) - [ ] All CRUD operations work against in-memory store - [ ] Demo banner + "Sign Up" CTA - [ ] Demo landing page with feature preview --- ## Architecture ``` User visits /demo → Server creates a session ID (cookie, no account needed) → App loads with pre-built mock data (realistic residences, tasks, contractors, docs) → All CRUD operations work against an in-memory store scoped to that session → No backend API calls — everything is client-side → Store resets on session expiry (or browser close) → Banner: "You're in demo mode — Sign up to save your data" → "Sign Up" converts session → real account (data NOT migrated, clean start) ``` ### Context Switch A `DataProvider` context determines whether the app uses real API calls or demo data: ```typescript // src/lib/demo/demo-context.tsx interface DataProvider { residences: { list: () => Promise; get: (id: number) => Promise; create: (data: CreateResidenceRequest) => Promise; update: (id: number, data: UpdateResidenceRequest) => Promise; delete: (id: number) => Promise; }; tasks: { /* same pattern */ }; contractors: { /* same pattern */ }; documents: { /* same pattern */ }; } const DataProviderContext = createContext(realApiProvider); export function DemoProvider({ children }: { children: ReactNode }) { const demoStore = useDemoStore(); const demoProvider = createDemoProvider(demoStore); return ( {children} ); } export function useDataProvider() { return useContext(DataProviderContext); } ``` ### Layout Integration ``` src/app/(demo)/layout.tsx → wraps children in src/app/(app)/layout.tsx → wraps children in (or just uses default) ``` Both route groups share the same page components — the data provider abstraction means zero code duplication. --- ## 1. Mock Data Seeds ### Residences (2) ```typescript const mockResidences = [ { id: 1, name: "Maple Street House", address: "142 Maple Street, Austin, TX", residenceType: "House", taskSummary: { overdue: 2, dueToday: 1, dueSoon: 3, upcoming: 5, completed: 8 }, contractorCount: 3, documentCount: 5, }, { id: 2, name: "Downtown Apartment", address: "800 Congress Ave #412, Austin, TX", residenceType: "Apartment", taskSummary: { overdue: 0, dueToday: 1, dueSoon: 2, upcoming: 3, completed: 4 }, contractorCount: 2, documentCount: 3, }, ]; ``` ### Tasks (15 across columns) Realistic tasks distributed across kanban columns: | Column | Count | Examples | |--------|-------|---------| | Overdue | 2 | "Replace smoke detector batteries" (due 2 weeks ago), "Clean gutters" (due 1 month ago) | | Due Today | 2 | "HVAC filter replacement", "Test garage door opener" | | Due Soon | 3 | "Power wash driveway" (5 days), "Inspect roof" (2 weeks), "Service water heater" (3 weeks) | | Upcoming | 4 | Various maintenance tasks due 1-3 months out | | In Progress | 1 | "Paint master bedroom" (in progress since yesterday) | | Completed | 3 | "Fix kitchen faucet leak", "Install new doorbell", "Reseal bathroom grout" | Each task has realistic: - Categories (Maintenance, Cleaning, Repair, etc.) - Priorities (High, Medium, Low) - Estimated costs ($25 - $500) - Some assigned to contractors - Completed tasks have completion records with notes ### Contractors (5) ```typescript const mockContractors = [ { id: 1, name: "Bob Martinez", company: "Bob's Plumbing", phone: "(512) 555-0101", email: "bob@bobsplumbing.com", specialty: "Plumbing", isFavorite: true }, { id: 2, name: "Sarah Chen", company: "Chen Electric", phone: "(512) 555-0202", email: "sarah@chenelectric.com", specialty: "Electrical", isFavorite: true }, { id: 3, name: "Mike Johnson", company: null, phone: "(512) 555-0303", email: null, specialty: "General Handyman", isFavorite: false }, { id: 4, name: "Lisa Park", company: "Park Landscaping", phone: "(512) 555-0404", email: "lisa@parklandscaping.com", specialty: "Landscaping", isFavorite: false }, { id: 5, name: "James Wilson", company: "Wilson HVAC", phone: "(512) 555-0505", email: "james@wilsonhvac.com", specialty: "HVAC", isFavorite: true }, ]; ``` ### Documents (8) Mix of warranties and general documents: | Type | Count | Examples | |------|-------|---------| | Warranty | 4 | "Samsung Refrigerator Warranty" (expires 2027), "Roof Warranty" (expires 2035), "HVAC System Warranty" (expired 2024), "Dishwasher Warranty" (expires 2026) | | Document | 4 | "Home Insurance Policy", "Property Tax Statement 2025", "HOA Agreement", "Electrical Inspection Certificate" | ### Lookups Pre-loaded categories, priorities, frequencies, specialties — same as production static data. --- ## 2. DemoDataProvider ```typescript // src/lib/demo/demo-provider.ts export function createDemoProvider(store: DemoStore): DataProvider { return { residences: { list: async () => store.getResidences(), get: async (id) => store.getResidence(id), create: async (data) => store.createResidence(data), update: async (id, data) => store.updateResidence(id, data), delete: async (id) => store.deleteResidence(id), }, tasks: { list: async () => store.getTasks(), create: async (data) => store.createTask(data), complete: async (id, data) => store.completeTask(id, data), // ... all task actions }, contractors: { /* ... */ }, documents: { /* ... */ }, }; } ``` All operations are synchronous under the hood (wrapped in async for interface compatibility). They: 1. Validate input (same Zod schemas as real forms) 2. Generate auto-increment IDs 3. Update the Zustand store 4. Return the created/updated object --- ## 3. Session-Scoped Zustand Store ```typescript // src/lib/demo/demo-store.ts interface DemoStore { // Data residences: Residence[]; tasks: Task[]; contractors: Contractor[]; documents: Document[]; completions: TaskCompletion[]; lookups: StaticData; // Auto-increment IDs nextIds: { residence: number; task: number; contractor: number; document: number }; // CRUD operations createResidence: (data: CreateResidenceRequest) => Residence; updateResidence: (id: number, data: UpdateResidenceRequest) => Residence; deleteResidence: (id: number) => void; // ... same for all domains // Task-specific completeTask: (id: number, data: TaskCompletionRequest) => Task; markInProgress: (id: number) => Task; cancelTask: (id: number) => Task; archiveTask: (id: number) => Task; // Reset reset: () => void; } export const useDemoStore = create((set, get) => ({ residences: mockResidences, tasks: mockTasks, contractors: mockContractors, documents: mockDocuments, completions: mockCompletions, lookups: mockLookups, nextIds: { residence: 3, task: 16, contractor: 6, document: 9 }, createResidence: (data) => { const id = get().nextIds.residence; const residence = { id, ...data, createdAt: new Date().toISOString() }; set((state) => ({ residences: [...state.residences, residence], nextIds: { ...state.nextIds, residence: id + 1 }, })); return residence; }, // ... other operations reset: () => set({ residences: mockResidences, tasks: mockTasks, contractors: mockContractors, documents: mockDocuments, completions: mockCompletions, nextIds: { residence: 3, task: 16, contractor: 6, document: 9 }, }), })); ``` ### Session Isolation - Store is created fresh when `/demo` is accessed - No `persist` middleware — store is pure in-memory - Closing the browser tab or navigating away clears the store - Each visitor gets their own Zustand store instance --- ## 4. Demo Banner Persistent banner at the top of the demo app: ``` ┌─────────────────────────────────────────────────────────────────┐ │ 🎯 You're exploring Casera in demo mode. [Sign Up Free] │ │ Changes aren't saved. Create an account to get started! │ └─────────────────────────────────────────────────────────────────┘ ``` - Fixed at top, doesn't scroll - "Sign Up Free" links to `/register` - Subtle, non-intrusive (light background, small text) - Can be temporarily dismissed (per session only) --- ## 5. Demo Landing Page ### Route: `/demo` Before entering the demo, show a brief preview page: ``` ┌─────────────────────────────────────────────────────┐ │ │ │ 🏠 Try Casera — No Account Needed │ │ │ │ Manage your home maintenance, track tasks, │ │ organize contractors, and store documents. │ │ │ │ [Start Demo →] │ │ │ │ ──────────────────────────────────────── │ │ │ │ Preview: │ │ [Screenshot of kanban board] │ │ [Screenshot of residence detail] │ │ │ │ Already have an account? [Log In] │ │ │ └─────────────────────────────────────────────────────┘ ``` Clicking "Start Demo" initializes the demo store and redirects to `/demo/residences`. --- ## 6. TanStack Query Integration In demo mode, TanStack Query hooks still work — they just call the demo provider instead of real API: ```typescript // src/lib/hooks/useResidences.ts export function useResidences() { const provider = useDataProvider(); return useQuery({ queryKey: ['residences'], queryFn: () => provider.residences.list(), staleTime: Infinity, // Demo data never goes stale }); } ``` Mutations also work through the provider: ```typescript export function useCreateResidence() { const provider = useDataProvider(); const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateResidenceRequest) => provider.residences.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['residences'] }); }, }); } ``` --- ## Key Design Decisions | Decision | Rationale | |----------|-----------| | No backend calls in demo | Zero infrastructure cost, no rate limiting, instant responses, no auth needed | | Session-scoped (no persistence) | Prevents demo data from accumulating, ensures fresh experience each visit | | Same components as real app | Zero code duplication, demo accurately represents the product | | Provider pattern (not if/else) | Clean separation, easy to test, no demo logic leaking into components | | Data NOT migrated on signup | Avoids edge cases with ID conflicts, mock data quality issues | ## Deliverables At the end of Phase 4, you should have: 1. Realistic mock data covering all 4 domains 2. Fully functional CRUD in demo mode (create, edit, delete tasks/residences/etc.) 3. Demo banner with sign-up CTA 4. Landing page with preview 5. Zero backend dependency for the demo experience 6. Same UI components used for both demo and real app