commit 5a50d77515e5fb5c8e4d16943301ac714077d242 Author: Trey t Date: Tue Mar 3 09:31:29 2026 -0600 feat: complete Phase 3 — advanced features for Casera web app Adds sharing (residence share codes, join, user management, .casera file export/import), subscription status with feature comparison, notification preferences with bell icon, profile settings (edit info, change password, theme picker, delete account), onboarding wizard with create/join paths, enhanced dashboard with stats cards, Recharts completion chart, recent activity feed, and task report PDF download. Co-Authored-By: Claude Opus 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/components.json b/components.json new file mode 100644 index 0000000..03909d9 --- /dev/null +++ b/components.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/docs/00-overview.md b/docs/00-overview.md new file mode 100644 index 0000000..0b1353a --- /dev/null +++ b/docs/00-overview.md @@ -0,0 +1,115 @@ +# Casera Web App — Build Plan Overview + +## What We're Building + +Full parity web app for Casera: **46 screens**, **104 API operations**, **4 domains** (Residences, Tasks, Contractors, Documents), plus auth, onboarding, subscriptions, settings, and a **sandboxed demo mode** with mock data. + +## Tech Stack + +| Layer | Choice | +|---|---| +| Framework | Next.js 15 (App Router) | +| Language | TypeScript | +| Styling | Tailwind CSS 4 | +| Components | shadcn/ui + Radix primitives | +| State (server) | TanStack Query v5 | +| State (client) | Zustand | +| Forms | React Hook Form + Zod validation | +| Auth | httpOnly cookie storing API token, Next.js middleware for route protection | +| Kanban | dnd-kit (drag-and-drop) for task board | +| Charts/metrics | Recharts (for summary dashboard) | +| Icons | Lucide (matches shadcn/ui) | +| Testing | Vitest + Playwright | +| Deployment | Docker → Dokku (separate app) | + +## Why Next.js + shadcn/ui + +- SSR for the marketing landing + demo preview (SEO, social sharing) +- shadcn/ui gives the exact component primitives this app needs (kanban boards, forms, dialogs, data tables) +- TanStack Query maps perfectly to the existing `APILayer` → `DataManager` cache pattern +- Separate Dokku app keeps deployment independent +- Biggest ecosystem for finding solutions + +## Project Structure + +``` +myCribAPI-Web/ +├── src/ +│ ├── app/ # Next.js App Router +│ │ ├── (marketing)/ # Public pages (landing, pricing) +│ │ ├── (auth)/ # Login, Register, Forgot Password, Reset +│ │ ├── (onboarding)/ # Multi-step onboarding flow +│ │ ├── (demo)/ # Demo mode (sandboxed) +│ │ │ └── layout.tsx # Wraps demo in DemoProvider context +│ │ ├── (app)/ # Authenticated app +│ │ │ ├── residences/ # List, [id] detail, new, [id]/edit +│ │ │ ├── tasks/ # Kanban board, new, [id]/edit, [id]/complete +│ │ │ ├── contractors/ # List, [id] detail, new, [id]/edit +│ │ │ ├── documents/ # Tabs (warranties/documents), [id] detail +│ │ │ └── settings/ # Profile, notifications, themes, subscription +│ │ └── layout.tsx # Root layout +│ ├── components/ +│ │ ├── ui/ # shadcn/ui primitives +│ │ ├── forms/ # Reusable form components +│ │ ├── cards/ # ResidenceCard, TaskCard, ContractorCard, etc. +│ │ ├── kanban/ # KanbanBoard, KanbanColumn, TaskActions +│ │ └── shared/ # EmptyState, LoadingOverlay, ErrorBanner, etc. +│ ├── lib/ +│ │ ├── api/ # API client (typed fetch wrappers per domain) +│ │ ├── demo/ # DemoDataProvider + mock data seeds +│ │ ├── auth/ # Token management, middleware helpers +│ │ ├── hooks/ # useResidences, useTasks, etc. (TanStack Query) +│ │ └── types/ # TypeScript types matching API DTOs +│ ├── stores/ # Zustand stores (auth, theme, demo state) +│ └── styles/ # Tailwind theme config, design tokens +├── public/ # Static assets +├── Dockerfile +├── next.config.ts +├── tailwind.config.ts +├── package.json +└── CLAUDE.md +``` + +## Build Phases + +| Phase | Focus | Details | +|-------|-------|---------| +| [Phase 1](./01-foundation.md) | Foundation | Scaffold, auth, layout, design system | +| [Phase 2](./02-core-crud.md) | Core CRUD | 4 domains: residences, tasks, contractors, documents | +| [Phase 3](./03-advanced-features.md) | Advanced Features | Sharing, subscription, notifications, onboarding | +| [Phase 4](./04-demo-mode.md) | Demo Mode | Mock data, in-memory store, session isolation | +| [Phase 5](./05-polish-deploy.md) | Polish & Deploy | Responsive, error handling, Dockerfile, tests | + +## Web-Only Adaptations + +| Mobile Feature | Web Equivalent | +|---|---| +| Push notifications | Not needed (no push API for web in scope) | +| Widgets | Not applicable | +| .casera file import/export | File download (export) + drag-and-drop or file picker (import) | +| Apple/Google Sign In | OAuth redirect flow (same backend endpoints) | +| StoreKit subscription | Stripe Checkout or link to App Store (TBD) | +| Camera capture | File upload input (no camera API needed) | +| Haptics | N/A | +| Background refresh | TanStack Query `refetchInterval` + window focus refetch | + +## Risks & Mitigations + +| Risk | Mitigation | +|---|---| +| Kanban board performance with many tasks | Virtualize columns with `react-window`, limit initial render to 50 tasks per column | +| Demo mode state leaking between sessions | Session ID in httpOnly cookie, Zustand store scoped to session, auto-clear on expiry | +| File upload size limits | Match backend 10MB limit, client-side validation before upload | +| OAuth redirect for Apple/Google | Use backend's existing social auth endpoints, redirect flow instead of native SDK | +| Subscription without StoreKit | Phase 1: link to App Store. Phase 2: add Stripe for web-only purchases (requires backend work) | +| 46 screens is a lot of UI | shadcn/ui + consistent patterns reduce per-screen effort to ~1-2 hours each after foundation is built | + +## Security Considerations + +| Concern | Approach | +|---|---| +| Auth token in browser | httpOnly cookie, not accessible to JS, CSRF protection via SameSite=Strict | +| Demo mode data isolation | No backend calls, purely client-side, session-scoped store, no persistence | +| API compatibility | 100% same endpoints, same token format (`Token `), same request/response shapes | +| Mobile feature gaps | Push notifications and widgets explicitly excluded. Everything else covered. | +| Deployment independence | Separate Dokku app, own domain (e.g., `app.casera.treytartt.com`), no coupling to Go API deployment | diff --git a/docs/01-foundation.md b/docs/01-foundation.md new file mode 100644 index 0000000..c0a9467 --- /dev/null +++ b/docs/01-foundation.md @@ -0,0 +1,275 @@ +# Phase 1 — Foundation + +Scaffold the project, wire up auth, build the app shell, and establish the design system. + +## Checklist + +- [ ] Next.js 15 project init with App Router + TypeScript +- [ ] Tailwind CSS 4 configuration +- [ ] shadcn/ui setup (install CLI, add base components) +- [ ] TypeScript types from API DTOs (all request/response shapes) +- [ ] API client layer (typed fetch wrappers, token injection, error handling) +- [ ] Auth flows: Login, Register, Verify Email, Forgot/Reset Password +- [ ] Auth middleware (route protection, token cookie) +- [ ] App shell: sidebar nav + top bar +- [ ] Design system: color tokens matching mobile app +- [ ] Theme switcher (11 themes, persisted to localStorage) + +## 1. Project Scaffold + +```bash +npx create-next-app@latest myCribAPI-Web --typescript --tailwind --eslint --app --src-dir +cd myCribAPI-Web +npx shadcn@latest init +``` + +Install core dependencies: +```bash +npm install @tanstack/react-query zustand react-hook-form @hookform/resolvers zod +npm install lucide-react recharts @dnd-kit/core @dnd-kit/sortable +npm install -D vitest @playwright/test +``` + +## 2. TypeScript Types + +Generate TypeScript types matching the Go API DTOs. Source from: +- `myCribAPI-go/internal/dto/requests/` — request shapes +- `myCribAPI-go/internal/dto/responses/` — response shapes +- `myCribAPI-go/internal/models/` — entity shapes + +Key types to define in `src/lib/types/`: + +``` +types/ +├── api.ts # ApiResult, PaginatedResponse, ErrorResponse +├── auth.ts # LoginRequest, RegisterRequest, UserResponse, TokenResponse +├── residence.ts # Residence, ResidenceDetail, MyResidencesResponse, CreateResidenceRequest +├── task.ts # Task, TaskDetail, TaskColumnsResponse, CreateTaskRequest, TaskCompletionRequest +├── contractor.ts # Contractor, ContractorDetail, CreateContractorRequest +├── document.ts # Document, DocumentDetail, CreateDocumentRequest +├── subscription.ts # SubscriptionStatus, FeatureBenefit, UpgradeTrigger +├── lookups.ts # TaskCategory, TaskPriority, TaskFrequency, ContractorSpecialty +└── notification.ts # NotificationPreference, Notification +``` + +## 3. API Client Layer + +Create typed fetch wrappers in `src/lib/api/`: + +``` +api/ +├── client.ts # Base fetch wrapper (token injection, error handling, base URL) +├── auth.ts # login(), register(), forgotPassword(), resetPassword() +├── residences.ts # getResidences(), getResidence(), createResidence(), updateResidence(), deleteResidence() +├── tasks.ts # getTasks(), getTasksByResidence(), createTask(), updateTask(), completeTask(), etc. +├── contractors.ts # getContractors(), getContractor(), createContractor(), etc. +├── documents.ts # getDocuments(), getDocument(), createDocument(), uploadFile(), etc. +├── lookups.ts # getStaticData(), getUpgradeTriggers() +├── subscription.ts # getSubscriptionStatus(), etc. +└── notifications.ts # getPreferences(), updatePreference(), etc. +``` + +**Base client pattern:** + +```typescript +// src/lib/api/client.ts +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'https://mycrib.treytartt.com/api'; + +async function apiFetch( + path: string, + options: RequestInit = {} +): Promise { + const res = await fetch(`${API_BASE_URL}${path}`, { + ...options, + credentials: 'include', // Send httpOnly cookie + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + + if (!res.ok) { + const error = await res.json().catch(() => ({ detail: 'Unknown error' })); + throw new ApiError(res.status, error.detail || error.message); + } + + return res.json(); +} +``` + +## 4. Auth Flows + +### Pages + +| Route | Screen | Components | +|-------|--------|------------| +| `/login` | Login form | Email, password, "Forgot password?" link, social sign-in buttons | +| `/register` | Registration form | First name, last name, email, password, confirm password | +| `/verify-email` | Email verification | Code input, resend button | +| `/forgot-password` | Forgot password | Email input, submit | +| `/reset-password` | Reset password | New password, confirm, token from URL | + +### Auth Token Strategy + +``` +Login → Server returns token → API route stores token in httpOnly cookie → Client redirects to /app + ↓ + All subsequent requests include cookie + ↓ + Next.js middleware reads cookie for route protection +``` + +- Token format: `Token ` (Django-compatible, same as mobile) +- Cookie: `httpOnly`, `SameSite=Strict`, `Secure` in production +- Next.js middleware in `src/middleware.ts` checks cookie existence for protected routes + +### Auth Store (Zustand) + +```typescript +// src/stores/auth.ts +interface AuthState { + user: UserResponse | null; + isAuthenticated: boolean; + isLoading: boolean; + login: (email: string, password: string) => Promise; + register: (data: RegisterRequest) => Promise; + logout: () => Promise; + fetchUser: () => Promise; +} +``` + +## 5. App Shell + +### Layout Structure + +``` +┌─────────────────────────────────────────────────────┐ +│ Top Bar: Logo | Search (optional) | Profile Menu │ +├──────────┬──────────────────────────────────────────┤ +│ │ │ +│ Sidebar │ Main Content │ +│ │ │ +│ 🏠 Home │ │ +│ 🏘 Resid │ │ +│ ✅ Tasks │ │ +│ 👷 Contr │ │ +│ 📄 Docs │ │ +│ │ │ +│ ──────── │ │ +│ ⚙ Setti │ │ +│ │ │ +├──────────┴──────────────────────────────────────────┤ +│ (mobile: bottom tab bar instead of sidebar) │ +└─────────────────────────────────────────────────────┘ +``` + +- **Desktop (≥1024px)**: Collapsible sidebar + top bar +- **Tablet (768-1023px)**: Collapsed sidebar (icons only) + top bar +- **Mobile (<768px)**: Bottom tab bar (matches iOS app), no sidebar + +### Navigation Items + +| Icon | Label | Route | +|------|-------|-------| +| Home | Home | `/app` | +| Building | Residences | `/app/residences` | +| CheckSquare | Tasks | `/app/tasks` | +| HardHat | Contractors | `/app/contractors` | +| FileText | Documents | `/app/documents` | +| Settings | Settings | `/app/settings` | + +## 6. Design System + +### Color Tokens + +Map the mobile app's color palette to CSS custom properties and Tailwind config: + +```css +/* src/styles/theme.css */ +:root { + /* Default theme (matches iOS appPrimary, etc.) */ + --color-primary: #07A0C3; /* BlueGreen */ + --color-secondary: #0055A5; /* Cerulean */ + --color-accent: #F5A623; /* BrightAmber */ + --color-error: #DD1C1A; /* PrimaryScarlet */ + --color-bg-primary: #FFF1D0; /* Cream */ + --color-bg-secondary: #F0F4F8; /* Blue-gray */ + --color-text-primary: #1A1A1A; + --color-text-secondary: #6B7280; + --color-text-on-primary: #FFFFFF; +} + +[data-theme="dark"] { + --color-bg-primary: #0A1929; + --color-bg-secondary: #1A2A3A; + --color-text-primary: #F0F0F0; + --color-text-secondary: #9CA3AF; +} +``` + +### 11 Themes + +Match the mobile app's theme system: + +| Theme | Primary | Secondary | +|-------|---------|-----------| +| Default | #07A0C3 | #0055A5 | +| Teal | TBD | TBD | +| Ocean | TBD | TBD | +| Forest | TBD | TBD | +| Sunset | TBD | TBD | +| Monochrome | TBD | TBD | +| Lavender | TBD | TBD | +| Crimson | TBD | TBD | +| Midnight | TBD | TBD | +| Desert | TBD | TBD | +| Mint | TBD | TBD | + +Theme values sourced from `MyCribKMM/composeApp/src/commonMain/.../ui/theme/ThemeColors.kt`. + +### Spacing + +```css +--spacing-xs: 4px; +--spacing-sm: 8px; +--spacing-md: 12px; +--spacing-lg: 16px; +--spacing-xl: 24px; +``` + +## 7. TanStack Query Setup + +```typescript +// src/lib/hooks/useResidences.ts +export function useResidences() { + return useQuery({ + queryKey: ['residences'], + queryFn: () => api.residences.getResidences(), + staleTime: 60 * 60 * 1000, // 1 hour (matches mobile cache timeout) + refetchOnWindowFocus: true, + }); +} +``` + +## 8. Lookups Initialization + +Match the mobile pattern — after login, fetch `/api/static_data/` and `/api/upgrade-triggers/`: + +```typescript +// After successful login +await queryClient.prefetchQuery({ + queryKey: ['lookups'], + queryFn: () => api.lookups.getStaticData(), + staleTime: Infinity, // ETag-based, manually invalidate +}); +``` + +## Deliverables + +At the end of Phase 1, you should have: +1. A working Next.js app with Tailwind + shadcn/ui +2. Login and registration flows that authenticate against the Go API +3. A protected app shell with sidebar navigation +4. A design system with color tokens matching the mobile app +5. Typed API client with error handling +6. Theme switching (light/dark + 11 color themes) diff --git a/docs/02-core-crud.md b/docs/02-core-crud.md new file mode 100644 index 0000000..76acb0b --- /dev/null +++ b/docs/02-core-crud.md @@ -0,0 +1,355 @@ +# Phase 2 — Core CRUD + +Build the 4 primary domains: Residences, Tasks, Contractors, and Documents. + +## Checklist + +- [ ] Residences: list (with summary card), detail, create/edit form, delete +- [ ] Tasks: kanban board (drag-and-drop columns), create/edit form, task actions (complete, cancel, archive, in-progress) +- [ ] Task completion: form with file upload (photos), completion history with image viewer +- [ ] Task templates: autocomplete search + template browser +- [ ] Contractors: list (search, filter by favorite/specialty), detail (quick actions), create/edit form, delete +- [ ] Documents: tabbed view (warranties/documents), detail, create/edit form (type-specific fields), file upload/download, image gallery + +--- + +## 1. Residences + +### Screens + +| Route | Screen | Description | +|-------|--------|-------------| +| `/app/residences` | Residence List | Grid of residence cards with summary data | +| `/app/residences/new` | Create Residence | Form: name, address, type, photo | +| `/app/residences/[id]` | Residence Detail | Summary dashboard, task/contractor/document counts, users | +| `/app/residences/[id]/edit` | Edit Residence | Same form as create, pre-filled | + +### API Endpoints Used + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| GET | `/api/residences/my/` | User's residences with summaries | +| GET | `/api/residences/:id/` | Residence detail | +| POST | `/api/residences/` | Create residence | +| PUT | `/api/residences/:id/` | Update residence | +| DELETE | `/api/residences/:id/` | Delete residence | + +### Components + +- **ResidenceCard**: Summary card showing name, address, type icon, task counts (overdue, due soon, total) +- **ResidenceForm**: React Hook Form + Zod. Fields: name (required), address, residence type (dropdown from lookups), photo upload +- **ResidenceSummary**: Dashboard showing task breakdown, contractor count, document count + +### TanStack Query Hooks + +```typescript +useResidences() // GET /residences/my/ +useResidence(id) // GET /residences/:id/ +useCreateResidence() // POST /residences/ +useUpdateResidence(id) // PUT /residences/:id/ +useDeleteResidence(id) // DELETE /residences/:id/ +``` + +### Cache Invalidation + +After create/update/delete → invalidate `['residences']` and `['residence', id]` queries. + +--- + +## 2. Tasks + +### Screens + +| Route | Screen | Description | +|-------|--------|-------------| +| `/app/tasks` | Kanban Board | All tasks across residences, grouped by column | +| `/app/residences/[id]/tasks` | Residence Kanban | Tasks for a specific residence | +| `/app/tasks/new` | Create Task | Full task form | +| `/app/tasks/[id]` | Task Detail | Full task info, completion history, actions | +| `/app/tasks/[id]/edit` | Edit Task | Same form as create, pre-filled | +| `/app/tasks/[id]/complete` | Complete Task | Completion form with photo upload | + +### Kanban Board + +Columns (from backend `TaskColumnsResponse`): +1. **Overdue** (red indicator) +2. **Due Today** (orange indicator) +3. **Due Soon** (yellow indicator, within 30 days) +4. **Upcoming** (blue indicator) +5. **In Progress** (green indicator) +6. **Completed** (gray indicator) + +**Drag-and-drop**: Use `@dnd-kit/core` + `@dnd-kit/sortable` for dragging tasks between columns. Dropping a task triggers the appropriate API action: +- Drop on "In Progress" → `POST /tasks/:id/in-progress/` +- Drop on "Completed" → Opens completion form +- Other moves may just reorder (no API call) + +**Board controls**: +- Filter by residence (dropdown) +- Filter by category, priority +- Search by title + +### Task Form Fields + +| Field | Type | Required | Source | +|-------|------|----------|--------| +| Title | Text / Autocomplete | Yes | Free text or template | +| Residence | Select | Yes | User's residences | +| Category | Select | No | Lookups (categories) | +| Priority | Select | No | Lookups (priorities) | +| Due Date | Date picker | No | Calendar | +| Frequency | Select | No | Lookups (frequencies) — for recurring tasks | +| Estimated Cost | Number | No | Currency input | +| Notes | Textarea | No | Free text | +| Assigned Contractor | Select | No | User's contractors | + +### Task Actions + +| Action | Endpoint | UI Trigger | +|--------|----------|------------| +| Complete | `POST /tasks/:id/complete/` | Button + completion form | +| Mark In Progress | `POST /tasks/:id/in-progress/` | Button or drag | +| Cancel | `POST /tasks/:id/cancel/` | Menu action | +| Archive | `POST /tasks/:id/archive/` | Menu action | +| Uncancel | `POST /tasks/:id/uncancel/` | Menu action | +| Unarchive | `POST /tasks/:id/unarchive/` | Menu action | + +### Task Completion Form + +| Field | Type | Required | +|-------|------|----------| +| Completed At | DateTime | Yes (default: now) | +| Actual Cost | Number | No | +| Notes | Textarea | No | +| Rating | Star rating (1-5) | No | +| Photos | File upload (multiple) | No | + +File upload uses `FormData` with multipart, same as mobile's `submitFormWithBinaryData`. + +### Task Templates + +- **Autocomplete search**: As user types in title field, search `/api/task-templates/search/?q=...` +- **Template browser**: Modal with categorized templates, click to prefill form +- Template data populates: title, category, priority, estimated cost, frequency + +### API Endpoints Used + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| GET | `/api/tasks/` | All user's tasks (kanban columns) | +| GET | `/api/tasks/by-residence/:id/` | Tasks for one residence | +| POST | `/api/tasks/` | Create task | +| PUT | `/api/tasks/:id/` | Update task | +| DELETE | `/api/tasks/:id/` | Delete task | +| POST | `/api/tasks/:id/complete/` | Complete task | +| POST | `/api/tasks/:id/in-progress/` | Mark in progress | +| POST | `/api/tasks/:id/cancel/` | Cancel task | +| POST | `/api/tasks/:id/archive/` | Archive task | +| POST | `/api/task-completions/` | Create completion (with images) | +| GET | `/api/task-completions/` | List completions | +| GET | `/api/task-templates/search/` | Search templates | + +### TanStack Query Hooks + +```typescript +useTasks() // GET /tasks/ +useTasksByResidence(residenceId) // GET /tasks/by-residence/:id/ +useTask(id) // GET /tasks/:id/ +useCreateTask() // POST /tasks/ +useUpdateTask(id) // PUT /tasks/:id/ +useDeleteTask(id) // DELETE /tasks/:id/ +useCompleteTask(id) // POST /tasks/:id/complete/ +useTaskTemplateSearch(query) // GET /task-templates/search/?q=... +useCreateCompletion() // POST /task-completions/ +``` + +--- + +## 3. Contractors + +### Screens + +| Route | Screen | Description | +|-------|--------|-------------| +| `/app/contractors` | Contractor List | Filterable list with search | +| `/app/contractors/new` | Create Contractor | Form with contact details | +| `/app/contractors/[id]` | Contractor Detail | Full info, quick actions, linked tasks | +| `/app/contractors/[id]/edit` | Edit Contractor | Same form as create, pre-filled | + +### List Features + +- **Search**: Filter by name, company +- **Filter**: By specialty (from lookups), by favorite +- **Sort**: By name, recently added +- **Quick actions**: Call, email, favorite toggle + +### Contractor Form Fields + +| Field | Type | Required | Source | +|-------|------|----------|--------| +| Name | Text | Yes | Free text | +| Company | Text | No | Free text | +| Phone | Phone | No | Tel input | +| Email | Email | No | Email input | +| Specialty | Select | No | Lookups (specialties) | +| Notes | Textarea | No | Free text | +| Is Favorite | Toggle | No | Boolean | +| Residence | Select | Yes | User's residences | + +### Contractor Detail + +- Contact info with click-to-call (`tel:`) and click-to-email (`mailto:`) +- Favorite toggle +- Linked tasks (tasks assigned to this contractor) +- Edit / delete actions + +### API Endpoints Used + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| GET | `/api/contractors/` | User's contractors | +| GET | `/api/contractors/:id/` | Contractor detail | +| POST | `/api/contractors/` | Create contractor | +| PUT | `/api/contractors/:id/` | Update contractor | +| DELETE | `/api/contractors/:id/` | Delete contractor | +| POST | `/api/contractors/:id/favorite/` | Toggle favorite | + +### TanStack Query Hooks + +```typescript +useContractors() // GET /contractors/ +useContractor(id) // GET /contractors/:id/ +useCreateContractor() // POST /contractors/ +useUpdateContractor(id) // PUT /contractors/:id/ +useDeleteContractor(id) // DELETE /contractors/:id/ +useToggleFavorite(id) // POST /contractors/:id/favorite/ +``` + +--- + +## 4. Documents + +### Screens + +| Route | Screen | Description | +|-------|--------|-------------| +| `/app/documents` | Document List | Tabbed view: Warranties / Documents | +| `/app/documents/new` | Create Document | Form with type-specific fields | +| `/app/documents/[id]` | Document Detail | Full info, file viewer, image gallery | +| `/app/documents/[id]/edit` | Edit Document | Same form as create, pre-filled | + +### Tabbed View + +- **Warranties tab**: Documents with `is_warranty = true`. Shows expiry dates, status (active/expired/expiring soon) +- **Documents tab**: All other documents. Shows type, date added, file preview + +### Document Form Fields + +| Field | Type | Required | Source | +|-------|------|----------|--------| +| Title | Text | Yes | Free text | +| Residence | Select | Yes | User's residences | +| Type | Select | No | Document type options | +| Notes | Textarea | No | Free text | +| File | File upload | No | File picker | +| Is Warranty | Toggle | No | Boolean | +| Purchase Date | Date | Conditional | If warranty | +| Expiry Date | Date | Conditional | If warranty | +| Purchase Price | Number | Conditional | If warranty | + +### Document Detail + +- File preview (PDF viewer, image gallery) +- Download button +- Warranty status indicator (if warranty) +- Edit / delete actions + +### API Endpoints Used + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| GET | `/api/documents/` | User's documents | +| GET | `/api/documents/:id/` | Document detail | +| POST | `/api/documents/` | Create document | +| PUT | `/api/documents/:id/` | Update document | +| DELETE | `/api/documents/:id/` | Delete document | +| POST | `/api/documents/:id/activate/` | Activate document | +| POST | `/api/documents/:id/deactivate/` | Deactivate document | + +### TanStack Query Hooks + +```typescript +useDocuments() // GET /documents/ +useDocument(id) // GET /documents/:id/ +useCreateDocument() // POST /documents/ +useUpdateDocument(id) // PUT /documents/:id/ +useDeleteDocument(id) // DELETE /documents/:id/ +``` + +--- + +## Shared Patterns + +### Form Pattern + +Every CRUD form follows the same structure: + +```typescript +// src/components/forms/TaskForm.tsx +const taskSchema = z.object({ + title: z.string().min(1, 'Title is required'), + residenceId: z.number().min(1, 'Residence is required'), + categoryId: z.number().optional(), + // ... +}); + +type TaskFormData = z.infer; + +export function TaskForm({ task, onSubmit }: Props) { + const form = useForm({ + resolver: zodResolver(taskSchema), + defaultValues: task ? mapTaskToFormData(task) : defaults, + }); + + return ( +
+ + {/* Fields */} +
+ + ); +} +``` + +### Mutation Pattern + +```typescript +export function useCreateTask() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: CreateTaskRequest) => api.tasks.createTask(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['tasks'] }); + queryClient.invalidateQueries({ queryKey: ['residences'] }); // Summary counts change + }, + }); +} +``` + +### Loading / Error / Empty States + +Every list and detail page handles three states: +1. **Loading**: Skeleton loader (shadcn Skeleton component) +2. **Error**: Error banner with retry button +3. **Empty**: Empty state illustration with CTA to create first item + +## Deliverables + +At the end of Phase 2, you should have: +1. Full CRUD for all 4 domains +2. Kanban board with drag-and-drop +3. Task completion with photo upload +4. Template search and autocomplete +5. All forms validated with Zod +6. Proper cache invalidation after mutations diff --git a/docs/03-advanced-features.md b/docs/03-advanced-features.md new file mode 100644 index 0000000..25d2784 --- /dev/null +++ b/docs/03-advanced-features.md @@ -0,0 +1,309 @@ +# Phase 3 — Advanced Features + +Build sharing, subscriptions, notifications, profile, onboarding, and summary metrics. + +## Checklist + +- [ ] Residence sharing: generate/display share code, join residence, manage users, .casera file export/import +- [ ] Contractor sharing: .casera file export/import +- [ ] Subscription: status display, feature comparison, upgrade prompt, usage tracking +- [ ] Notification preferences: toggle + time picker per notification type +- [ ] Profile: edit name/email, change password, delete account +- [ ] Onboarding: multi-step wizard (fresh vs join paths) +- [ ] Summary metrics: dashboard cards with Recharts +- [ ] Task report: PDF generation + download + +--- + +## 1. Residence Sharing + +### Screens + +| Route | Screen | Description | +|-------|--------|-------------| +| `/app/residences/[id]/share` | Share Residence | Generate/display share code, manage users | +| `/app/residences/join` | Join Residence | Enter share code or import .casera file | + +### Share Code Flow + +``` +Owner opens Share screen + → If no active share code: "Generate Share Code" button + → POST /api/residences/:id/share/ → returns { code: "ABC123", expires_at: "..." } + → Display code with copy button + expiry countdown + → "Revoke" button to disable code + +Invitee opens Join screen + → Enter share code manually, OR + → Upload .casera file (drag-and-drop zone) + → POST /api/residences/join/ { code: "ABC123" } + → On success: redirect to residence detail +``` + +### .casera File Handling + +**Export** (owner): +- Button on residence detail: "Export as .casera" +- Generates JSON file with `{ type: "residence", code: "ABC123", ... }` +- Browser downloads the file + +**Import** (invitee): +- Drag-and-drop zone on join page +- Or file picker button +- Read JSON, extract code, auto-submit join request + +### Manage Users + +| Action | Endpoint | Description | +|--------|----------|-------------| +| List users | `GET /api/residences/:id/users/` | Show all users with roles | +| Remove user | `DELETE /api/residences/:id/users/:userId/` | Owner removes a member | + +### API Endpoints + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| POST | `/api/residences/:id/share/` | Generate share code | +| DELETE | `/api/residences/:id/share/` | Revoke share code | +| POST | `/api/residences/join/` | Join with share code | +| GET | `/api/residences/:id/users/` | List users | +| DELETE | `/api/residences/:id/users/:userId/` | Remove user | + +--- + +## 2. Contractor Sharing + +### .casera File Export/Import + +**Export**: +- Button on contractor detail: "Share Contractor" +- Generates JSON file with `{ type: "contractor", name: "...", phone: "...", ... }` +- Browser downloads the file + +**Import**: +- Button on contractor list: "Import Contractor" +- File picker or drag-and-drop for .casera file +- Read JSON, show confirmation dialog, create contractor +- `POST /api/contractors/import/` + +--- + +## 3. Subscription + +### Screens + +| Route | Screen | Description | +|-------|--------|-------------| +| `/app/settings/subscription` | Subscription Status | Current plan, usage, upgrade CTA | + +### Status Display + +``` +┌─────────────────────────────────────────┐ +│ Current Plan: Free │ +│ Residences: 1/1 Tasks: 5/5 │ +│ │ +│ ┌─────────────────────────────────┐ │ +│ │ Upgrade to Premium │ │ +│ │ • Unlimited residences │ │ +│ │ • Unlimited tasks │ │ +│ │ • Document storage │ │ +│ │ • Priority support │ │ +│ │ [Upgrade on App Store] │ │ +│ └─────────────────────────────────┘ │ +└─────────────────────────────────────────┘ +``` + +### Feature Gating + +Check tier limits before allowing creation: +- If at limit → show upgrade prompt dialog instead of create form +- Limits come from `GET /api/subscription/status/` response + +### Upgrade Path (Phase 1) + +Web users are directed to the mobile app for purchases: +- "Download Casera on the App Store to upgrade" +- Link to App Store listing + +### Upgrade Path (Future — Phase 2) + +Add Stripe Checkout for web-only subscription purchases. Requires backend work to support Stripe webhooks alongside StoreKit/Play Store. + +### API Endpoints + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| GET | `/api/subscription/status/` | Current subscription + limits | +| GET | `/api/upgrade-triggers/` | Feature comparison data | + +--- + +## 4. Notification Preferences + +### Screen + +| Route | Screen | Description | +|-------|--------|-------------| +| `/app/settings/notifications` | Notification Preferences | Toggle + time picker per type | + +### Preference Types + +| Preference | Description | Controls | +|------------|-------------|----------| +| Task Reminders | Reminders for upcoming due dates | Toggle on/off + time of day | +| Overdue Alerts | Alerts when tasks become overdue | Toggle on/off | +| Completion Confirmations | Notifications when tasks are completed | Toggle on/off | +| Residence Updates | When someone else modifies your residence | Toggle on/off | + +### API Endpoints + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| GET | `/api/notifications/preferences/` | Get all preferences | +| PUT | `/api/notifications/preferences/:id/` | Update a preference | +| GET | `/api/notifications/` | List notifications (in-app inbox) | +| POST | `/api/notifications/:id/read/` | Mark notification as read | + +### In-App Notification Inbox + +- Bell icon in top bar with unread count badge +- Dropdown panel showing recent notifications +- Click to navigate to related item +- "Mark all as read" action + +--- + +## 5. Profile + +### Screen + +| Route | Screen | Description | +|-------|--------|-------------| +| `/app/settings/profile` | Profile Settings | Edit name, email, password, delete account | + +### Sections + +**Personal Info:** +- First name, Last name, Email +- `PUT /api/auth/profile/` + +**Change Password:** +- Current password, New password, Confirm new password +- `POST /api/auth/change-password/` + +**Danger Zone:** +- Delete account button with confirmation dialog +- `DELETE /api/auth/account/` + +**Theme:** +- Theme picker (11 options, preview swatches) +- Dark/light mode toggle +- Persisted to localStorage + +--- + +## 6. Onboarding + +### Screen + +| Route | Screen | Description | +|-------|--------|-------------| +| `/onboarding` | Onboarding Wizard | Multi-step flow after first registration | + +### Flow + +``` +Step 1: Welcome + → "Welcome to Casera! Let's set up your first property." + +Step 2: Choose Path + → "Create a new residence" OR "Join an existing residence" + + Path A: Create Residence + Step 3a: Residence form (name, address, type) + Step 4a: "Add your first task?" (optional quick-add) + Step 5a: Done → redirect to residence detail + + Path B: Join Residence + Step 3b: Enter share code + Step 4b: Success → redirect to residence detail +``` + +### State Management + +Use Zustand store to track onboarding progress: +```typescript +interface OnboardingState { + currentStep: number; + path: 'create' | 'join' | null; + residenceData: Partial; + isComplete: boolean; + nextStep: () => void; + prevStep: () => void; + setPath: (path: 'create' | 'join') => void; + complete: () => void; +} +``` + +After completion, set a flag so onboarding isn't shown again: +- `POST /api/auth/profile/` with `onboarding_complete: true` +- Or store in localStorage as fallback + +--- + +## 7. Summary Metrics + +### Dashboard (Home Page) + +``` +┌──────────────────────────────────────────────────────┐ +│ Welcome back, Trey │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐│ +│ │ 3 │ │ 2 │ │ 12 │ │ 5 ││ +│ │ Overdue │ │ Due Today│ │ Active │ │ Done ││ +│ │ 🔴 │ │ 🟠 │ │ 🔵 │ │ ✅ ││ +│ └──────────┘ └──────────┘ └──────────┘ └─────────┘│ +│ │ +│ ┌─────────────────────────────────────────────────┐│ +│ │ Task Completion Trend (last 30 days) ││ +│ │ [Area chart via Recharts] ││ +│ └─────────────────────────────────────────────────┘│ +│ │ +│ Recent Activity │ +│ • Task "Fix leak" completed 2h ago │ +│ • New contractor "Bob's Plumbing" added yesterday │ +│ • Residence "Main House" updated 3d ago │ +│ │ +└──────────────────────────────────────────────────────┘ +``` + +Data sources: +- Task counts from `GET /api/tasks/` response (column counts) +- Summary calculated client-side from kanban data (same as mobile) +- Charts via Recharts library + +--- + +## 8. Task Reports + +### Feature + +- Button on residence detail: "Download Task Report" +- `GET /api/residences/:id/report/` → returns PDF +- Browser triggers download + +--- + +## Deliverables + +At the end of Phase 3, you should have: +1. Residence sharing with code generation, join flow, and user management +2. Contractor sharing via .casera files +3. Subscription status with tier limits and upgrade prompts +4. Notification preferences with toggles +5. Profile editing (name, email, password, delete account) +6. Onboarding wizard for new users +7. Home dashboard with summary metrics and charts +8. Task report PDF download diff --git a/docs/04-demo-mode.md b/docs/04-demo-mode.md new file mode 100644 index 0000000..bd9f45a --- /dev/null +++ b/docs/04-demo-mode.md @@ -0,0 +1,360 @@ +# 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 diff --git a/docs/05-polish-deploy.md b/docs/05-polish-deploy.md new file mode 100644 index 0000000..d9eb13e --- /dev/null +++ b/docs/05-polish-deploy.md @@ -0,0 +1,399 @@ +# Phase 5 — Polish & Deploy + +Responsive design, error handling, deployment, testing, and performance optimization. + +## Checklist + +- [ ] Responsive design (mobile-first, works on phone browsers too) +- [ ] Loading states, empty states, error handling for every screen +- [ ] Dockerfile + Dokku deployment config +- [ ] E2E tests (Playwright) for critical paths +- [ ] Performance optimization (code splitting, image optimization) +- [ ] SEO + Open Graph meta tags for marketing/demo pages +- [ ] Accessibility audit (keyboard navigation, screen readers, ARIA labels) +- [ ] Analytics integration (PostHog) + +--- + +## 1. Responsive Design + +### Breakpoint Strategy + +| Breakpoint | Target | Layout | +|------------|--------|--------| +| `<640px` | Mobile phones | Bottom tab bar, stacked cards, full-width forms | +| `640-1023px` | Tablets | Collapsed sidebar (icons only), 2-column grids | +| `≥1024px` | Desktop | Full sidebar, 3-column grids, side panels | + +### Mobile Adaptations + +- **Navigation**: Bottom tab bar replaces sidebar (matches iOS app experience) +- **Kanban board**: Horizontal scroll with swipeable columns (single column visible at a time on small screens) +- **Cards**: Full-width stacked layout +- **Forms**: Single column, full-width inputs +- **Tables**: Convert to card-based layout on mobile +- **Dialogs**: Full-screen on mobile, centered modal on desktop + +### Implementation + +Use Tailwind responsive prefixes consistently: + +```tsx +// Grid that adapts from 1 → 2 → 3 columns +
+ {residences.map(r => )} +
+ +// Sidebar visibility +