/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { useEffect, useMemo, useState } from 'react' import type { z } from 'zod' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { Loader2 } from 'lucide-react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { cn } from '@/lib/utils' import { useStatus } from '@/hooks/use-status' import { Button } from '@/components/ui/button' import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog } from '@/components/dialog' import { PasswordInput } from '@/components/password-input' import { Turnstile } from '@/components/turnstile' import { register, wechatLoginByCode } from '@/features/auth/api' import { LegalConsent } from '@/features/auth/components/legal-consent' import { OAuthProviders } from '@/features/auth/components/oauth-providers' import { registerFormSchema } from '@/features/auth/constants' import { useAuthRedirect } from '@/features/auth/hooks/use-auth-redirect' import { useEmailVerification } from '@/features/auth/hooks/use-email-verification' import { useTurnstile } from '@/features/auth/hooks/use-turnstile' import { getAffiliateCode, saveAffiliateCode, } from '@/features/auth/lib/storage' export function SignUpForm({ className, ...props }: React.HTMLAttributes) { const { t } = useTranslation() const [isLoading, setIsLoading] = useState(false) const [verificationCode, setVerificationCode] = useState('') const [agreedToLegal, setAgreedToLegal] = useState(false) const [wechatCode, setWeChatCode] = useState('') const [isWeChatDialogOpen, setIsWeChatDialogOpen] = useState(false) const [isWeChatSubmitting, setIsWeChatSubmitting] = useState(false) const legalConsentErrorMessage = t('Please agree to the legal terms first') const { status } = useStatus() const { isTurnstileEnabled, turnstileSiteKey, turnstileToken, setTurnstileToken, validateTurnstile, } = useTurnstile() const { redirectToLogin, handleLoginSuccess } = useAuthRedirect() const { isSending: isSendingCode, secondsLeft, isActive, sendCode, } = useEmailVerification({ turnstileToken, validateTurnstile, }) const form = useForm>({ resolver: zodResolver(registerFormSchema), defaultValues: { username: '', email: '', password: '', confirmPassword: '', }, }) const emailValue = form.watch('email') const emailVerificationRequired = !!status?.email_verification const hasUserAgreement = Boolean(status?.user_agreement_enabled) const hasPrivacyPolicy = Boolean(status?.privacy_policy_enabled) const requiresLegalConsent = hasUserAgreement || hasPrivacyPolicy const oauthRegisterEnabled = status?.oauth_register_enabled ?? status?.data?.oauth_register_enabled ?? true const hasWeChatLogin = Boolean(status?.wechat_login) const turnstileReady = !isTurnstileEnabled || Boolean(turnstileToken) const wechatQrCodeUrl = useMemo(() => { return ( status?.wechat_qrcode || status?.wechat_qr_code || status?.wechat_qrcode_image_url || status?.wechat_qr_code_image_url || status?.wechat_account_qrcode_image_url || status?.WeChatAccountQRCodeImageURL || status?.data?.wechat_qrcode || status?.data?.WeChatAccountQRCodeImageURL || '' ) }, [status]) useEffect(() => { if (requiresLegalConsent) { setAgreedToLegal(false) } else { setAgreedToLegal(true) } }, [requiresLegalConsent]) useEffect(() => { const aff = new URLSearchParams(window.location.search).get('aff')?.trim() if (aff) { saveAffiliateCode(aff) } }, []) async function onSubmit(data: z.infer) { if (requiresLegalConsent && !agreedToLegal) { toast.error(legalConsentErrorMessage) return } // Validate email verification if required if (emailVerificationRequired) { if (!data.email) { toast.error(t('Please enter your email')) return } if (!verificationCode) { toast.error(t('Please enter the verification code')) return } } if (!validateTurnstile()) return setIsLoading(true) try { const res = await register({ username: data.username, password: data.password, email: data.email || undefined, verification_code: verificationCode || undefined, aff_code: getAffiliateCode(), turnstile: turnstileToken, }) if (res?.success) { toast.success(t('Account created! Please sign in')) redirectToLogin() } else { toast.error(res?.message || t('Failed to create account')) } } catch (_error) { // Errors are handled by global interceptor } finally { setIsLoading(false) } } async function handleSendVerificationCode() { await sendCode(emailValue || '') } const handleOpenWeChatDialog = () => { if (requiresLegalConsent && !agreedToLegal) { toast.error(legalConsentErrorMessage) return } setIsWeChatDialogOpen(true) } const handleWeChatDialogChange = (open: boolean) => { setIsWeChatDialogOpen(open) if (!open) { setWeChatCode('') setIsWeChatSubmitting(false) } } async function handleWeChatLogin() { if (!wechatCode.trim()) { toast.error(t('Please enter the verification code')) return } setIsWeChatSubmitting(true) try { const res = await wechatLoginByCode(wechatCode) if (res?.success) { await handleLoginSuccess(res.data as { id?: number } | null) toast.success(t('Signed in via WeChat')) handleWeChatDialogChange(false) } else { toast.error(res?.message || t('Login failed')) } } catch (_error) { toast.error(t('Login failed')) } finally { setIsWeChatSubmitting(false) } } return (
{/* Username Field */} ( {t('Username')} )} /> {/* Password Field */} ( {t('Password')} )} /> {/* Confirm Password Field */} ( {t('Confirm password')} )} /> {/* Email Verification Section */} {emailVerificationRequired && ( <> {/* Email Field */} ( {t('Email (required for verification)')} )} /> {/* Verification Code Field */}
setVerificationCode(e.target.value)} />
)} {/* Turnstile */} {isTurnstileEnabled && (
)} {/* Submit Button */} {oauthRegisterEnabled && ( )} {hasWeChatLogin && ( } > {wechatQrCodeUrl ? (
{t('WeChat
) : (

{t('QR code is not configured. Please contact support.')}

)}
setWeChatCode(event.target.value)} autoComplete='one-time-code' />
)} ) }