e2172c20f2
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>
782 lines
26 KiB
Markdown
782 lines
26 KiB
Markdown
# Phase 3 — Advanced Features Implementation Plan
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**Goal:** Build sharing, subscriptions, notifications, profile settings, onboarding wizard, summary metrics with charts, and task report PDF download.
|
|
|
|
**Architecture:** A shared foundation layer (hooks, settings hub, notification bell) is built first, then 5 domain agents run concurrently on independent feature areas. Each agent produces pages and components that compile independently.
|
|
|
|
**Tech Stack:** Next.js 16 (App Router), TanStack Query v5, React Hook Form + Zod 4, shadcn/ui + Radix, Tailwind CSS 4, Recharts (new), Zustand, Lucide icons.
|
|
|
|
---
|
|
|
|
## Execution Strategy: 7 Tasks, 5 Run in Parallel
|
|
|
|
```
|
|
Task 1: Shared Foundation (sequential — all features depend on this)
|
|
↓
|
|
Task 2: Sharing (residence + contractor) ─┐
|
|
Task 3: Profile Settings ─┤
|
|
Task 4: Notifications + Subscription ─┤ ← 5 parallel agents
|
|
Task 5: Onboarding Wizard ─┤
|
|
Task 6: Dashboard + Task Reports ─┘
|
|
↓
|
|
Task 7: Integration + Verification (sequential — cross-feature wiring)
|
|
```
|
|
|
|
**Verification gate:** Each task ends with `npm run build`. No task is complete until it compiles clean.
|
|
|
|
---
|
|
|
|
## Task 1: Shared Foundation
|
|
|
|
**Files:**
|
|
- Create: `src/lib/hooks/use-notifications.ts`
|
|
- Create: `src/lib/hooks/use-subscription.ts`
|
|
- Create: `src/components/notifications/notification-bell.tsx`
|
|
- Create: `src/app/app/settings/layout.tsx`
|
|
- Modify: `src/app/app/settings/page.tsx` (overwrite stub → settings hub)
|
|
- Modify: `src/components/layout/top-bar.tsx` (add notification bell)
|
|
- Modify: `src/lib/hooks/index.ts` (add new hook exports)
|
|
|
|
### Step 1: Install Recharts
|
|
|
|
```bash
|
|
cd /Users/treyt/Desktop/code/HoneyDue/honeyDueAPI-Web
|
|
npm install recharts
|
|
```
|
|
|
|
### Step 2: Create notification hooks
|
|
|
|
```typescript
|
|
// src/lib/hooks/use-notifications.ts
|
|
"use client";
|
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import * as notificationsApi from '@/lib/api/notifications';
|
|
import type { UpdatePreferencesRequest } from '@/lib/api/notifications';
|
|
|
|
export function useNotifications(limit?: number) {
|
|
return useQuery({
|
|
queryKey: ['notifications', limit],
|
|
queryFn: () => notificationsApi.listNotifications(limit),
|
|
});
|
|
}
|
|
|
|
export function useUnreadCount() {
|
|
return useQuery({
|
|
queryKey: ['notifications', 'unread-count'],
|
|
queryFn: () => notificationsApi.getUnreadCount(),
|
|
refetchInterval: 60_000, // Poll every minute
|
|
});
|
|
}
|
|
|
|
export function useNotificationPreferences() {
|
|
return useQuery({
|
|
queryKey: ['notifications', 'preferences'],
|
|
queryFn: () => notificationsApi.getPreferences(),
|
|
});
|
|
}
|
|
|
|
export function useUpdatePreferences() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (data: UpdatePreferencesRequest) => notificationsApi.updatePreferences(data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['notifications', 'preferences'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useMarkAsRead() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (id: number) => notificationsApi.markAsRead(id),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['notifications'] });
|
|
queryClient.invalidateQueries({ queryKey: ['notifications', 'unread-count'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useMarkAllAsRead() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: () => notificationsApi.markAllAsRead(),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['notifications'] });
|
|
queryClient.invalidateQueries({ queryKey: ['notifications', 'unread-count'] });
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
### Step 3: Create subscription hooks
|
|
|
|
```typescript
|
|
// src/lib/hooks/use-subscription.ts
|
|
"use client";
|
|
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import * as subscriptionApi from '@/lib/api/subscription';
|
|
|
|
export function useSubscriptionStatus() {
|
|
return useQuery({
|
|
queryKey: ['subscription', 'status'],
|
|
queryFn: () => subscriptionApi.getSubscriptionStatus(),
|
|
});
|
|
}
|
|
|
|
export function useFeatureBenefits() {
|
|
return useQuery({
|
|
queryKey: ['subscription', 'features'],
|
|
queryFn: () => subscriptionApi.getFeatureBenefits(),
|
|
staleTime: Infinity,
|
|
});
|
|
}
|
|
|
|
export function useUpgradeTriggers() {
|
|
return useQuery({
|
|
queryKey: ['subscription', 'upgrade-triggers'],
|
|
queryFn: () => subscriptionApi.getAllUpgradeTriggers(),
|
|
staleTime: Infinity,
|
|
});
|
|
}
|
|
```
|
|
|
|
### Step 4: Create NotificationBell component
|
|
|
|
```tsx
|
|
// src/components/notifications/notification-bell.tsx
|
|
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Bell } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { useNotifications, useUnreadCount, useMarkAsRead, useMarkAllAsRead } from "@/lib/hooks/use-notifications";
|
|
|
|
export function NotificationBell() {
|
|
const { data: unreadData } = useUnreadCount();
|
|
const { data: notifData } = useNotifications(10);
|
|
const markAsRead = useMarkAsRead();
|
|
const markAllAsRead = useMarkAllAsRead();
|
|
const unreadCount = unreadData?.unread_count ?? 0;
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="relative">
|
|
<Bell className="size-5" />
|
|
{unreadCount > 0 && (
|
|
<span className="absolute -top-1 -right-1 flex size-5 items-center justify-center rounded-full bg-destructive text-[10px] font-bold text-destructive-foreground">
|
|
{unreadCount > 9 ? "9+" : unreadCount}
|
|
</span>
|
|
)}
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-80 max-h-96 overflow-y-auto">
|
|
<div className="flex items-center justify-between px-3 py-2">
|
|
<p className="text-sm font-semibold">Notifications</p>
|
|
{unreadCount > 0 && (
|
|
<Button variant="ghost" size="sm" className="text-xs h-auto py-1" onClick={() => markAllAsRead.mutate()}>
|
|
Mark all read
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<DropdownMenuSeparator />
|
|
{(!notifData || notifData.results.length === 0) ? (
|
|
<div className="px-3 py-6 text-center text-sm text-muted-foreground">No notifications</div>
|
|
) : (
|
|
notifData.results.map((n) => (
|
|
<DropdownMenuItem key={n.id} className="flex-col items-start gap-1 py-2"
|
|
onClick={() => { if (!n.is_read) markAsRead.mutate(n.id); }}>
|
|
<p className={`text-sm ${n.is_read ? "text-muted-foreground" : "font-medium"}`}>{n.title}</p>
|
|
<p className="text-xs text-muted-foreground">{n.body}</p>
|
|
<p className="text-xs text-muted-foreground">{new Date(n.created_at).toLocaleDateString()}</p>
|
|
</DropdownMenuItem>
|
|
))
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Step 5: Modify TopBar to include notification bell
|
|
|
|
In `src/components/layout/top-bar.tsx`, add `<NotificationBell />` before the profile dropdown. Import from `@/components/notifications/notification-bell`.
|
|
|
|
Add a `<div className="flex items-center gap-2">` wrapper around the bell and avatar dropdown.
|
|
|
|
### Step 6: Create settings layout with sidebar navigation
|
|
|
|
```tsx
|
|
// src/app/app/settings/layout.tsx
|
|
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { cn } from "@/lib/utils";
|
|
import { User, Bell, CreditCard } from "lucide-react";
|
|
|
|
const settingsNav = [
|
|
{ label: "Profile", href: "/app/settings/profile", icon: User },
|
|
{ label: "Notifications", href: "/app/settings/notifications", icon: Bell },
|
|
{ label: "Subscription", href: "/app/settings/subscription", icon: CreditCard },
|
|
];
|
|
|
|
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
|
const pathname = usePathname();
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<h1 className="text-2xl font-bold tracking-tight">Settings</h1>
|
|
<div className="flex flex-col sm:flex-row gap-6">
|
|
<nav className="flex sm:flex-col gap-1 sm:w-48 shrink-0">
|
|
{settingsNav.map((item) => (
|
|
<Link key={item.href} href={item.href}
|
|
className={cn(
|
|
"flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors",
|
|
"hover:bg-accent hover:text-accent-foreground",
|
|
pathname === item.href ? "bg-accent text-accent-foreground" : "text-muted-foreground"
|
|
)}>
|
|
<item.icon className="size-4" />
|
|
{item.label}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
<div className="flex-1 min-w-0">{children}</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Step 7: Overwrite settings page stub → redirect to profile
|
|
|
|
```tsx
|
|
// src/app/app/settings/page.tsx
|
|
import { redirect } from "next/navigation";
|
|
|
|
export default function SettingsPage() {
|
|
redirect("/app/settings/profile");
|
|
}
|
|
```
|
|
|
|
### Step 8: Update hooks barrel export
|
|
|
|
Add to `src/lib/hooks/index.ts`:
|
|
```typescript
|
|
export * from './use-notifications';
|
|
export * from './use-subscription';
|
|
```
|
|
|
|
### Step 9: Verify build
|
|
|
|
```bash
|
|
cd /Users/treyt/Desktop/code/HoneyDue/honeyDueAPI-Web && npm run build
|
|
```
|
|
|
|
### Step 10: Commit
|
|
|
|
```bash
|
|
git add -A && git commit -m "feat: add Phase 3 foundation — notification hooks, subscription hooks, settings layout, notification bell"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 2: Sharing (Residence + Contractor)
|
|
|
|
**Depends on:** Task 1
|
|
|
|
**Files:**
|
|
- Create: `src/app/app/residences/[id]/share/page.tsx`
|
|
- Create: `src/app/app/residences/join/page.tsx`
|
|
- Create: `src/components/sharing/share-code-display.tsx`
|
|
- Create: `src/components/sharing/user-management.tsx`
|
|
- Create: `src/components/sharing/honeydue-file-handler.tsx`
|
|
- Create: `src/lib/hooks/use-sharing.ts`
|
|
- Modify: `src/app/app/residences/[id]/page.tsx` (add Share button)
|
|
- Modify: `src/app/app/contractors/[id]/page.tsx` (add Share/Export button)
|
|
- Modify: `src/app/app/contractors/page.tsx` (add Import button)
|
|
|
|
### Residence Sharing Components
|
|
|
|
**share-code-display.tsx** — Displays share code with copy button and expiry countdown:
|
|
- If no active code: "Generate Share Code" button → calls `POST /residences/:id/generate-share-code/`
|
|
- If active code: displays code in large monospace text, copy-to-clipboard button, expiry timer
|
|
- Uses existing `residencesApi.generateShareCode()` and `residencesApi.getShareCode()`
|
|
|
|
**user-management.tsx** — Lists residence users with remove action:
|
|
- Uses `residencesApi.getResidenceUsers(id)` to fetch user list
|
|
- Each user row: name, email, role badge (Owner/Member)
|
|
- Owner can click "Remove" → ConfirmDialog → `residencesApi.removeResidenceUser(residenceId, userId)`
|
|
|
|
### .honeydue File Handler
|
|
|
|
**honeydue-file-handler.tsx** — Reusable component for both residence and contractor sharing:
|
|
- **Export mode**: Takes data object, generates JSON `{ type: "residence"|"contractor", ... }`, triggers browser download as `.honeydue` file
|
|
- **Import mode**: FileUpload drop zone accepting `.honeydue` files, reads JSON, validates type, calls callback with parsed data
|
|
- Uses `URL.createObjectURL` + anchor click for download
|
|
- Uses `FileReader.readAsText` for import
|
|
|
|
### Pages
|
|
|
|
**Residence Share page** (`/app/residences/[id]/share`):
|
|
- ShareCodeDisplay for generating/displaying share codes
|
|
- .honeydue export button → downloads file with share code embedded
|
|
- UserManagement table
|
|
- Only accessible to residence owner
|
|
|
|
**Residence Join page** (`/app/residences/join`):
|
|
- Text input for manual code entry
|
|
- .honeydue file import drop zone
|
|
- On submit: calls `residencesApi.joinWithCode()` → redirect to residence detail
|
|
|
|
### Contractor Sharing
|
|
|
|
On contractor detail page, add "Share" button → generates .honeydue file with contractor data for download.
|
|
On contractor list page, add "Import" button → opens import dialog → reads .honeydue file → creates contractor via API.
|
|
|
|
### Query hooks
|
|
|
|
```typescript
|
|
// src/lib/hooks/use-sharing.ts
|
|
"use client";
|
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import * as residencesApi from '@/lib/api/residences';
|
|
|
|
export function useShareCode(residenceId: number) {
|
|
return useQuery({
|
|
queryKey: ['residences', residenceId, 'share-code'],
|
|
queryFn: () => residencesApi.getShareCode(residenceId),
|
|
enabled: !!residenceId,
|
|
});
|
|
}
|
|
|
|
export function useGenerateShareCode(residenceId: number) {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: () => residencesApi.generateShareCode(residenceId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['residences', residenceId, 'share-code'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useResidenceUsers(residenceId: number) {
|
|
return useQuery({
|
|
queryKey: ['residences', residenceId, 'users'],
|
|
queryFn: () => residencesApi.getResidenceUsers(residenceId),
|
|
enabled: !!residenceId,
|
|
});
|
|
}
|
|
|
|
export function useRemoveResidenceUser(residenceId: number) {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (userId: number) => residencesApi.removeResidenceUser(residenceId, userId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['residences', residenceId, 'users'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useJoinResidence() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (code: string) => residencesApi.joinWithCode({ code }),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['residences'] });
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
### Verify build
|
|
|
|
```bash
|
|
cd /Users/treyt/Desktop/code/HoneyDue/honeyDueAPI-Web && npm run build
|
|
```
|
|
|
|
### Commit
|
|
|
|
```bash
|
|
git add -A && git commit -m "feat: add residence sharing (share code, join, user management) and contractor .honeydue export/import"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 3: Profile Settings
|
|
|
|
**Depends on:** Task 1
|
|
|
|
**Files:**
|
|
- Create: `src/app/app/settings/profile/page.tsx`
|
|
- Create: `src/components/settings/profile-form.tsx`
|
|
- Create: `src/components/settings/change-password-form.tsx`
|
|
- Create: `src/components/settings/delete-account-section.tsx`
|
|
- Create: `src/components/settings/theme-picker.tsx`
|
|
- Modify: `src/lib/api/auth.ts` (add changePassword and deleteAccount functions if missing)
|
|
|
|
### API additions needed in auth.ts
|
|
|
|
Check if these exist, add if missing:
|
|
```typescript
|
|
export async function changePassword(data: { current_password: string; new_password: string }): Promise<MessageResponse> {
|
|
const res = await fetch('/api/proxy/auth/change-password/', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-Timezone': timezone() },
|
|
body: JSON.stringify(data),
|
|
});
|
|
return handleResponse<MessageResponse>(res);
|
|
}
|
|
|
|
export async function deleteAccount(): Promise<MessageResponse> {
|
|
const res = await fetch('/api/proxy/auth/delete-account/', {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json', 'X-Timezone': timezone() },
|
|
});
|
|
return handleResponse<MessageResponse>(res);
|
|
}
|
|
```
|
|
|
|
### Components
|
|
|
|
**profile-form.tsx** — Edit first name, last name, email:
|
|
- Zod schema: first_name (required), last_name (required), email (required, valid email)
|
|
- Pre-fills from current user (from auth store)
|
|
- On submit: calls `authApi.updateProfile()` → updates auth store user
|
|
- Success toast / inline success message
|
|
|
|
**change-password-form.tsx** — Change password:
|
|
- Zod schema: current_password (required, min 8), new_password (required, min 8), confirm_password (must match new_password)
|
|
- On submit: calls `authApi.changePassword()` → clear form on success
|
|
|
|
**delete-account-section.tsx** — Danger zone:
|
|
- Red bordered card with warning text
|
|
- "Delete Account" button → ConfirmDialog with "Type DELETE to confirm" pattern
|
|
- On confirm: calls `authApi.deleteAccount()` → logout → redirect to login
|
|
|
|
**theme-picker.tsx** — Visual theme selector:
|
|
- Grid of 11 theme swatches (colored circles/squares from theme-config)
|
|
- Click to select → applies theme via `useTheme()` hook from existing theme store
|
|
- Dark/light toggle using existing theme store
|
|
- Read theme config from `src/lib/themes/theme-config.ts`
|
|
|
|
### Profile page
|
|
|
|
```tsx
|
|
// src/app/app/settings/profile/page.tsx
|
|
"use client";
|
|
|
|
// 4 sections stacked vertically:
|
|
// 1. ProfileForm (personal info)
|
|
// 2. ChangePasswordForm
|
|
// 3. ThemePicker
|
|
// 4. DeleteAccountSection (danger zone at bottom)
|
|
```
|
|
|
|
### Verify build
|
|
|
|
```bash
|
|
cd /Users/treyt/Desktop/code/HoneyDue/honeyDueAPI-Web && npm run build
|
|
```
|
|
|
|
### Commit
|
|
|
|
```bash
|
|
git add -A && git commit -m "feat: add profile settings — edit info, change password, theme picker, delete account"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 4: Notifications + Subscription
|
|
|
|
**Depends on:** Task 1
|
|
|
|
**Files:**
|
|
- Create: `src/app/app/settings/notifications/page.tsx`
|
|
- Create: `src/app/app/settings/subscription/page.tsx`
|
|
- Create: `src/components/settings/notification-preferences.tsx`
|
|
- Create: `src/components/settings/subscription-status.tsx`
|
|
- Create: `src/components/settings/feature-comparison.tsx`
|
|
- Create: `src/components/shared/upgrade-prompt.tsx`
|
|
|
|
### Notification Preferences
|
|
|
|
**notification-preferences.tsx** — Toggle switches per notification type:
|
|
- Uses `useNotificationPreferences()` to load current state
|
|
- Each preference row: label, description, toggle switch (shadcn Switch component — install via shadcn if not present)
|
|
- On toggle: calls `useUpdatePreferences()` with changed field
|
|
- Preference types: Task Reminders, Task Completions, Residence Updates, Share Notifications, Marketing
|
|
|
|
**Notifications page** (`/app/settings/notifications`):
|
|
- NotificationPreferences component
|
|
- Section header "Notification Preferences"
|
|
- Each toggle saves immediately (optimistic update)
|
|
|
|
### Subscription
|
|
|
|
**subscription-status.tsx** — Current plan display:
|
|
- Uses `useSubscriptionStatus()` to load tier, limits, usage
|
|
- Shows current tier name (Free/Premium)
|
|
- Progress bars for usage: residences (X/max), tasks per residence (X/max), contractors (X/max), documents (X/max)
|
|
- If free tier: show upgrade CTA section
|
|
|
|
**feature-comparison.tsx** — Free vs Premium comparison:
|
|
- Uses `useFeatureBenefits()` to load feature list
|
|
- Two-column comparison table: Free vs Premium
|
|
- Check/cross icons per feature
|
|
- "Upgrade on App Store" button linking to App Store
|
|
|
|
**upgrade-prompt.tsx** — Reusable upgrade prompt dialog:
|
|
- Shown when user hits a tier limit (e.g., max residences reached)
|
|
- Takes `feature` prop (what they're trying to do)
|
|
- Shows limit info + upgrade CTA
|
|
- Uses shadcn Dialog
|
|
- Can be imported by any domain page that needs feature gating
|
|
|
|
**Subscription page** (`/app/settings/subscription`):
|
|
- SubscriptionStatus card at top
|
|
- FeatureComparison below
|
|
- If premium: show expiry date, management info
|
|
|
|
### Install Switch component
|
|
|
|
```bash
|
|
npx shadcn@latest add switch
|
|
```
|
|
|
|
### Verify build
|
|
|
|
```bash
|
|
cd /Users/treyt/Desktop/code/HoneyDue/honeyDueAPI-Web && npm run build
|
|
```
|
|
|
|
### Commit
|
|
|
|
```bash
|
|
git add -A && git commit -m "feat: add notification preferences and subscription status with feature comparison"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 5: Onboarding Wizard
|
|
|
|
**Depends on:** Task 1
|
|
|
|
**Files:**
|
|
- Create: `src/app/onboarding/page.tsx`
|
|
- Create: `src/app/onboarding/layout.tsx`
|
|
- Create: `src/components/onboarding/welcome-step.tsx`
|
|
- Create: `src/components/onboarding/choose-path-step.tsx`
|
|
- Create: `src/components/onboarding/create-residence-step.tsx`
|
|
- Create: `src/components/onboarding/first-task-step.tsx`
|
|
- Create: `src/components/onboarding/join-residence-step.tsx`
|
|
- Create: `src/components/onboarding/complete-step.tsx`
|
|
- Create: `src/stores/onboarding.ts`
|
|
|
|
### Onboarding Zustand Store
|
|
|
|
```typescript
|
|
// src/stores/onboarding.ts
|
|
import { create } from "zustand";
|
|
|
|
interface OnboardingState {
|
|
currentStep: number;
|
|
path: "create" | "join" | null;
|
|
residenceId: number | null;
|
|
isComplete: boolean;
|
|
nextStep: () => void;
|
|
prevStep: () => void;
|
|
setPath: (path: "create" | "join") => void;
|
|
setResidenceId: (id: number) => void;
|
|
complete: () => void;
|
|
reset: () => void;
|
|
}
|
|
|
|
export const useOnboardingStore = create<OnboardingState>()((set) => ({
|
|
currentStep: 0,
|
|
path: null,
|
|
residenceId: null,
|
|
isComplete: false,
|
|
nextStep: () => set((s) => ({ currentStep: s.currentStep + 1 })),
|
|
prevStep: () => set((s) => ({ currentStep: Math.max(0, s.currentStep - 1) })),
|
|
setPath: (path) => set({ path, currentStep: 2 }),
|
|
setResidenceId: (id) => set({ residenceId: id }),
|
|
complete: () => set({ isComplete: true }),
|
|
reset: () => set({ currentStep: 0, path: null, residenceId: null, isComplete: false }),
|
|
}));
|
|
```
|
|
|
|
### Onboarding Layout
|
|
|
|
- Clean, centered layout (no sidebar or app shell)
|
|
- Progress indicator (step dots or progress bar)
|
|
- honeyDue logo at top
|
|
|
|
### Steps
|
|
|
|
**Step 0: Welcome** — "Welcome to honeyDue!" message, illustration, "Get Started" button
|
|
|
|
**Step 1: Choose Path** — Two cards: "Create a new residence" (House icon) and "Join an existing residence" (Users icon). Clicking sets path and advances.
|
|
|
|
**Path A — Create:**
|
|
- **Step 2a: Create Residence** — Simplified ResidenceForm (name, address, type only). On submit: creates residence via `useCreateResidence()`, stores ID, advances.
|
|
- **Step 3a: First Task (optional)** — "Add your first task?" Quick task form (title + due date only) or "Skip" button. On submit: creates task, advances. On skip: advances.
|
|
- **Step 4a: Complete** — "You're all set!" Redirect to `/app/residences/${id}`
|
|
|
|
**Path B — Join:**
|
|
- **Step 2b: Join Residence** — Code input (6-char) or .honeydue file import. On submit: joins via API, advances.
|
|
- **Step 3b: Complete** — "Welcome to the residence!" Redirect to residence detail.
|
|
|
|
### Onboarding trigger
|
|
|
|
After login/register, check if user has 0 residences → redirect to `/onboarding`.
|
|
This is done via middleware or the auth store's login flow.
|
|
For now, just mark `localStorage.setItem('onboarding_complete', 'true')` after completion, and check in the app layout.
|
|
|
|
### Verify build
|
|
|
|
```bash
|
|
cd /Users/treyt/Desktop/code/HoneyDue/honeyDueAPI-Web && npm run build
|
|
```
|
|
|
|
### Commit
|
|
|
|
```bash
|
|
git add -A && git commit -m "feat: add onboarding wizard with create/join paths"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 6: Enhanced Dashboard + Task Reports
|
|
|
|
**Depends on:** Task 1
|
|
|
|
**Files:**
|
|
- Modify: `src/app/app/page.tsx` (enhance existing dashboard)
|
|
- Create: `src/components/dashboard/task-completion-chart.tsx`
|
|
- Create: `src/components/dashboard/recent-activity.tsx`
|
|
- Create: `src/components/dashboard/stats-cards.tsx`
|
|
- Modify: `src/app/app/residences/[id]/page.tsx` (add "Download Report" button)
|
|
|
|
### Enhanced Dashboard
|
|
|
|
Replace the current simple dashboard with the spec's design:
|
|
|
|
**stats-cards.tsx** — Top row of stat cards:
|
|
- 4 cards: Overdue (red), Due Today (orange), Active (blue), Completed (green)
|
|
- Data from `useTasks()` kanban response `total_summary` field
|
|
- Each card: count, label, colored icon
|
|
|
|
**task-completion-chart.tsx** — Area chart using Recharts:
|
|
- Uses task completion data (from completions API or derived from kanban data)
|
|
- Shows last 30 days of completions as an area chart
|
|
- `<ResponsiveContainer>` + `<AreaChart>` + `<Area>` from recharts
|
|
- Styled with theme colors via CSS variables
|
|
- If no completion data available, show "No completion data yet" empty state
|
|
|
|
**recent-activity.tsx** — Recent activity feed:
|
|
- Uses `useNotifications(5)` to show last 5 notifications as activity items
|
|
- Each item: icon, title, time ago (relative)
|
|
- "View all" link to settings/notifications
|
|
- If no notifications: "No recent activity"
|
|
|
|
### Dashboard page structure
|
|
|
|
```tsx
|
|
// src/app/app/page.tsx (enhanced)
|
|
// 1. Welcome message with user name (from auth store)
|
|
// 2. StatsCards (4-column grid)
|
|
// 3. TaskCompletionChart (full width card)
|
|
// 4. RecentActivity (list)
|
|
```
|
|
|
|
### Task Report PDF
|
|
|
|
On residence detail page (`src/app/app/residences/[id]/page.tsx`), add a "Download Report" button:
|
|
- Calls `residencesApi.generateTasksReport(residenceId)`
|
|
- The API generates a PDF and emails it (existing behavior)
|
|
- Show success message "Report sent to your email"
|
|
- Alternative: if API returns a PDF URL, trigger browser download
|
|
|
|
### Verify build
|
|
|
|
```bash
|
|
cd /Users/treyt/Desktop/code/HoneyDue/honeyDueAPI-Web && npm run build
|
|
```
|
|
|
|
### Commit
|
|
|
|
```bash
|
|
git add -A && git commit -m "feat: add enhanced dashboard with charts and task report PDF download"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 7: Integration + Verification
|
|
|
|
**Depends on:** Tasks 2-6
|
|
|
|
### Step 1: Full build verification
|
|
|
|
```bash
|
|
cd /Users/treyt/Desktop/code/HoneyDue/honeyDueAPI-Web && npm run build
|
|
```
|
|
|
|
### Step 2: Route verification
|
|
|
|
Confirm all new routes compile:
|
|
- `/app/settings/profile`
|
|
- `/app/settings/notifications`
|
|
- `/app/settings/subscription`
|
|
- `/app/residences/[id]/share`
|
|
- `/app/residences/join`
|
|
- `/onboarding`
|
|
|
|
### Step 3: Cross-feature integration checks
|
|
|
|
- TopBar notification bell renders and shows unread count
|
|
- Settings hub sidebar navigates between profile/notifications/subscription
|
|
- Residence detail has Share and Download Report buttons
|
|
- Contractor detail has Share button, list has Import button
|
|
- Dashboard shows stats, chart, and recent activity
|
|
|
|
### Step 4: Commit
|
|
|
|
```bash
|
|
git add -A && git commit -m "feat: complete Phase 3 — advanced features integration verified"
|
|
```
|
|
|
|
---
|
|
|
|
## Deliverables Checklist
|
|
|
|
At the end of Phase 3, verify:
|
|
|
|
- [ ] **Residence sharing**: generate share code, copy, join with code, manage users (list/remove)
|
|
- [ ] **Contractor sharing**: .honeydue file export from detail, import on list page
|
|
- [ ] **.honeydue files**: download as JSON, import via drag-and-drop
|
|
- [ ] **Subscription**: status display with usage bars, feature comparison table, upgrade CTA
|
|
- [ ] **Feature gating**: upgrade prompt dialog available for tier limit enforcement
|
|
- [ ] **Notification preferences**: toggle switches per notification type, saves immediately
|
|
- [ ] **In-app notifications**: bell icon in TopBar with unread count badge, dropdown with recent notifications
|
|
- [ ] **Profile**: edit name/email form, change password form, delete account with confirmation
|
|
- [ ] **Theme picker**: 11 theme swatches + dark/light toggle in profile settings
|
|
- [ ] **Onboarding**: multi-step wizard with create/join paths, stores completion state
|
|
- [ ] **Dashboard**: enhanced with stats cards, completion trend chart (Recharts), recent activity feed
|
|
- [ ] **Task reports**: "Download Report" button on residence detail, triggers PDF generation
|
|
- [ ] **Settings hub**: sidebar navigation between profile, notifications, subscription
|
|
- [ ] **Build passes**: `npm run build` exits 0
|