13 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-agent-portal-shell | 04 | execute | 4 |
|
|
false |
|
|
Purpose: CLIENT-03 requires the agent to view a client's profile and associated documents. Edit + delete round out the client management feature set. The checkpoint verifies all 4 Phase 3 success criteria in a real browser. Output: Working /portal/clients/[id] page with edit modal, delete confirmation, and documents table; human approval of complete Phase 3 portal.
<execution_context> @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/STATE.md @.planning/phases/03-agent-portal-shell/03-CONTEXT.md @.planning/phases/03-agent-portal-shell/03-03-SUMMARY.mdFrom src/app/portal/_components/DocumentsTable.tsx:
type DocumentRow = {
id: string;
name: string;
clientName: string | null;
status: "Draft" | "Sent" | "Viewed" | "Signed";
sentAt: Date | null;
clientId: string;
};
export function DocumentsTable({ rows, showClientColumn }: Props): JSX.Element
// Pass showClientColumn={false} on the profile page — client is implied
From src/app/portal/_components/ClientModal.tsx:
// Already supports edit mode:
type Props = {
isOpen: boolean;
onClose: () => void;
mode?: "create" | "edit";
clientId?: string;
defaultName?: string;
defaultEmail?: string;
};
export function ClientModal(props: Props): JSX.Element | null
From src/lib/actions/clients.ts:
// updateClient uses bind pattern — in profile page:
// const boundUpdate = updateClient.bind(null, client.id);
export async function updateClient(
id: string,
_prevState: { error?: string } | null,
formData: FormData
): Promise<{ error?: string; success?: boolean }>
export async function deleteClient(id: string): Promise<{ error?: string; success?: boolean }>
Next.js 16 dynamic params (MUST await):
export default async function ClientProfilePage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params; // CRITICAL — sync access throws at build time
}
Drizzle query for client profile:
import { eq, desc, sql } from "drizzle-orm";
// Fetch client
const [client] = await db.select().from(clients).where(eq(clients.id, id));
if (!client) notFound(); // import from next/navigation
// Fetch documents for this client
const docs = await db
.select({
id: documents.id,
name: documents.name,
status: documents.status,
sentAt: documents.sentAt,
clientId: documents.clientId,
clientName: sql<string>`${clients.name}`,
})
.from(documents)
.leftJoin(clients, eq(documents.clientId, clients.id))
.where(eq(documents.clientId, id))
.orderBy(desc(documents.createdAt));
- Overlay + centered dialog box (same overlay pattern as ClientModal:
fixed inset-0 z-50 bg-black/40) - Title, message text, Cancel button + Confirm button
- Confirm button: red/destructive styling (
bg-red-600 text-white hover:bg-red-700) confirmLabeldefaults to "Delete"
src/app/portal/(protected)/clients/[id]/page.tsx:
This page needs both server data AND client modal/dialog state. Use the same pattern as clients/page.tsx: server component fetches data, renders a "use client" inner component with the interactive parts.
Server component (default export):
const { id } = await params— CRITICAL: params is a Promise in Next.js 16- Fetch client by id (use
notFound()if not found) - Fetch documents for this client with LEFT JOIN
- Render
<ClientProfileClient client={client} docs={docs} />
Inline "use client" component (ClientProfileClient) in the same file OR in _components/ClientProfileClient.tsx:
Props: { client: { id: string; name: string; email: string }; docs: DocumentRow[] }
State: const [isEditOpen, setIsEditOpen] = useState(false) and const [isDeleteOpen, setIsDeleteOpen] = useState(false)
Layout (two-section layout per CONTEXT.md decision):
- Header section: white card (
bg-white rounded-xl shadow-sm p-6 mb-6)- Back link: arrow + "Back to Clients" as a Next.js
<Link href="/portal/clients">withtext-[var(--gold)] text-sm - Client name as
<h1>(text-[var(--navy)] text-2xl font-semibold) - Email below the name (gray text)
- Two buttons on the right: "Edit" button (opens edit modal) + "Delete Client" button (opens confirm dialog)
- Edit button: outlined style (
border border-[var(--navy)] text-[var(--navy)] px-4 py-2 rounded-lg text-sm) - Delete button: red outlined style (
border border-red-300 text-red-600 px-4 py-2 rounded-lg text-sm)
- Back link: arrow + "Back to Clients" as a Next.js
- Documents section: white card (
bg-white rounded-xl shadow-sm p-6)<h2>Documents</h2>heading- If
docs.length === 0: "No documents yet." gray text - If
docs.length > 0:<DocumentsTable rows={docs} showClientColumn={false} />
Edit modal: <ClientModal isOpen={isEditOpen} onClose={() => setIsEditOpen(false)} mode="edit" clientId={client.id} defaultName={client.name} defaultEmail={client.email} />
Delete flow:
<ConfirmDialog isOpen={isDeleteOpen} title="Delete client?" message={"Delete " + client.name + "? This will also delete all associated documents. This cannot be undone."} onConfirm={handleDelete} onCancel={() => setIsDeleteOpen(false)} />handleDelete: callawait deleteClient(client.id)thenrouter.push("/portal/clients")— importuseRouterfromnext/navigation
PITFALL: deleteClient is a server action. To call it from a client component, import it and call it directly in an async event handler (Next.js 15+ allows this). The action runs server-side; the client awaits the promise.
PITFALL: await params before any usage of id — synchronous access causes build failure in Next.js 16.
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1
Client profile page renders with client details header, edit/delete buttons, and documents table. ConfirmDialog component exported. TypeScript compiles cleanly.
1. Authentication and routing (Phase 1 preserved)
- Visit http://localhost:3000/portal/dashboard while logged out → should redirect to /agent/login
- Log in with the seeded agent account → should land on /portal/dashboard (not /agent/dashboard)
2. Dashboard (DASH-01 + DASH-02)
- Verify you see a table with 4 documents: "Purchase Agreement - 842 Maple Dr", "Seller Disclosure - 842 Maple Dr", "Buyer Rep Agreement", "Purchase Agreement - 1205 Oak Ave"
- Verify status badges: two "Signed" (green), one "Sent" (blue), one "Draft" (gray)
- Verify Client column shows "Sarah Johnson" and "Mike Torres"
- Select "Signed" in the filter dropdown → URL changes to ?status=Signed → table shows only 2 rows
- Select "All statuses" → table returns to 4 rows
3. Clients list (CLIENT-01 + CLIENT-02)
- Visit http://localhost:3000/portal/clients
- Verify card grid shows 2 client cards: Sarah Johnson and Mike Torres
- Each card shows name, email, document count, and last activity date
- Click "+ Add Client" → modal opens with name + email fields
- Fill in a test client (e.g., "Test User" + "test@example.com") → click "Add Client" → modal closes → new card appears in grid
4. Client profile (CLIENT-03)
- Click on "Sarah Johnson" card → lands on /portal/clients/[sarah's id]
- Verify header shows "Sarah Johnson", her email, Edit and Delete Client buttons
- Verify documents section shows Sarah's 2 documents with status badges
- Click "Edit" → modal opens pre-filled with "Sarah Johnson" and her email → change name → save → header updates
- Click "Delete Client" → confirmation dialog appears → click Cancel → client is NOT deleted
- (Optional) Delete the test client you created in step 3 → confirm → redirected to /portal/clients
5. Navigation
- Verify top nav is visible on all three portal pages
- Click "Dashboard" nav link → navigates to /portal/dashboard
- Click "Clients" nav link → navigates to /portal/clients
- Current page should be highlighted in nav (gold underline or similar)
- Sign out → redirects to /agent/login Type "approved" if all checklist items pass. Describe any issues found and I will fix them before you approve.
<success_criteria>
- Agent can see client profile with name, email, and associated documents (CLIENT-03)
- Agent can edit client name/email via modal — profile updates immediately
- Agent can delete a client via confirmation dialog — redirected to clients list after
- All four Phase 3 success criteria confirmed in real browser by agent
- TypeScript compiles cleanly with zero errors </success_criteria>