'use client' import { Suspense, useEffect, useMemo, useState } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import BrandingLogo from '../ui/BrandingLogo' import { clearToken, getApiBase, setToken } from '../lib/auth' type InviteInfo = { code: string label?: string | null description?: string | null enabled: boolean is_expired?: boolean is_usable?: boolean expires_at?: string | null max_uses?: number | null use_count?: number | null remaining_uses?: number | null profile?: { id: number name: string description?: string | null } | null } const formatDate = (value?: string | null) => { if (!value) return 'Never' const date = new Date(value) if (Number.isNaN(date.valueOf())) return value return date.toLocaleString() } function SignupPageContent() { const router = useRouter() const searchParams = useSearchParams() const [inviteCode, setInviteCode] = useState(searchParams.get('code') ?? '') const [invite, setInvite] = useState(null) const [inviteLoading, setInviteLoading] = useState(false) const [loading, setLoading] = useState(false) const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [confirmPassword, setConfirmPassword] = useState('') const [error, setError] = useState(null) const [status, setStatus] = useState(null) const canSubmit = useMemo(() => { return Boolean(invite?.is_usable && username.trim() && password && !loading) }, [invite, username, password, loading]) const lookupInvite = async (code: string) => { const trimmed = code.trim() if (!trimmed) { setInvite(null) return } setInviteLoading(true) setError(null) setStatus(null) try { const baseUrl = getApiBase() const response = await fetch(`${baseUrl}/auth/invites/${encodeURIComponent(trimmed)}`) if (!response.ok) { const text = await response.text() throw new Error(text || 'Invite not found') } const data = await response.json() setInvite(data?.invite ?? null) setStatus('Invite loaded.') } catch (err) { console.error(err) setInvite(null) setError('Invite code not found or unavailable.') } finally { setInviteLoading(false) } } useEffect(() => { const initialCode = searchParams.get('code') ?? '' if (initialCode) { setInviteCode(initialCode) void lookupInvite(initialCode) } }, [searchParams]) const submit = async (event: React.FormEvent) => { event.preventDefault() if (password !== confirmPassword) { setError('Passwords do not match.') return } if (!inviteCode.trim()) { setError('Invite code is required.') return } if (!invite?.is_usable) { setError('Invite is not usable. Refresh invite details or ask an admin for a new code.') return } setLoading(true) setError(null) setStatus(null) try { clearToken() const baseUrl = getApiBase() const response = await fetch(`${baseUrl}/auth/signup`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ invite_code: inviteCode, username: username.trim(), password, }), }) if (!response.ok) { const text = await response.text() throw new Error(text || 'Sign-up failed') } const data = await response.json() if (data?.access_token) { setToken(data.access_token) window.location.href = '/' return } throw new Error('Sign-up did not return a token') } catch (err) { console.error(err) setError(err instanceof Error ? err.message : 'Unable to create account.') } finally { setLoading(false) } } return (

Create account

Use an invite code from your admin to create a Magent account.

{invite && (
{invite.label || invite.code} {invite.is_usable ? 'Usable' : 'Unavailable'}
{invite.description &&

{invite.description}

}
Code: {invite.code} Expires: {formatDate(invite.expires_at)} Remaining uses: {invite.remaining_uses ?? 'Unlimited'} Profile: {invite.profile?.name || 'None'}
)} {error &&
{error}
} {status &&
{status}
}
) } export default function SignupPage() { return ( Loading sign-up…}> ) }