Files
red/.planning/phases/01-foundation/01-02-PLAN.md
2026-03-19 13:24:04 -06:00

15 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
01-foundation 02 execute 2
01-01
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
true
AUTH-01
AUTH-02
AUTH-03
AUTH-04
truths artifacts key_links
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)
path provides exports
src/app/agent/login/page.tsx Branded login page with email/password form, password toggle, error display, signed-out confirmation
default (LoginPage)
path provides exports
src/app/agent/dashboard/page.tsx Protected blank dashboard — server component that calls auth() and shows agent email + logout button
default (DashboardPage)
path provides exports
src/app/agent/layout.tsx Agent portal layout wrapper — defense-in-depth session check, shared nav shell
default (AgentLayout)
path provides exports
src/components/ui/LogoutButton.tsx Client component with logout form that calls signOut server action
LogoutButton
from to via pattern
src/app/agent/login/page.tsx src/lib/auth.ts loginAction() calls signIn('credentials', ...) from @/lib/auth signIn.*credentials
from to via pattern
src/components/ui/LogoutButton.tsx src/lib/auth.ts logoutAction() calls signOut({ redirectTo: '/agent/login?signed_out=1' }) signOut.*signed_out
from to via pattern
src/app/agent/dashboard/page.tsx src/lib/auth.ts const session = await auth() — defense-in-depth beyond middleware 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).

<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/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):

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):

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):

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 <input type={showPassword ? "text" : "password"} ... />
  • 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 (

Sign out ); }


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 (
    <div className="min-h-screen bg-gray-50">
      <header className="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
        <span className="text-sm font-medium text-gray-700">Agent Portal</span>
        <div className="flex items-center gap-4">
          <span className="text-sm text-gray-500">{session.user?.email}</span>
          <LogoutButton />
        </div>
      </header>
      <main className="p-6">{children}</main>
    </div>
  );
}

Create src/app/agent/dashboard/page.tsx — blank dashboard stub:

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 (
    <div>
      <h1 className="text-2xl font-semibold text-gray-900">Dashboard</h1>
      <p className="mt-2 text-gray-500">
        Welcome back, {session.user?.email}. Portal content coming in Phase 3.
      </p>
    </div>
  );
}

Update src/app/page.tsx (public homepage — placeholder for Phase 2):

export default function HomePage() {
  return (
    <main className="min-h-screen flex items-center justify-center">
      <div className="text-center">
        <h1 className="text-4xl font-bold text-gray-900">Teressa Copeland Homes</h1>
        <p className="mt-4 text-gray-600">Real estate expertise for Utah home buyers and sellers.</p>
        <p className="mt-2 text-sm text-gray-400">Marketing site coming in Phase 2.</p>
      </div>
    </main>
  );
}

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):
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:

npm run build && echo "BUILD OK"
npx tsc --noEmit && echo "TYPECHECK OK"

<success_criteria>

  • /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) </success_criteria>
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