docs(03-agent-portal-shell): create phase 3 plan — 4 plans in 4 waves
This commit is contained in:
199
.planning/phases/03-agent-portal-shell/03-01-PLAN.md
Normal file
199
.planning/phases/03-agent-portal-shell/03-01-PLAN.md
Normal file
@@ -0,0 +1,199 @@
|
||||
---
|
||||
phase: 03-agent-portal-shell
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- teressa-copeland-homes/src/lib/db/schema.ts
|
||||
- teressa-copeland-homes/middleware.ts
|
||||
- teressa-copeland-homes/src/lib/auth.config.ts
|
||||
- teressa-copeland-homes/src/app/agent/(protected)/dashboard/page.tsx
|
||||
- teressa-copeland-homes/drizzle/0001_clients_documents.sql
|
||||
autonomous: true
|
||||
requirements: [CLIENT-01, CLIENT-02, CLIENT-03, DASH-01, DASH-02]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Running `npm run db:generate && npm run db:migrate` produces a migration that creates the `clients` and `documents` tables with no errors"
|
||||
- "Visiting /portal/dashboard while unauthenticated redirects to /agent/login — not a 404 or blank page"
|
||||
- "After login, agent is redirected to /portal/dashboard (not /agent/dashboard)"
|
||||
- "The document_status PostgreSQL enum exists with values Draft, Sent, Viewed, Signed"
|
||||
artifacts:
|
||||
- path: "teressa-copeland-homes/src/lib/db/schema.ts"
|
||||
provides: "clients and documents table definitions + documentStatusEnum"
|
||||
contains: "pgTable(\"clients\""
|
||||
- path: "teressa-copeland-homes/middleware.ts"
|
||||
provides: "Route protection for /portal/:path*"
|
||||
contains: "/portal/:path*"
|
||||
- path: "teressa-copeland-homes/src/lib/auth.config.ts"
|
||||
provides: "Post-login redirect to /portal/dashboard"
|
||||
contains: "/portal/dashboard"
|
||||
key_links:
|
||||
- from: "middleware.ts matcher"
|
||||
to: "/portal/:path* routes"
|
||||
via: "matcher array"
|
||||
pattern: "portal.*path"
|
||||
- from: "auth.config.ts authorized callback"
|
||||
to: "session check for /portal routes"
|
||||
via: "nextUrl.pathname.startsWith('/portal')"
|
||||
pattern: "startsWith.*portal"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Add the `clients` and `documents` stub tables to the Drizzle schema, generate and run the migration, and update the middleware + auth config so all `/portal/` routes are protected and post-login redirect lands on `/portal/dashboard`.
|
||||
|
||||
Purpose: Every subsequent Phase 3 plan depends on these two tables and on the `/portal` route prefix being protected. This plan lays the data and routing foundations.
|
||||
Output: Working Drizzle migration for clients + documents tables; middleware protecting `/portal/*`; post-login redirect updated.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/Users/ccopeland/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/03-agent-portal-shell/03-CONTEXT.md
|
||||
@.planning/phases/03-agent-portal-shell/03-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Existing schema pattern (from src/lib/db/schema.ts) -->
|
||||
<!-- Executor: match this exact style for new tables -->
|
||||
|
||||
Existing users table (for reference pattern):
|
||||
```typescript
|
||||
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
// users table already exists — do NOT redefine it
|
||||
|
||||
// ADD below the existing users table:
|
||||
export const clients = pgTable("clients", {
|
||||
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||
name: text("name").notNull(),
|
||||
email: text("email").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const documentStatusEnum = pgEnum("document_status", [
|
||||
"Draft", "Sent", "Viewed", "Signed"
|
||||
]);
|
||||
|
||||
export const documents = pgTable("documents", {
|
||||
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||
name: text("name").notNull(),
|
||||
clientId: text("client_id").notNull().references(() => clients.id, { onDelete: "cascade" }),
|
||||
status: documentStatusEnum("status").notNull().default("Draft"),
|
||||
sentAt: timestamp("sent_at"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
```
|
||||
|
||||
Existing middleware.ts pattern (matcher array to extend):
|
||||
```typescript
|
||||
export const config = {
|
||||
matcher: ["/agent/:path*"], // ADD "/portal/:path*" here
|
||||
};
|
||||
```
|
||||
|
||||
Existing auth.config.ts authorized callback (redirect to update):
|
||||
```typescript
|
||||
// Change: redirect(new URL("/agent/dashboard", nextUrl))
|
||||
// To: redirect(new URL("/portal/dashboard", nextUrl))
|
||||
// Also add: nextUrl.pathname.startsWith("/portal") to protected routes check
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Extend Drizzle schema with clients and documents tables</name>
|
||||
<files>teressa-copeland-homes/src/lib/db/schema.ts</files>
|
||||
<action>
|
||||
Read the current schema.ts first. Then add, below the existing users table definition:
|
||||
|
||||
1. Import `pgEnum` from `drizzle-orm/pg-core` (add to existing import).
|
||||
2. Export `documentStatusEnum` using `pgEnum("document_status", ["Draft", "Sent", "Viewed", "Signed"])`.
|
||||
3. Export `clients` table: id (text PK with crypto.randomUUID), name (text not null), email (text not null), createdAt (timestamp defaultNow not null), updatedAt (timestamp defaultNow not null).
|
||||
4. Export `documents` table (stub — no PDF content yet): id (text PK with crypto.randomUUID), name (text not null), clientId (text not null, FK to clients.id with onDelete: "cascade"), status (documentStatusEnum column default "Draft" not null), sentAt (timestamp nullable), createdAt (timestamp defaultNow not null).
|
||||
|
||||
CRITICAL: Export the enum BEFORE the documents table (it is referenced by the documents table column). Export both tables so they are importable by other modules.
|
||||
|
||||
Then generate and run the migration:
|
||||
```
|
||||
cd teressa-copeland-homes && npm run db:generate && npm run db:migrate
|
||||
```
|
||||
|
||||
The migration SQL file will be created automatically in the `drizzle/` directory. Do NOT hand-write SQL.
|
||||
|
||||
PITFALL: pgEnum must be exported AND referenced by a table column or drizzle-kit generate may omit it. Confirm the generated SQL contains `CREATE TYPE document_status AS ENUM`.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run db:generate && npm run db:migrate 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<done>Migration runs without error. `clients` and `documents` tables exist in the database. `document_status` enum exists in PostgreSQL. schema.ts exports `clients`, `documents`, and `documentStatusEnum`.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Update middleware and auth config to protect /portal routes</name>
|
||||
<files>
|
||||
teressa-copeland-homes/middleware.ts
|
||||
teressa-copeland-homes/src/lib/auth.config.ts
|
||||
teressa-copeland-homes/src/app/agent/(protected)/dashboard/page.tsx
|
||||
</files>
|
||||
<action>
|
||||
**middleware.ts**: Read the file. Change the matcher to include both `/agent/:path*` and `/portal/:path*`:
|
||||
```typescript
|
||||
export const config = {
|
||||
matcher: ["/agent/:path*", "/portal/:path*"],
|
||||
};
|
||||
```
|
||||
|
||||
**auth.config.ts**: Read the file. Make two changes:
|
||||
1. In the `authorized` callback, extend the protected route check to also cover `/portal` routes:
|
||||
```typescript
|
||||
// Add alongside the existing /agent check:
|
||||
if (nextUrl.pathname.startsWith("/portal")) {
|
||||
if (!isLoggedIn) return Response.redirect(new URL("/agent/login", nextUrl));
|
||||
}
|
||||
```
|
||||
2. Change the post-login redirect (where a logged-in user visiting the login page is sent) from `/agent/dashboard` to `/portal/dashboard`:
|
||||
```typescript
|
||||
// Change: new URL("/agent/dashboard", nextUrl)
|
||||
// To: new URL("/portal/dashboard", nextUrl)
|
||||
```
|
||||
|
||||
PITFALL (from RESEARCH.md): The `authorized` callback in Auth.js v5 beta may handle the redirect differently than a plain middleware — read the existing callback logic carefully and mirror the existing `/agent` protection pattern rather than rewriting it.
|
||||
|
||||
**src/app/agent/(protected)/dashboard/page.tsx**: Replace the dashboard stub with a redirect to `/portal/dashboard`. Import `redirect` from `next/navigation` and call `redirect("/portal/dashboard")` as the only content. This ensures any old link to `/agent/dashboard` silently forwards to the new location.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1</automated>
|
||||
</verify>
|
||||
<done>TypeScript compiles with zero errors. middleware.ts matcher includes `/portal/:path*`. auth.config.ts post-login redirect is `/portal/dashboard`. /agent/dashboard/page.tsx redirects to /portal/dashboard.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. `npm run db:generate && npm run db:migrate` completes without error
|
||||
2. `npx tsc --noEmit` produces zero TypeScript errors
|
||||
3. schema.ts contains exports for `clients`, `documents`, and `documentStatusEnum`
|
||||
4. middleware.ts matcher array contains `"/portal/:path*"`
|
||||
5. auth.config.ts contains `startsWith("/portal")` in protected route check
|
||||
6. auth.config.ts post-login redirect is `/portal/dashboard`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Drizzle migration creates `clients` and `documents` tables and `document_status` enum in PostgreSQL
|
||||
- All `/portal/` routes require authentication (middleware protects them)
|
||||
- After login, agent is redirected to `/portal/dashboard`
|
||||
- TypeScript compiles cleanly
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/03-agent-portal-shell/03-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user