// Profile — URL `/profile`. Shows the signed-in identity (from the gate's // `session.me`, re-read here), a self-contained password-change (requests a reset // link via the api's `/auth/password-reset` route), and sign out. Kept // deliberately thin — the portal is account admin, not a settings sprawl. import type { ReactNode } from 'react' import { useEffect, useState, type FormEvent } from 'react' import type { PageMeta } from '@voltro/web' import { useAction } from '@voltro/client' import { T, useT } from '@voltro/i18n' import { getCatalog } from '../../../locales/index' import type { Identity } from '../../../lib/api' export const renderMode = 'ssr' as const export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({ title: getCatalog(locale)['meta.profile.title'], }) const csrf = async (): Promise => { const body = (await fetch('/auth/csrf').then((r) => r.json())) as { csrfToken?: string } return body.csrfToken ?? '' } export default function Profile(): ReactNode { const me = useAction, Identity>('app', 'session.me') const [ident, setIdent] = useState(null) const [email, setEmail] = useState('') const [sent, setSent] = useState(false) const emailLabel = useT('profile.cp.email') useEffect(() => { void me.run({}).then(setIdent).catch(() => setIdent(null)) }, []) // read the identity once on mount const onChangePassword = (e: FormEvent): void => { e.preventDefault() void csrf() .then((token) => fetch('/auth/password-reset', { method: 'POST', headers: { 'content-type': 'application/json', 'x-csrf-token': token }, body: JSON.stringify({ email }), }), ) .finally(() => setSent(true)) } const signOut = (): void => { void csrf() .then((token) => fetch('/auth/sign-out', { method: 'POST', headers: { 'x-csrf-token': token } })) .finally(() => window.location.assign('/login')) } return (

{ident?.id ?? '—'}
{ident?.tenantId ?? '—'}

{sent ? (

) : (
setEmail(e.target.value)} />
)}
) }