Sync dev changes into release-1.0
This commit is contained in:
194
frontend/app/register/page.tsx
Normal file
194
frontend/app/register/page.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user