Configures both previously-empty test runners (vitest.config.ts, playwright.config.ts) and adds the typed lib/api client layer (axios + interceptors), the session-cookie plumbing (lib/auth), TanStack Query infrastructure (lib/query, providers), and a real sign-in flow consuming supporthub-api's own login (010-identity-auth) - the true foundation every other user story in this feature depends on. Two structural fixes to the existing scaffold, both found only by running the app rather than by inspection: middleware.ts belongs at src/middleware.ts under this project's src/ layout, not the repo root; and next.config.mjs's output:'export' is incompatible with Next.js Middleware outright (the dev server refuses to start it), so this app now runs as a standard Next.js server - confirmed with the user before making that deployment-mode change. Verified end-to-end with a real, locally-running supporthub-api: all 5 Playwright scenarios (unauthenticated redirect, sign-in, wrong-password generic error, non-admin role gating, sign-out) pass against a live backend, not a mock. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import { FormEvent, useState } from 'react';
|
|
import { Button, Input, Alert, AlertDescription } from '@/components/ui';
|
|
import { useLogin } from './use-login';
|
|
import { ApiError } from '@/lib/api/types';
|
|
|
|
/** US0 acceptance scenario 3: a login failure (wrong password or unknown email) is shown as
|
|
* one generic message — 010-identity-auth's own identical-failure-response guarantee — never
|
|
* a hint about which part was wrong. */
|
|
export function SignInForm() {
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const login = useLogin();
|
|
|
|
function handleSubmit(event: FormEvent) {
|
|
event.preventDefault();
|
|
login.mutate({ email, password });
|
|
}
|
|
|
|
// Renders the backend's own message verbatim (Principle IV) — 010-identity-auth already
|
|
// guarantees this single, generic message for every failure branch (wrong password, unknown
|
|
// email, inactive account), so there's nothing for the frontend to add or rephrase.
|
|
const errorMessage = login.error instanceof ApiError ? login.error.message : undefined;
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="w-full max-w-sm flex flex-col gap-4">
|
|
{errorMessage && (
|
|
<Alert variant="destructive">
|
|
<AlertDescription>{errorMessage}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
<Input
|
|
type="email"
|
|
label="Email"
|
|
required
|
|
autoComplete="username"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
/>
|
|
<Input
|
|
type="password"
|
|
label="Password"
|
|
required
|
|
autoComplete="current-password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
/>
|
|
<Button type="submit" isLoading={login.isPending} className="w-full">
|
|
Sign in
|
|
</Button>
|
|
</form>
|
|
);
|
|
}
|