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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<T>, 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<T>(
|
||||
path: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
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 <hex>` (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<void>;
|
||||
register: (data: RegisterRequest) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
fetchUser: () => Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
## 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)
|
||||
Reference in New Issue
Block a user