/
opt
/
canhelp
/
apps
/
web
/
src
/
app
/
(auth)
/
register
/
/opt/canhelp/apps/web/src/app/(auth)/register
mkdir
upload
Name
Size
Mode
Actions
page.tsx
8768
0644
edit
dl
rm
Edit:
/opt/canhelp/apps/web/src/app/(auth)/register/page.tsx
(8768B)
'use client' import { useState, Suspense } from 'react' import { useSearchParams } from 'next/navigation' import Link from 'next/link' import { signUp, authClient } from '@/lib/auth' import { useLocale } from '@/context/locale' import { TurnstileWidget } from '@/components/Turnstile' import { CAPTCHA_ENABLED } from '@/components/Turnstile' function RegisterForm() { const searchParams = useSearchParams() const refCode = searchParams.get('ref') ?? '' const redirectParam = searchParams.get('redirect') const redirectPath = redirectParam && redirectParam.startsWith('/') ? redirectParam : '/dashboard' const { t } = useLocale() const [error, setError] = useState('') const [loading, setLoading] = useState(false) const [googleLoading, setGoogleLoading] = useState(false) const [registered, setRegistered] = useState(false) const [captchaToken, setCaptchaToken] = useState(CAPTCHA_ENABLED ? '' : 'dev') async function handleGoogleRegister() { setError('') setGoogleLoading(true) try { const callbackUrl = new URL(redirectPath, window.location.origin) if (refCode && callbackUrl.pathname === '/dashboard') { callbackUrl.searchParams.set('ref', refCode) } await authClient.signIn.social({ provider: 'google', callbackURL: callbackUrl.toString(), }) } catch { setError(t('common.error')) setGoogleLoading(false) } } async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault() setError('') setLoading(true) const form = new FormData(e.currentTarget) const firstName = form.get('firstName') as string const lastName = form.get('lastName') as string try { const result = await signUp.email({ email: form.get('email') as string, password: form.get('password') as string, name: `${firstName} ${lastName}`, // @ts-ignore — Better Auth additionalFields firstName, lastName, role: 'user', captchaToken, ...(refCode ? { refCode } : {}), callbackURL: `${window.location.origin}${redirectPath}`, }) if (result?.error) { // Better Auth returns EMAIL_NOT_VERIFIED (status 403) when signup succeeded // but email verification is required — treat this as success, not an error if ( result.error.status === 403 || (result.error.code as string) === 'EMAIL_NOT_VERIFIED' ) { setRegistered(true) } else if ((result.error.code as string) === 'CAPTCHA_FAILED') { setError(t('register.captcha_failed')) } else { setError(result.error.message || t('common.error')) } } else { setRegistered(true) } } catch { setError(t('common.error')) } setLoading(false) } return ( <div className="min-h-screen flex items-center justify-center px-4 py-8"> <div className="w-full max-w-md"> {registered ? ( <div className="bg-white rounded-xl border border-gray-200 p-8 text-center space-y-4"> <div className="text-5xl">✉️</div> <h2 className="text-xl font-bold text-gray-900">{t('register.check_email.title')}</h2> <p className="text-gray-500 text-sm">{t('register.check_email.desc')}</p> <Link href={redirectPath !== '/dashboard' ? `/login?redirect=${encodeURIComponent(redirectPath)}` : '/login'} className="block text-green-600 hover:underline text-sm font-medium"> {t('register.check_email.login')} </Link> </div> ) : ( <> <div className="text-center mb-8"> <h1 className="text-3xl font-bold text-white">{t('register.title')}</h1> <p className="text-white/70 mt-2">{t('register.subtitle')}</p> </div> <form onSubmit={handleSubmit} className="bg-white rounded-xl border border-gray-200 p-6 space-y-4"> {error && ( <div className="bg-red-50 text-red-700 p-3 rounded-lg text-sm">{error}</div> )} <div className="grid grid-cols-2 gap-3"> <div> <label className="block text-sm font-medium text-gray-700 mb-1">{t('register.first_name')}</label> <input name="firstName" required className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400" /> </div> <div> <label className="block text-sm font-medium text-gray-700 mb-1">{t('register.last_name')}</label> <input name="lastName" required className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400" /> </div> </div> <div> <label className="block text-sm font-medium text-gray-700 mb-1">{t('register.email')}</label> <input name="email" type="email" required autoComplete="email" className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400" /> </div> <div> <label className="block text-sm font-medium text-gray-700 mb-1">{t('register.password')}</label> <input name="password" type="password" required minLength={8} autoComplete="new-password" className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400" /> </div> <TurnstileWidget onSuccess={setCaptchaToken} onError={() => setCaptchaToken('')} onExpire={() => setCaptchaToken('')} /> <button type="submit" disabled={loading || !captchaToken} className="w-full bg-green-600 text-white py-3 rounded-xl font-semibold hover:bg-green-700 disabled:opacity-50" > {loading ? t('register.submitting') : t('register.submit')} </button> {/* Divider */} <div className="flex items-center gap-3 py-1"> <div className="flex-1 h-px bg-gray-200" /> <span className="text-xs text-gray-400">{t('login.or_continue_with')}</span> <div className="flex-1 h-px bg-gray-200" /> </div> {/* Google Sign-In / Register */} <button type="button" onClick={handleGoogleRegister} disabled={googleLoading} className="w-full flex items-center justify-center gap-3 py-2.5 px-4 rounded-xl border border-gray-200 bg-white hover:bg-gray-50 disabled:opacity-50 transition font-medium text-gray-700 text-sm" > <svg width="18" height="18" viewBox="0 0 48 48" fill="none"> <path d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z" fill="#FFC107"/> <path d="M6.3 14.7l7 5.1C15 16.1 19.1 13 24 13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 16.3 2 9.7 7.4 6.3 14.7z" fill="#FF3D00"/> <path d="M24 46c5.5 0 10.5-1.9 14.4-5.1l-6.7-5.5C29.5 37 26.9 38 24 38c-6.1 0-11.2-4-13-9.5l-7 5.4C7.6 41.9 15.2 46 24 46z" fill="#4CAF50"/> <path d="M44.5 20H24v8.5h11.8c-.9 2.8-2.8 5.1-5.3 6.6l6.7 5.5C41.5 37.3 45 31.1 45 24c0-1.3-.2-2.7-.5-4z" fill="#1976D2"/> </svg> {googleLoading ? t('register.submitting') : t('register.continue_google')} </button> <p className="text-center text-sm text-gray-500"> {t('register.has_account')}{' '} <Link href={redirectPath !== '/dashboard' ? `/login?redirect=${encodeURIComponent(redirectPath)}` : '/login'} className="text-green-600 hover:underline font-medium"> {t('register.login_link')} </Link> </p> </form> </> )} </div> </div> ) } export default function RegisterPage() { return ( <Suspense fallback={null}> <RegisterForm /> </Suspense> ) }
Save
cmd:
run