feat(web-next): minimal authenticated shell home

Shell home for the platform-retrofit floor: a sign-in form (dev-seed
credentials) and a signed-in placeholder with sign-out. Both flows run
through the composed tRPC appRouter via server actions that own the
session cookie on the app side; the auth feature is untouched. Adds the
auth sign-in Playwright spec as the surviving e2e baseline and drops the
last demo-template metadata.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
2026-07-12 20:20:37 +02:00
parent e1aa934153
commit f7b869bf67
6 changed files with 344 additions and 6 deletions

View File

@@ -5,8 +5,8 @@ import { bindAll } from "../server/bind-production";
import { Providers } from "./providers";
export const metadata: Metadata = {
title: "Template — Next.js",
description: "Clean Architecture Monorepo Template",
title: "Veect",
description: "Veect",
};
export default async function RootLayout({

View File

@@ -1,11 +1,90 @@
import { cookies } from "next/headers";
import { SESSION_COOKIE } from "@repo/auth";
import { bindAll } from "../server/bind-production";
import { signInAction, signOutAction } from "../server/auth-actions";
export default async function Home() {
/**
* Shell home: the sign-in surface plus a minimal authenticated placeholder.
* The workspaces UI that replaces the placeholder arrives with the
* walking-skeleton PRD. Signed-in state is cookie presence only — session
* validation stays inside the auth feature and is exercised on sign-out.
*/
export default async function Home({
searchParams,
}: {
searchParams: Promise<{ error?: string }>;
}) {
await bindAll();
const [cookieStore, params] = await Promise.all([cookies(), searchParams]);
const signedIn = cookieStore.has(SESSION_COOKIE);
return (
<main className="mx-auto max-w-5xl px-6 py-8">
<h1 className="mb-4 text-2xl font-bold text-foreground">Veect</h1>
<main className="mx-auto flex min-h-screen w-full max-w-sm flex-col justify-center gap-6 px-6 py-8">
<h1 className="text-2xl font-bold text-foreground">Veect</h1>
{signedIn ? (
<section aria-label="Signed in" className="flex flex-col gap-4">
<p className="text-foreground">You are signed in.</p>
<form action={signOutAction}>
<button
type="submit"
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground"
>
Sign out
</button>
</form>
</section>
) : (
<form
action={signInAction}
aria-label="Sign in"
className="flex flex-col gap-4"
>
<div className="flex flex-col gap-1">
<label
htmlFor="username"
className="text-sm font-medium text-foreground"
>
Username
</label>
<input
id="username"
name="username"
type="text"
autoComplete="username"
required
className="rounded-md border border-input bg-background px-3 py-2 text-foreground"
/>
</div>
<div className="flex flex-col gap-1">
<label
htmlFor="password"
className="text-sm font-medium text-foreground"
>
Password
</label>
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className="rounded-md border border-input bg-background px-3 py-2 text-foreground"
/>
</div>
{params.error === "invalid-credentials" ? (
<p role="alert" className="text-sm text-destructive">
Invalid username or password.
</p>
) : null}
<button
type="submit"
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground"
>
Sign in
</button>
</form>
)}
</main>
);
}