From 41703bf74b7ddc7a8bd958f901a1d1f760f17f87 Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Thu, 19 Mar 2026 13:24:04 -0600 Subject: [PATCH] docs(01-foundation): create phase 1 plan --- .planning/ROADMAP.md | 9 +- .planning/phases/01-foundation/01-01-PLAN.md | 440 +++++++++++++++++++ .planning/phases/01-foundation/01-02-PLAN.md | 348 +++++++++++++++ .planning/phases/01-foundation/01-03-PLAN.md | 215 +++++++++ 4 files changed, 1009 insertions(+), 3 deletions(-) create mode 100644 .planning/phases/01-foundation/01-01-PLAN.md create mode 100644 .planning/phases/01-foundation/01-02-PLAN.md create mode 100644 .planning/phases/01-foundation/01-03-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index dc59a4c..9095b5e 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -32,9 +32,12 @@ Decimal phases appear between their surrounding integers in numeric order. 3. Visiting any `/agent/*` route while unauthenticated redirects to the login page (not a 404 or blank screen) 4. Agent can log out from the portal and is returned to the login page 5. Database schema is deployed to Neon, Vercel Blob bucket is created, and the project is deployed to Vercel with environment variables wired -**Plans**: TBD +**Plans**: 3 plans -Plans: none yet +Plans: +- [ ] 01-01-PLAN.md — Next.js scaffold, Drizzle schema, Auth.js v5 config, route-protection middleware, seed script +- [ ] 01-02-PLAN.md — Branded login page, agent portal layout, dashboard stub, logout mechanism +- [ ] 01-03-PLAN.md — Git push, Vercel deployment, production auth flow verification (checkpoint) ### Phase 2: Marketing Site **Goal**: Visitors can find Teressa online, see her brand, view the listings placeholder, and submit a contact inquiry @@ -124,7 +127,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Foundation | 0/? | Not started | - | +| 1. Foundation | 0/3 | Planned | - | | 2. Marketing Site | 0/? | Not started | - | | 3. Agent Portal Shell | 0/? | Not started | - | | 4. PDF Ingest | 0/? | Not started | - | diff --git a/.planning/phases/01-foundation/01-01-PLAN.md b/.planning/phases/01-foundation/01-01-PLAN.md new file mode 100644 index 0000000..4145c9b --- /dev/null +++ b/.planning/phases/01-foundation/01-01-PLAN.md @@ -0,0 +1,440 @@ +--- +phase: 01-foundation +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - package.json + - tsconfig.json + - next.config.ts + - tailwind.config.ts + - drizzle.config.ts + - src/lib/db/index.ts + - src/lib/db/schema.ts + - src/lib/auth.ts + - middleware.ts + - scripts/seed.ts + - drizzle/ +autonomous: true +requirements: + - AUTH-01 + - AUTH-02 + - AUTH-03 + +user_setup: + - service: neon + why: "PostgreSQL database for Drizzle ORM" + env_vars: + - name: DATABASE_URL + source: "Neon console → your project → Connection string → Pooled connection (use the ?sslmode=require URL)" + dashboard_config: + - task: "Create a new Neon project named 'teressa-copeland-homes' in the us-east-1 region" + location: "https://console.neon.tech/app/new" + - service: vercel-blob + why: "File/blob storage for signed PDFs and templates (provisioned now, used in Phase 5+)" + env_vars: + - name: BLOB_READ_WRITE_TOKEN + source: "Vercel Dashboard → your project → Storage → Blob → Create Store → token appears after creation" + dashboard_config: + - task: "Create a Blob store named 'teressa-copeland-homes-blob' linked to the Vercel project" + location: "Vercel Dashboard → Storage tab" + - service: vercel + why: "Deployment host and environment variable manager" + env_vars: + - name: AUTH_SECRET + source: "Generate with: npx auth secret — copies to clipboard; paste into Vercel dashboard" + - name: AGENT_EMAIL + source: "Teressa's login email address (e.g. teressa@teressacopelandhomes.com)" + - name: AGENT_PASSWORD + source: "A strong password for Teressa's portal login" + dashboard_config: + - task: "Create a new Vercel project linked to the Git repo; set all 5 env vars (DATABASE_URL, BLOB_READ_WRITE_TOKEN, AUTH_SECRET, AGENT_EMAIL, AGENT_PASSWORD) in the project's Environment Variables settings" + location: "https://vercel.com/new" + +must_haves: + truths: + - "Next.js project builds without TypeScript errors (npm run build succeeds)" + - "Drizzle schema generates a valid SQL migration file for the users table" + - "Auth.js configuration exports handlers, auth, signIn, signOut without import errors" + - "Middleware.ts exists at project root (not inside src/) and imports from @/lib/auth" + - "Seed script creates Teressa's hashed-password account in the database when run" + - "Vercel Blob store is provisioned and BLOB_READ_WRITE_TOKEN is available" + artifacts: + - path: "src/lib/db/schema.ts" + provides: "users table definition (id, email, password_hash, created_at)" + exports: ["users"] + - path: "src/lib/db/index.ts" + provides: "Drizzle client singleton using Neon HTTP driver" + exports: ["db"] + - path: "src/lib/auth.ts" + provides: "Auth.js v5 configuration — JWT strategy, Credentials provider, 7-day rolling session" + exports: ["handlers", "auth", "signIn", "signOut"] + - path: "middleware.ts" + provides: "Edge middleware that redirects unauthenticated /agent/* requests to /agent/login" + - path: "scripts/seed.ts" + provides: "One-time seed script that creates Teressa's account from AGENT_EMAIL + AGENT_PASSWORD env vars" + - path: "drizzle.config.ts" + provides: "Drizzle Kit configuration pointing to schema.ts and /drizzle output directory" + - path: "drizzle/" + provides: "Generated SQL migration files committed to repo" + key_links: + - from: "src/lib/auth.ts" + to: "src/lib/db" + via: "Credentials authorize() callback calls db.select(users)" + pattern: "db\\.select.*from.*users" + - from: "middleware.ts" + to: "src/lib/auth.ts" + via: "import { auth } from @/lib/auth" + pattern: "import.*auth.*from.*@/lib/auth" + - from: "scripts/seed.ts" + to: "src/lib/db/schema.ts" + via: "import { users } from ../src/lib/db/schema" + pattern: "import.*users.*from" +--- + + +Scaffold the Next.js 15 project, install all Phase 1 dependencies, define the database schema, configure Drizzle ORM, write the Auth.js v5 configuration with JWT strategy + Credentials provider, create the route-protection middleware, and write the database seed script. This plan produces all the non-UI foundation contracts that Plans 02 and 03 build on top of. + +Purpose: Every subsequent plan in this phase and every future phase depends on these files. Getting the contracts right here prevents cascading rework. +Output: Scaffolded project with auth.ts, schema.ts, db/index.ts, middleware.ts, seed.ts, and drizzle migration committed. + + + +@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md +@/Users/ccopeland/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-foundation/01-CONTEXT.md +@.planning/phases/01-foundation/01-RESEARCH.md + + + + + +src/lib/db/schema.ts will export: +```typescript +export const users = pgTable("users", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + email: text("email").notNull().unique(), + passwordHash: text("password_hash").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), +}); +``` + +src/lib/auth.ts will export: +```typescript +export const { handlers, auth, signIn, signOut } = NextAuth({ ... }); +``` + +src/lib/db/index.ts will export: +```typescript +export const db = drizzle({ client: sql, schema }); +``` + + + + + + + Task 1: Scaffold Next.js 15 project and install all dependencies + package.json, tsconfig.json, next.config.ts, tailwind.config.ts, .gitignore + +Run the following scaffold command from the parent directory of where the project should live. If the directory `/Users/ccopeland/temp/red/teressa-copeland-homes` already exists, skip scaffolding and install missing deps into the existing project. + +```bash +npx create-next-app@latest teressa-copeland-homes \ + --typescript \ + --tailwind \ + --eslint \ + --app \ + --src-dir \ + --import-alias "@/*" \ + --no-git +``` + +After scaffolding (cd into the project directory), install Phase 1 dependencies: + +```bash +# Auth + DB + Blob +npm install next-auth@beta +npm install drizzle-orm @neondatabase/serverless +npm install bcryptjs @vercel/blob zod +npm install -D drizzle-kit tsx @types/bcryptjs dotenv +``` + +After install, note the exact installed `next-auth` version with `npm ls next-auth` and pin it in package.json (replace `"next-auth": "beta"` with the exact version string like `"next-auth": "5.0.0-beta.25"`). + +Add these npm scripts to package.json: +```json +"db:generate": "drizzle-kit generate", +"db:migrate": "drizzle-kit migrate", +"db:seed": "tsx scripts/seed.ts", +"db:studio": "drizzle-kit studio" +``` + +Ensure `.gitignore` includes: +``` +.env +.env.local +.env*.local +``` + +Do NOT create a `.env` file or `.env.local` file — all secrets go through Vercel dashboard per user decision. + +Confirm `npm run build` succeeds (will show only placeholder page errors, not import errors). + + npm run build passes with no TypeScript compilation errors; npm ls next-auth shows a pinned specific version (not "beta") + Project directory exists, all dependencies installed, exact next-auth version pinned in package.json, npm run build succeeds + + + + Task 2: Define database schema, configure Drizzle Kit, and write seed script + src/lib/db/schema.ts, src/lib/db/index.ts, drizzle.config.ts, scripts/seed.ts, drizzle/ (generated) + +Create `drizzle.config.ts` at the project root: +```typescript +import "dotenv/config"; +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + schema: "./src/lib/db/schema.ts", + out: "./drizzle", + dialect: "postgresql", + dbCredentials: { + url: process.env.DATABASE_URL!, + }, +}); +``` + +Create `src/lib/db/schema.ts`: +```typescript +import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; + +export const users = pgTable("users", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + email: text("email").notNull().unique(), + passwordHash: text("password_hash").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), +}); +``` + +Create `src/lib/db/index.ts`: +```typescript +import { drizzle } from "drizzle-orm/neon-http"; +import { neon } from "@neondatabase/serverless"; +import * as schema from "./schema"; + +const sql = neon(process.env.DATABASE_URL!); +export const db = drizzle({ client: sql, schema }); +``` + +Create `scripts/seed.ts`: +```typescript +import "dotenv/config"; +import { drizzle } from "drizzle-orm/neon-http"; +import { neon } from "@neondatabase/serverless"; +import { users } from "../src/lib/db/schema"; +import bcrypt from "bcryptjs"; + +const sql = neon(process.env.DATABASE_URL!); +const db = drizzle({ client: sql }); + +async function seed() { + const email = process.env.AGENT_EMAIL; + const password = process.env.AGENT_PASSWORD; + + if (!email || !password) { + throw new Error("AGENT_EMAIL and AGENT_PASSWORD env vars are required"); + } + + const passwordHash = await bcrypt.hash(password, 12); + + await db.insert(users).values({ email, passwordHash }).onConflictDoNothing(); + + console.log(`Seeded agent account: ${email}`); + process.exit(0); +} + +seed().catch((err) => { + console.error(err); + process.exit(1); +}); +``` + +Generate migration files (requires DATABASE_URL to be set — if not yet available, generate schema only and note that `db:migrate` and `db:seed` run after user sets up Neon): +```bash +# If DATABASE_URL is available locally (via `vercel env pull` after Vercel project creation): +npm run db:generate # Creates drizzle/0000_*.sql migration file +npm run db:migrate # Applies migration to Neon database +npm run db:seed # Creates Teressa's account + +# If DATABASE_URL is NOT yet available: +# Generate migration file only (does not need DB connection): +npm run db:generate +# Note in SUMMARY.md that db:migrate and db:seed must run after user sets up Neon +``` + +The generated `/drizzle` directory with SQL migration files MUST be committed to the repo (per user decision — version-controlled, auditable migrations). + + drizzle/ directory contains at least one .sql migration file; src/lib/db/schema.ts and index.ts export users and db without TypeScript errors (npx tsc --noEmit) + Schema file exports users table, db/index.ts exports db singleton, drizzle.config.ts present, seed script present, migration SQL file exists in /drizzle and committed to git + + + + Task 3: Configure Auth.js v5 with JWT strategy and create route-protection middleware + src/lib/auth.ts, src/app/api/auth/[...nextauth]/route.ts, middleware.ts + +Create `src/lib/auth.ts` — the single source of truth for authentication configuration: +```typescript +import NextAuth from "next-auth"; +import Credentials from "next-auth/providers/credentials"; +import { db } from "@/lib/db"; +import { users } from "@/lib/db/schema"; +import { eq } from "drizzle-orm"; +import bcrypt from "bcryptjs"; +import { z } from "zod"; + +const loginSchema = z.object({ + email: z.string().email(), + password: z.string().min(1), +}); + +export const { handlers, auth, signIn, signOut } = NextAuth({ + session: { + strategy: "jwt", + maxAge: 7 * 24 * 60 * 60, // 7 days — rolling session per user decision + updateAge: 24 * 60 * 60, // Refresh cookie once per day (not every request) + }, + pages: { + signIn: "/agent/login", // Custom login page — per user decision + }, + providers: [ + Credentials({ + async authorize(credentials) { + const parsed = loginSchema.safeParse(credentials); + if (!parsed.success) return null; + + const [user] = await db + .select() + .from(users) + .where(eq(users.email, parsed.data.email)) + .limit(1); + + if (!user) return null; + + const passwordMatch = await bcrypt.compare( + parsed.data.password, + user.passwordHash + ); + if (!passwordMatch) return null; + + return { id: user.id, email: user.email }; + }, + }), + ], + callbacks: { + jwt({ token, user }) { + if (user) token.id = user.id; + return token; + }, + session({ session, token }) { + session.user.id = token.id as string; + return session; + }, + }, +}); +``` + +Create `src/app/api/auth/[...nextauth]/route.ts`: +```typescript +import { handlers } from "@/lib/auth"; +export const { GET, POST } = handlers; +``` + +Create `middleware.ts` at the PROJECT ROOT (not inside src/ — critical, middleware placed in src/ is silently ignored by Next.js): +```typescript +import { auth } from "@/lib/auth"; +import { NextResponse } from "next/server"; + +export default auth((req) => { + const isLoggedIn = !!req.auth; + const isAgentRoute = req.nextUrl.pathname.startsWith("/agent"); + const isLoginPage = req.nextUrl.pathname === "/agent/login"; + + // Protect all /agent/* routes except the login page itself + if (isAgentRoute && !isLoginPage && !isLoggedIn) { + return NextResponse.redirect(new URL("/agent/login", req.nextUrl.origin)); + } + + // Redirect already-authenticated users away from the login page + if (isLoginPage && isLoggedIn) { + return NextResponse.redirect(new URL("/agent/dashboard", req.nextUrl.origin)); + } +}); + +export const config = { + matcher: [ + // Run on /agent/* routes, excluding Next.js internals and static files + "/((?!_next/static|_next/image|favicon.ico).*)", + ], +}; +``` + +Verify `middleware.ts` is at the same level as `package.json` — NOT inside `src/`. This is the single most common pitfall: middleware inside src/ is silently ignored and routes are unprotected. + +AUTH_SECRET env var must be set for Auth.js to function. The user will add this to Vercel dashboard. For local development, note this requirement in SUMMARY.md — `vercel env pull` will populate `.env.local` once the Vercel project is created. + + npx tsc --noEmit passes with no errors in auth.ts, middleware.ts, and route.ts; middleware.ts file exists at project root (same level as package.json), NOT inside src/ + src/lib/auth.ts exports handlers, auth, signIn, signOut; API route handler exists at src/app/api/auth/[...nextauth]/route.ts; middleware.ts exists at project root with /agent/* protection logic + + + + + +Run these commands from the project root directory after completing all tasks: + +```bash +# TypeScript: zero errors across all created files +npx tsc --noEmit + +# Build: confirms Next.js can compile the project +npm run build + +# Migration file exists (committed to git) +ls drizzle/*.sql + +# Middleware location (MUST be at root, not src/) +ls middleware.ts && echo "OK" || echo "MISSING — check location" +ls src/middleware.ts 2>/dev/null && echo "WRONG LOCATION" || echo "Correctly absent from src/" + +# Auth exports are importable (no circular deps or missing modules) +node -e "import('./src/lib/auth.ts').then(() => console.log('auth.ts OK')).catch(e => console.error(e.message))" +``` + + + +- npm run build succeeds with no TypeScript errors +- drizzle/ directory contains at least one committed SQL migration file +- middleware.ts exists at project root, NOT inside src/ +- src/lib/auth.ts exports handlers, auth, signIn, signOut (JWT strategy, 7-day maxAge, Credentials provider) +- src/lib/db/schema.ts exports users table with id, email, passwordHash, createdAt columns +- src/lib/db/index.ts exports db (Drizzle + Neon HTTP driver singleton) +- src/app/api/auth/[...nextauth]/route.ts exports GET and POST +- scripts/seed.ts reads AGENT_EMAIL and AGENT_PASSWORD from env and creates hashed user row +- next-auth version is pinned to a specific beta version (not "beta" or "^5...") + + + +After completion, create `.planning/phases/01-foundation/01-01-SUMMARY.md` using the summary template. + +Include in the summary: +- Exact next-auth beta version pinned +- Whether db:migrate and db:seed were run successfully (or if they are pending user Neon setup) +- Location of middleware.ts confirmed (root vs src/) +- Any deviations from the planned patterns and why + diff --git a/.planning/phases/01-foundation/01-02-PLAN.md b/.planning/phases/01-foundation/01-02-PLAN.md new file mode 100644 index 0000000..390f38a --- /dev/null +++ b/.planning/phases/01-foundation/01-02-PLAN.md @@ -0,0 +1,348 @@ +--- +phase: 01-foundation +plan: 02 +type: execute +wave: 2 +depends_on: + - "01-01" +files_modified: + - src/app/agent/login/page.tsx + - src/app/agent/dashboard/page.tsx + - src/app/agent/layout.tsx + - src/components/ui/LogoutButton.tsx + - src/app/layout.tsx + - src/app/page.tsx + - public/red.jpg +autonomous: true +requirements: + - AUTH-01 + - AUTH-02 + - AUTH-03 + - AUTH-04 + +must_haves: + truths: + - "Visiting /agent/login displays a branded form with email, password (with show/hide toggle), and a submit button" + - "Submitting invalid credentials shows 'Invalid email or password' without hinting which field is wrong" + - "Submitting valid credentials redirects to /agent/dashboard" + - "After login, the agent's email is visible somewhere on the dashboard" + - "The dashboard has a logout button that, when clicked, redirects to /agent/login" + - "Visiting /agent/login after logout shows 'You've been signed out' confirmation message" + - "Visiting /agent/dashboard without a session redirects to /agent/login (enforced by middleware from Plan 01)" + artifacts: + - path: "src/app/agent/login/page.tsx" + provides: "Branded login page with email/password form, password toggle, error display, signed-out confirmation" + exports: ["default (LoginPage)"] + - path: "src/app/agent/dashboard/page.tsx" + provides: "Protected blank dashboard — server component that calls auth() and shows agent email + logout button" + exports: ["default (DashboardPage)"] + - path: "src/app/agent/layout.tsx" + provides: "Agent portal layout wrapper — defense-in-depth session check, shared nav shell" + exports: ["default (AgentLayout)"] + - path: "src/components/ui/LogoutButton.tsx" + provides: "Client component with logout form that calls signOut server action" + exports: ["LogoutButton"] + key_links: + - from: "src/app/agent/login/page.tsx" + to: "src/lib/auth.ts" + via: "loginAction() calls signIn('credentials', ...) from @/lib/auth" + pattern: "signIn.*credentials" + - from: "src/components/ui/LogoutButton.tsx" + to: "src/lib/auth.ts" + via: "logoutAction() calls signOut({ redirectTo: '/agent/login?signed_out=1' })" + pattern: "signOut.*signed_out" + - from: "src/app/agent/dashboard/page.tsx" + to: "src/lib/auth.ts" + via: "const session = await auth() — defense-in-depth beyond middleware" + pattern: "await auth\\(\\)" +--- + + +Build the two agent-facing pages (login and dashboard) and the logout mechanism. The login page is fully branded with Teressa's photo and implements all user-specified behaviors. The dashboard is a blank stub that confirms the session is valid and provides a logout button. Together these pages satisfy all four AUTH requirements. + +Purpose: These pages are the visible proof that Phase 1 works. The middleware from Plan 01 protects routes — these pages are what the agent actually sees. +Output: /agent/login (branded form) and /agent/dashboard (blank, protected, with logout). + + + +@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md +@/Users/ccopeland/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/01-foundation/01-CONTEXT.md +@.planning/phases/01-foundation/01-RESEARCH.md +@.planning/phases/01-foundation/01-01-SUMMARY.md + + + + + +From src/lib/auth.ts (created in Plan 01): +```typescript +export const { handlers, auth, signIn, signOut } = NextAuth({ ... }); +// auth() — call in server components to get session +// signIn("credentials", { email, password, redirectTo }) — call in server actions +// signOut({ redirectTo }) — call in server actions +``` + +From src/lib/db/schema.ts (created in Plan 01): +```typescript +export const users = pgTable("users", { ... }); +// Not needed directly in UI — session comes from auth() +``` + +Key behaviors locked by user decisions (CONTEXT.md): +- Login route: /agent/login +- Post-login redirect: /agent/dashboard +- Error message: "Invalid email or password" (generic — do not vary by field) +- Password visibility toggle: included +- Session behavior: 7-day rolling (handled by auth.ts — no code needed here) +- Logout: signOut({ redirectTo: "/agent/login?signed_out=1" }) +- Logout confirmation: "You've been signed out" (shown when ?signed_out=1 is in URL) +- No forgot-password link (per deferred decisions — do NOT add it) + +Brand assets: +- Agent photo: /Users/ccopeland/Downloads/red.jpg — copy to public/red.jpg +- Brand colors/logo: Use discretion — create CSS custom properties for easy swap later + - Suggested color palette: warm gold (#C9A84C) for accent, deep navy (#1B2B4B) for primary, off-white (#FAF9F7) for background + - Login page should feel like a premium real estate brand, not a generic SaaS admin + + + + + + + Task 1: Build the branded login page with password toggle and error handling + src/app/agent/login/page.tsx, public/red.jpg + +Copy the agent photo from its source path to the public directory: +```bash +cp /Users/ccopeland/Downloads/red.jpg /path/to/project/public/red.jpg +``` +If the file does not exist at that path, use a placeholder image (create a simple Next.js Image with a colored background placeholder) and note it in the SUMMARY.md. + +Create `src/app/agent/login/page.tsx` as a **server component** (no "use client") that: + +1. Reads `searchParams` to detect `?error=invalid` and `?signed_out=1` +2. Contains an inline `loginAction` server action that calls signIn +3. Contains an inline `SignedOutMessage` and `ErrorMessage` based on searchParams +4. Renders a full-page branded layout with a left/right split or centered card + +The login page structure: +- Page fills the full viewport height (min-h-screen) +- Left side (hidden on mobile): Teressa's photo (`/red.jpg`) as a cover image, overlaid with a subtle dark gradient and brand name text +- Right side (or centered on mobile): Login form card + - "Agent Portal" heading (h1, per user decision) + - If `?signed_out=1`: green/teal banner — "You've been signed out." + - If `?error=invalid`: red banner — "Invalid email or password." + - Email input: type="email", name="email", required, label="Email address" + - Password input: type="password" by default, name="password", required, label="Password" + - Password toggle button (show/hide) — this is a client interaction, so wrap just the password field and toggle button in a small "use client" sub-component called `PasswordField` + - Submit button: "Sign in" text, full-width, shows loading state during submission + - Brand accent color on the submit button and focus rings + +The `loginAction` server action (inside the page file): +```typescript +async function loginAction(formData: FormData) { + "use server"; + try { + await signIn("credentials", { + email: formData.get("email") as string, + password: formData.get("password") as string, + redirectTo: "/agent/dashboard", + }); + } catch (error) { + // CRITICAL: Only catch AuthError. Everything else (including NEXT_REDIRECT) must re-throw. + if (error instanceof AuthError) { + redirect("/agent/login?error=invalid"); + } + throw error; // Re-throws NEXT_REDIRECT — allows the redirect to fire + } +} +``` + +The `PasswordField` sub-component (can live in same file or separate file, use client): +- Manages `showPassword` boolean state +- Renders `` +- Renders a toggle button (eye/eye-off icon or text "Show"/"Hide") +- Use Tailwind for all styling — no additional UI libraries + +Brand color guide (CSS custom properties or Tailwind classes): +- Primary background: #FAF9F7 (warm off-white) +- Primary text: #1B2B4B (deep navy) +- Accent (buttons, focus rings): #C9A84C (warm gold) +- Error background: #FEF2F2, error text: #991B1B +- Success background: #F0FDF4, success text: #166534 + +Do NOT add a "Forgot password?" link — this is explicitly deferred per user decisions. + + +Navigate to /agent/login in browser (or run: curl -s http://localhost:3000/agent/login | grep -i "Agent Portal"). Verify: +1. Page renders without errors +2. Submitting wrong credentials reloads with "Invalid email or password" banner +3. Password toggle switches input type between password/text +4. npm run build passes with no TypeScript errors in this file + + Login page renders at /agent/login with branded design, password toggle works, error displays on failed login, signed-out message displays when ?signed_out=1, no TypeScript errors + + + + Task 2: Build the agent portal layout, dashboard stub, and logout mechanism + src/app/agent/layout.tsx, src/app/agent/dashboard/page.tsx, src/components/ui/LogoutButton.tsx, src/app/page.tsx + +Create `src/components/ui/LogoutButton.tsx` as a client component with a server action for logout: +```typescript +// src/components/ui/LogoutButton.tsx +import { signOut } from "@/lib/auth"; + +async function logoutAction() { + "use server"; + await signOut({ redirectTo: "/agent/login?signed_out=1" }); +} + +export function LogoutButton() { + return ( +
+ +
+ ); +} +``` + +Note: signOut() server action must NOT be caught — it throws NEXT_REDIRECT which must bubble up. The form action pattern avoids the issue. + +Create `src/app/agent/layout.tsx` — the shared agent portal layout: +```typescript +import { auth } from "@/lib/auth"; +import { redirect } from "next/navigation"; +import { LogoutButton } from "@/components/ui/LogoutButton"; + +export default async function AgentLayout({ + children, +}: { + children: React.ReactNode; +}) { + // Defense-in-depth: middleware handles most cases, this catches edge cases + const session = await auth(); + if (!session) redirect("/agent/login"); + + return ( +
+
+ Agent Portal +
+ {session.user?.email} + +
+
+
{children}
+
+ ); +} +``` + +Create `src/app/agent/dashboard/page.tsx` — blank dashboard stub: +```typescript +import { auth } from "@/lib/auth"; +import { redirect } from "next/navigation"; + +export default async function DashboardPage() { + // Defense-in-depth session check (layout also checks, this is belt-and-suspenders) + const session = await auth(); + if (!session) redirect("/agent/login"); + + return ( +
+

Dashboard

+

+ Welcome back, {session.user?.email}. Portal content coming in Phase 3. +

+
+ ); +} +``` + +Update `src/app/page.tsx` (public homepage — placeholder for Phase 2): +```typescript +export default function HomePage() { + return ( +
+
+

Teressa Copeland Homes

+

Real estate expertise for Utah home buyers and sellers.

+

Marketing site coming in Phase 2.

+
+
+ ); +} +``` + +AUTH-04 verification: The LogoutButton must be accessible from the dashboard (it is in the agent layout header). Clicking it calls logoutAction which calls signOut() with ?signed_out=1, which redirects to /agent/login, which reads the query param and shows "You've been signed out." + +AUTH-02 verification: The 7-day rolling JWT session is handled entirely in auth.ts (Plan 01). No additional code needed here — the httpOnly encrypted cookie persists across browser restarts by default. Do not add any sessionStorage usage. + +AUTH-03 verification: The middleware from Plan 01 handles unauthenticated redirects. The layout's auth() check is defense-in-depth — not the primary enforcement mechanism. Both layers must be in place. +
+ +1. Run npm run build — no TypeScript errors +2. Verify agent layout exists: ls src/app/agent/layout.tsx +3. Verify dashboard exists: ls src/app/agent/dashboard/page.tsx +4. Verify LogoutButton exists: ls src/components/ui/LogoutButton.tsx +5. TypeScript check: npx tsc --noEmit + + AgentLayout renders portal header with agent email and sign-out button; DashboardPage shows welcome message with agent email; LogoutButton calls signOut({ redirectTo: "/agent/login?signed_out=1" }); npm run build and npx tsc --noEmit pass with no errors +
+ +
+ + +Full auth flow verification (requires running dev server): + +```bash +npm run dev +``` + +Manual verification checklist: +1. Visit http://localhost:3000/agent/dashboard — should redirect to /agent/login (AUTH-03) +2. Submit wrong credentials on /agent/login — should show "Invalid email or password" (AUTH-01) +3. Submit correct credentials — should redirect to /agent/dashboard with agent email visible (AUTH-01) +4. Refresh /agent/dashboard — should stay logged in, not redirect (AUTH-02 partial — session persists across refresh) +5. Click "Sign out" — should redirect to /agent/login with "You've been signed out" message (AUTH-04) +6. Visit /agent/dashboard while logged out — should redirect to /agent/login (AUTH-03) + +Build verification: +```bash +npm run build && echo "BUILD OK" +npx tsc --noEmit && echo "TYPECHECK OK" +``` + + + +- /agent/login renders with branded design — photo, "Agent Portal" heading, email/password form +- Password show/hide toggle works +- Invalid login shows "Invalid email or password" (not a more specific error) +- Valid login redirects to /agent/dashboard +- /agent/dashboard shows agent email and Sign out button +- Signing out redirects to /agent/login with "You've been signed out" message +- /agent/dashboard (and all future /agent/* routes) redirect to /agent/login when unauthenticated +- npm run build passes with no errors +- No forgot-password link exists on any page (deferred per user decisions) + + + +After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md` using the summary template. + +Include in the summary: +- Confirmation that the agent photo was copied from /Users/ccopeland/Downloads/red.jpg (or note if placeholder used) +- Confirmation of the brand colors used (or CSS custom properties defined) +- Manual verification results for the full auth flow (items 1-6 above) +- Any deviations from planned patterns and why + diff --git a/.planning/phases/01-foundation/01-03-PLAN.md b/.planning/phases/01-foundation/01-03-PLAN.md new file mode 100644 index 0000000..31e912d --- /dev/null +++ b/.planning/phases/01-foundation/01-03-PLAN.md @@ -0,0 +1,215 @@ +--- +phase: 01-foundation +plan: 03 +type: execute +wave: 3 +depends_on: + - "01-01" + - "01-02" +files_modified: [] +autonomous: false +requirements: + - AUTH-01 + - AUTH-02 + - AUTH-03 + - AUTH-04 + +must_haves: + truths: + - "Project is deployed and live at a Vercel URL (teressacopelandhomes.com or a *.vercel.app preview URL)" + - "Agent can log in with email and password in the production deployment" + - "Agent session persists after browser tab close and reopen" + - "Visiting any /agent/* route while logged out redirects to /agent/login" + - "Agent can log out and sees 'You've been signed out'" + - "Database schema is live in Neon with Teressa's seeded account" + - "Vercel Blob store exists and BLOB_READ_WRITE_TOKEN is set in Vercel environment" + artifacts: + - path: "Vercel project" + provides: "Production deployment with all 5 env vars set" + - path: "Neon database" + provides: "users table with Teressa's seeded account" + - path: "Vercel Blob store" + provides: "Single blob store for future document storage (Phase 4+)" + key_links: + - from: "Vercel deployment" + to: "Neon database" + via: "DATABASE_URL env var in Vercel project settings" + pattern: "DATABASE_URL set in Vercel env" + - from: "Vercel deployment" + to: "Auth.js" + via: "AUTH_SECRET env var in Vercel project settings" + pattern: "AUTH_SECRET set in Vercel env" +--- + + +Deploy the project to Vercel, wire all environment variables, run the database migration and seed on the production Neon database, and verify the complete auth flow end-to-end in the live deployment. This plan is a blocking checkpoint — a human must confirm the production deployment works before Phase 1 is complete. + +Purpose: Phase 1's success criterion explicitly requires the project to be deployed to Vercel with environment variables wired. This checkpoint ensures that is true before declaring Phase 1 done. +Output: Live production deployment of teressacopelandhomes.com (or *.vercel.app) with working auth. + + + +@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md +@/Users/ccopeland/.claude/get-shit-done/templates/summary.md + + + +@.planning/ROADMAP.md +@.planning/phases/01-foundation/01-CONTEXT.md +@.planning/phases/01-foundation/01-01-SUMMARY.md +@.planning/phases/01-foundation/01-02-SUMMARY.md + + + + + + Task 1: Push to Git and verify Vercel auto-deploy triggers + + +Initialize a git repo if not already done, then push to the remote that Vercel's Git integration watches: + +```bash +# From the project root directory: +git init +git add . +git commit -m "feat(01-foundation): Next.js scaffold, auth, database schema, and login UI" +git branch -M main +git remote add origin +git push -u origin main +``` + +Per user decision: Vercel native Git integration is used (push to main → auto-deploy). GitHub Actions are NOT used. + +After pushing: +1. Open the Vercel dashboard for the project +2. Watch the deployment in the "Deployments" tab — it should trigger automatically +3. If the deployment FAILS because of missing env vars (expected on first deploy), that is fine — the checkpoint task below addresses this +4. Note the Vercel project URL (either custom domain or *.vercel.app URL) + +IMPORTANT: If the Vercel project was not yet created (user setup not yet done), the push will not trigger a deployment. Claude cannot create the Vercel project or set env vars — the user_setup block in Plan 01's frontmatter lists these steps. Only proceed here if the user confirms setup is complete. + +If user setup IS complete and the deployment succeeds: +```bash +# Pull env vars for local use (creates .env.local — already in .gitignore) +vercel env pull + +# Run migration against production Neon database +npm run db:migrate + +# Seed Teressa's account +npm run db:seed +``` + +If db:migrate and db:seed were already run locally against Neon during Plan 01 (because DATABASE_URL was available early), skip running them again — the onConflictDoNothing in seed.ts makes re-runs safe but unnecessary. + + Vercel dashboard shows deployment status; git push completes without error; vercel env pull creates a .env.local file with all 5 required env vars (DATABASE_URL, AUTH_SECRET, BLOB_READ_WRITE_TOKEN, AGENT_EMAIL, AGENT_PASSWORD) + Code is pushed to main, Vercel deployment has triggered (or completed), .env.local populated via vercel env pull + + + + Task 2: Verify complete auth flow in production + + Human verification of the complete Phase 1 auth flow in the live Vercel production deployment. See how-to-verify for the 7 test cases. + +Complete Phase 1 auth system: +- Branded login page at /agent/login with password toggle +- Protected agent dashboard at /agent/dashboard +- 7-day rolling JWT session (persistent across browser restarts) +- Middleware-enforced route protection for all /agent/* routes +- Logout with "You've been signed out" confirmation +- Neon database with users table and Teressa's seeded account +- Vercel Blob store provisioned +- Deployed to Vercel with all environment variables wired + + +Open the production URL from the Vercel dashboard (either https://teressacopelandhomes.com or the *.vercel.app preview URL). + +**Test 1 — Route protection (AUTH-03):** +- Navigate directly to: `[your-vercel-url]/agent/dashboard` +- Expected: Redirected to `/agent/login` (not a 404 or blank page) +- Pass / Fail? + +**Test 2 — Login with wrong credentials (AUTH-01):** +- On /agent/login, enter any email + wrong password +- Expected: Page reloads, shows "Invalid email or password" banner +- The error should NOT say which field is wrong +- Pass / Fail? + +**Test 3 — Login with correct credentials (AUTH-01):** +- On /agent/login, enter Teressa's AGENT_EMAIL and AGENT_PASSWORD +- Expected: Redirects to /agent/dashboard showing Teressa's email address +- Pass / Fail? + +**Test 4 — Session persistence (AUTH-02):** +- After logging in, close the browser tab (or close and reopen the browser window) +- Navigate back to `[your-vercel-url]/agent/dashboard` +- Expected: Still logged in — dashboard shows without redirecting to login +- Pass / Fail? + +**Test 5 — Logout (AUTH-04):** +- On /agent/dashboard, click "Sign out" +- Expected: Redirected to /agent/login with a "You've been signed out" message visible on the page +- Pass / Fail? + +**Test 6 — Post-logout protection (AUTH-03):** +- After logging out, navigate to `[your-vercel-url]/agent/dashboard` +- Expected: Redirected to /agent/login (session is invalidated) +- Pass / Fail? + +**Test 7 — Password toggle:** +- On /agent/login, type any password in the password field +- Click the show/hide toggle +- Expected: Password becomes visible as plain text +- Pass / Fail? + +If all 7 tests pass: type "approved" to complete Phase 1. +If any test fails: describe which test failed and what you saw. + + All 7 tests pass and human types "approved" + Human has approved all 7 auth flow tests in the production Vercel deployment + Type "approved" if all 7 tests pass, or describe failures + + + + + +All verification is human-driven in the checkpoint above. The 7 tests map directly to the Phase 1 success criteria from ROADMAP.md: + +| ROADMAP Criterion | Tests | +|-------------------|-------| +| Agent can log in with email and password | Tests 2 + 3 | +| Session persists after browser refresh/tab close | Test 4 | +| Unauthenticated /agent/* routes redirect to login | Tests 1 + 6 | +| Agent can log out | Test 5 | +| Database deployed to Neon, Blob created, Vercel wired | Implicit in Tests 3 + 4 working in production | + + + +All 7 verification tests pass in the production Vercel deployment: +1. /agent/dashboard redirects unauthenticated users to /agent/login +2. Wrong credentials show "Invalid email or password" +3. Correct credentials grant access to /agent/dashboard +4. Session survives browser tab close and reopen +5. Logout redirects to /agent/login with confirmation message +6. Post-logout /agent/dashboard visit redirects to login +7. Password toggle switches between hidden/visible + +Human approves the checkpoint with "approved". + + + +After human approval, create `.planning/phases/01-foundation/01-03-SUMMARY.md` using the summary template. + +Include in the summary: +- Production deployment URL +- Confirmation that all 7 verification tests passed +- Neon project name and region +- Whether db:migrate and db:seed ran successfully +- Vercel Blob store name +- Any issues encountered during deployment and how they were resolved + +Then update STATE.md: +- Current focus: Phase 2 - Marketing Site +- Phase 1 status: Complete +- Last activity: [today's date] — Phase 1 complete; auth flow verified in production +