// Sign in / sign up — URL `/login`. A REAL auth form: it POSTs to the api's // `/auth/*` routes (contributed by @voltro/plugin-auth on the sibling api), // which set the HttpOnly session cookie. State-changing auth routes are // CSRF-protected, so we fetch a token first. On success we do a FULL navigation // (`window.location.assign`) rather than a client one, so the dashboard's SSR // auth gate re-runs with the freshly-set cookie. import type { ReactNode } from 'react' import { useState, type FormEvent } from 'react' import type { PageMeta } from '@voltro/web' import { T, useT } from '@voltro/i18n' import { getCatalog } from '../../locales/index' export const renderMode = 'static' as const export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({ title: getCatalog(locale)['meta.login.title'], }) type Mode = 'signIn' | 'signUp' // One round-trip: get a CSRF token, then POST the credentials with it. Returns // true on a 2xx (cookie set), false otherwise. async function submitAuth(mode: Mode, email: string, password: string): Promise { const csrf = (await fetch('/auth/csrf').then((r) => r.json())) as { csrfToken: string } const path = mode === 'signUp' ? '/auth/sign-up' : '/auth/sign-in' const res = await fetch(path, { method: 'POST', headers: { 'content-type': 'application/json', 'x-csrf-token': csrf.csrfToken }, body: JSON.stringify({ email, password }), }) return res.ok } export default function Login(): ReactNode { const [mode, setMode] = useState('signIn') const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [pending, setPending] = useState(false) const [failed, setFailed] = useState(false) const emailLabel = useT('auth.email') const passwordLabel = useT('auth.password') const onSubmit = (e: FormEvent): void => { e.preventDefault() setPending(true) setFailed(false) void submitAuth(mode, email, password) .then((ok) => { if (ok) { const from = new URLSearchParams(window.location.search).get('from') ?? '/' window.location.assign(from) } else { setFailed(true) setPending(false) } }) .catch(() => { setFailed(true) setPending(false) }) } return (

{failed ?

: null}

) }