Files
honeyDueWeb/docs/04-demo-mode.md
T
Trey t e2172c20f2 Rebrand from Casera/MyCrib to honeyDue
Total rebrand across Web project:
- Package name: casera-web -> honeydue-web
- Cookie: casera-token -> honeydue-token
- Theme store: casera-theme -> honeydue-theme
- File sharing: .casera -> .honeydue, component/function renames
- casera-file-handler.tsx -> honeydue-file-handler.tsx
- All UI text, metadata, OG tags updated
- Domains: casera.treytartt.com -> honeyDue.treytartt.com
- Demo data emails updated
- All documentation updated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 06:33:59 -06:00

361 lines
12 KiB
Markdown

# 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<Residence[]>;
get: (id: number) => Promise<ResidenceDetail>;
create: (data: CreateResidenceRequest) => Promise<Residence>;
update: (id: number, data: UpdateResidenceRequest) => Promise<Residence>;
delete: (id: number) => Promise<void>;
};
tasks: { /* same pattern */ };
contractors: { /* same pattern */ };
documents: { /* same pattern */ };
}
const DataProviderContext = createContext<DataProvider>(realApiProvider);
export function DemoProvider({ children }: { children: ReactNode }) {
const demoStore = useDemoStore();
const demoProvider = createDemoProvider(demoStore);
return (
<DataProviderContext.Provider value={demoProvider}>
<DemoBanner />
{children}
</DataProviderContext.Provider>
);
}
export function useDataProvider() {
return useContext(DataProviderContext);
}
```
### Layout Integration
```
src/app/(demo)/layout.tsx → wraps children in <DemoProvider>
src/app/(app)/layout.tsx → wraps children in <RealProvider> (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<DemoStore>((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 honeyDue 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 honeyDue — 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