195 lines
6.2 KiB
TypeScript
195 lines
6.2 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { authFetch, getApiBase, setToken } from '../lib/auth'
|
|
import BrandingLogo from '../ui/BrandingLogo'
|
|
|
|
type SignupConfig = {
|
|
invites_enabled: boolean
|
|
captcha_provider: string
|
|
hcaptcha_site_key?: string | null
|
|
recaptcha_site_key?: string | null
|
|
turnstile_site_key?: string | null
|
|
}
|
|
|
|
export default function RegisterPage() {
|
|
const router = useRouter()
|
|
const [config, setConfig] = useState<SignupConfig | null>(null)
|
|
const [inviteCode, setInviteCode] = useState('')
|
|
const [username, setUsername] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [email, setEmail] = useState('')
|
|
const [discord, setDiscord] = useState('')
|
|
const [telegram, setTelegram] = useState('')
|
|
const [matrix, setMatrix] = useState('')
|
|
const [captchaToken, setCaptchaToken] = useState('')
|
|
const [status, setStatus] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
const load = async () => {
|
|
try {
|
|
const baseUrl = getApiBase()
|
|
const response = await authFetch(`${baseUrl}/auth/signup/config`)
|
|
if (!response.ok) {
|
|
throw new Error('Signup unavailable')
|
|
}
|
|
const data = await response.json()
|
|
setConfig(data)
|
|
} catch (err) {
|
|
console.error(err)
|
|
setStatus('Sign-up is not available right now.')
|
|
}
|
|
}
|
|
void load()
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (!config?.captcha_provider || config.captcha_provider === 'none') {
|
|
return
|
|
}
|
|
const provider = config.captcha_provider
|
|
const script = document.createElement('script')
|
|
if (provider === 'hcaptcha') {
|
|
script.src = 'https://js.hcaptcha.com/1/api.js'
|
|
} else if (provider === 'recaptcha') {
|
|
script.src = 'https://www.google.com/recaptcha/api.js'
|
|
} else if (provider === 'turnstile') {
|
|
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'
|
|
}
|
|
script.async = true
|
|
script.defer = true
|
|
document.body.appendChild(script)
|
|
;(window as any).magentCaptchaCallback = (token: string) => {
|
|
setCaptchaToken(token)
|
|
}
|
|
return () => {
|
|
document.body.removeChild(script)
|
|
}
|
|
}, [config?.captcha_provider])
|
|
|
|
const submit = async (event: React.FormEvent) => {
|
|
event.preventDefault()
|
|
setStatus(null)
|
|
try {
|
|
const baseUrl = getApiBase()
|
|
const response = await authFetch(`${baseUrl}/auth/register`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
invite_code: inviteCode,
|
|
username,
|
|
password,
|
|
contact: { email, discord, telegram, matrix },
|
|
captcha_token: captchaToken,
|
|
}),
|
|
})
|
|
if (!response.ok) {
|
|
const text = await response.text()
|
|
throw new Error(text || 'Registration failed')
|
|
}
|
|
const data = await response.json()
|
|
if (data?.access_token) {
|
|
setToken(data.access_token)
|
|
}
|
|
router.push('/')
|
|
} catch (err) {
|
|
console.error(err)
|
|
const message =
|
|
err instanceof Error && err.message
|
|
? err.message.replace(/^\\{\"detail\":\"|\"\\}$/g, '')
|
|
: 'Could not complete sign-up.'
|
|
setStatus(message)
|
|
}
|
|
}
|
|
|
|
const captchaProvider = config?.captcha_provider || 'none'
|
|
const captchaKey =
|
|
captchaProvider === 'hcaptcha'
|
|
? config?.hcaptcha_site_key
|
|
: captchaProvider === 'recaptcha'
|
|
? config?.recaptcha_site_key
|
|
: config?.turnstile_site_key
|
|
|
|
return (
|
|
<main className="card auth-card">
|
|
<BrandingLogo className="brand-logo brand-logo--login" />
|
|
<h1>Create your account</h1>
|
|
{!config?.invites_enabled ? (
|
|
<div className="status-banner">Sign-ups are currently closed.</div>
|
|
) : (
|
|
<form onSubmit={submit} className="auth-form">
|
|
<label>
|
|
Invite code
|
|
<input
|
|
type="text"
|
|
value={inviteCode}
|
|
onChange={(event) => setInviteCode(event.target.value)}
|
|
required
|
|
/>
|
|
</label>
|
|
<label>
|
|
Username
|
|
<input
|
|
type="text"
|
|
value={username}
|
|
onChange={(event) => setUsername(event.target.value)}
|
|
required
|
|
/>
|
|
</label>
|
|
<label>
|
|
Password
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={(event) => setPassword(event.target.value)}
|
|
required
|
|
/>
|
|
</label>
|
|
<label>
|
|
Email address (optional)
|
|
<input type="email" value={email} onChange={(event) => setEmail(event.target.value)} />
|
|
</label>
|
|
<label>
|
|
Discord handle (optional)
|
|
<input
|
|
type="text"
|
|
value={discord}
|
|
onChange={(event) => setDiscord(event.target.value)}
|
|
/>
|
|
</label>
|
|
<label>
|
|
Telegram ID (optional)
|
|
<input
|
|
type="text"
|
|
value={telegram}
|
|
onChange={(event) => setTelegram(event.target.value)}
|
|
/>
|
|
</label>
|
|
<label>
|
|
Matrix ID (optional)
|
|
<input type="text" value={matrix} onChange={(event) => setMatrix(event.target.value)} />
|
|
</label>
|
|
{captchaProvider !== 'none' && captchaKey ? (
|
|
<div className="captcha-wrap">
|
|
{captchaProvider === 'hcaptcha' && (
|
|
<div className="h-captcha" data-sitekey={captchaKey} data-callback="magentCaptchaCallback" />
|
|
)}
|
|
{captchaProvider === 'recaptcha' && (
|
|
<div className="g-recaptcha" data-sitekey={captchaKey} data-callback="magentCaptchaCallback" />
|
|
)}
|
|
{captchaProvider === 'turnstile' && (
|
|
<div className="cf-turnstile" data-sitekey={captchaKey} data-callback="magentCaptchaCallback" />
|
|
)}
|
|
</div>
|
|
) : null}
|
|
{status && <div className="status-banner">{status}</div>}
|
|
<div className="auth-actions">
|
|
<button type="submit">Create account</button>
|
|
</div>
|
|
</form>
|
|
)}
|
|
</main>
|
|
)
|
|
}
|