RegisterForm.jsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact [email protected]
  14. */
  15. import React, { useContext, useEffect, useRef, useState } from 'react';
  16. import { Link, useNavigate } from 'react-router-dom';
  17. import {
  18. API,
  19. getLogo,
  20. showError,
  21. showInfo,
  22. showSuccess,
  23. updateAPI,
  24. getSystemName,
  25. setUserData,
  26. } from '../../helpers';
  27. import Turnstile from 'react-turnstile';
  28. import { Button, Card, Checkbox, Divider, Form, Icon, Modal } from '@douyinfe/semi-ui';
  29. import Title from '@douyinfe/semi-ui/lib/es/typography/title';
  30. import Text from '@douyinfe/semi-ui/lib/es/typography/text';
  31. import {
  32. IconGithubLogo,
  33. IconMail,
  34. IconUser,
  35. IconLock,
  36. IconKey,
  37. } from '@douyinfe/semi-icons';
  38. import {
  39. onGitHubOAuthClicked,
  40. onLinuxDOOAuthClicked,
  41. onOIDCClicked,
  42. } from '../../helpers';
  43. import OIDCIcon from '../common/logo/OIDCIcon';
  44. import LinuxDoIcon from '../common/logo/LinuxDoIcon';
  45. import WeChatIcon from '../common/logo/WeChatIcon';
  46. import TelegramLoginButton from 'react-telegram-login/src';
  47. import { UserContext } from '../../context/User';
  48. import { useTranslation } from 'react-i18next';
  49. const RegisterForm = () => {
  50. let navigate = useNavigate();
  51. const { t } = useTranslation();
  52. const [inputs, setInputs] = useState({
  53. username: '',
  54. password: '',
  55. password2: '',
  56. email: '',
  57. verification_code: '',
  58. wechat_verification_code: '',
  59. });
  60. const { username, password, password2 } = inputs;
  61. const [userState, userDispatch] = useContext(UserContext);
  62. const [turnstileEnabled, setTurnstileEnabled] = useState(false);
  63. const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
  64. const [turnstileToken, setTurnstileToken] = useState('');
  65. const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
  66. const [showEmailRegister, setShowEmailRegister] = useState(false);
  67. const [wechatLoading, setWechatLoading] = useState(false);
  68. const [githubLoading, setGithubLoading] = useState(false);
  69. const [oidcLoading, setOidcLoading] = useState(false);
  70. const [linuxdoLoading, setLinuxdoLoading] = useState(false);
  71. const [emailRegisterLoading, setEmailRegisterLoading] = useState(false);
  72. const [registerLoading, setRegisterLoading] = useState(false);
  73. const [verificationCodeLoading, setVerificationCodeLoading] = useState(false);
  74. const [otherRegisterOptionsLoading, setOtherRegisterOptionsLoading] =
  75. useState(false);
  76. const [wechatCodeSubmitLoading, setWechatCodeSubmitLoading] = useState(false);
  77. const [disableButton, setDisableButton] = useState(false);
  78. const [countdown, setCountdown] = useState(30);
  79. const [agreedToTerms, setAgreedToTerms] = useState(false);
  80. const [hasUserAgreement, setHasUserAgreement] = useState(false);
  81. const [hasPrivacyPolicy, setHasPrivacyPolicy] = useState(false);
  82. const [githubButtonText, setGithubButtonText] = useState('使用 GitHub 继续');
  83. const [githubButtonDisabled, setGithubButtonDisabled] = useState(false);
  84. const githubTimeoutRef = useRef(null);
  85. const logo = getLogo();
  86. const systemName = getSystemName();
  87. let affCode = new URLSearchParams(window.location.search).get('aff');
  88. if (affCode) {
  89. localStorage.setItem('aff', affCode);
  90. }
  91. const [status] = useState(() => {
  92. const savedStatus = localStorage.getItem('status');
  93. return savedStatus ? JSON.parse(savedStatus) : {};
  94. });
  95. const [showEmailVerification, setShowEmailVerification] = useState(() => {
  96. return status.email_verification ?? false;
  97. });
  98. useEffect(() => {
  99. setShowEmailVerification(status.email_verification);
  100. if (status.turnstile_check) {
  101. setTurnstileEnabled(true);
  102. setTurnstileSiteKey(status.turnstile_site_key);
  103. }
  104. // 从 status 获取用户协议和隐私政策的启用状态
  105. setHasUserAgreement(status.user_agreement_enabled || false);
  106. setHasPrivacyPolicy(status.privacy_policy_enabled || false);
  107. }, [status]);
  108. useEffect(() => {
  109. let countdownInterval = null;
  110. if (disableButton && countdown > 0) {
  111. countdownInterval = setInterval(() => {
  112. setCountdown(countdown - 1);
  113. }, 1000);
  114. } else if (countdown === 0) {
  115. setDisableButton(false);
  116. setCountdown(30);
  117. }
  118. return () => clearInterval(countdownInterval); // Clean up on unmount
  119. }, [disableButton, countdown]);
  120. useEffect(() => {
  121. return () => {
  122. if (githubTimeoutRef.current) {
  123. clearTimeout(githubTimeoutRef.current);
  124. }
  125. };
  126. }, []);
  127. const onWeChatLoginClicked = () => {
  128. setWechatLoading(true);
  129. setShowWeChatLoginModal(true);
  130. setWechatLoading(false);
  131. };
  132. const onSubmitWeChatVerificationCode = async () => {
  133. if (turnstileEnabled && turnstileToken === '') {
  134. showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
  135. return;
  136. }
  137. setWechatCodeSubmitLoading(true);
  138. try {
  139. const res = await API.get(
  140. `/api/oauth/wechat?code=${inputs.wechat_verification_code}`,
  141. );
  142. const { success, message, data } = res.data;
  143. if (success) {
  144. userDispatch({ type: 'login', payload: data });
  145. localStorage.setItem('user', JSON.stringify(data));
  146. setUserData(data);
  147. updateAPI();
  148. navigate('/');
  149. showSuccess('登录成功!');
  150. setShowWeChatLoginModal(false);
  151. } else {
  152. showError(message);
  153. }
  154. } catch (error) {
  155. showError('登录失败,请重试');
  156. } finally {
  157. setWechatCodeSubmitLoading(false);
  158. }
  159. };
  160. function handleChange(name, value) {
  161. setInputs((inputs) => ({ ...inputs, [name]: value }));
  162. }
  163. async function handleSubmit(e) {
  164. if (password.length < 8) {
  165. showInfo('密码长度不得小于 8 位!');
  166. return;
  167. }
  168. if (password !== password2) {
  169. showInfo('两次输入的密码不一致');
  170. return;
  171. }
  172. if (username && password) {
  173. if (turnstileEnabled && turnstileToken === '') {
  174. showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
  175. return;
  176. }
  177. setRegisterLoading(true);
  178. try {
  179. if (!affCode) {
  180. affCode = localStorage.getItem('aff');
  181. }
  182. inputs.aff_code = affCode;
  183. const res = await API.post(
  184. `/api/user/register?turnstile=${turnstileToken}`,
  185. inputs,
  186. );
  187. const { success, message } = res.data;
  188. if (success) {
  189. navigate('/login');
  190. showSuccess('注册成功!');
  191. } else {
  192. showError(message);
  193. }
  194. } catch (error) {
  195. showError('注册失败,请重试');
  196. } finally {
  197. setRegisterLoading(false);
  198. }
  199. }
  200. }
  201. const sendVerificationCode = async () => {
  202. if (inputs.email === '') return;
  203. if (turnstileEnabled && turnstileToken === '') {
  204. showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
  205. return;
  206. }
  207. setVerificationCodeLoading(true);
  208. try {
  209. const res = await API.get(
  210. `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`,
  211. );
  212. const { success, message } = res.data;
  213. if (success) {
  214. showSuccess('验证码发送成功,请检查你的邮箱!');
  215. setDisableButton(true); // 发送成功后禁用按钮,开始倒计时
  216. } else {
  217. showError(message);
  218. }
  219. } catch (error) {
  220. showError('发送验证码失败,请重试');
  221. } finally {
  222. setVerificationCodeLoading(false);
  223. }
  224. };
  225. const handleGitHubClick = () => {
  226. if (githubButtonDisabled) {
  227. return;
  228. }
  229. setGithubLoading(true);
  230. setGithubButtonDisabled(true);
  231. setGithubButtonText(t('正在跳转 GitHub...'));
  232. if (githubTimeoutRef.current) {
  233. clearTimeout(githubTimeoutRef.current);
  234. }
  235. githubTimeoutRef.current = setTimeout(() => {
  236. setGithubLoading(false);
  237. setGithubButtonText(t('请求超时,请刷新页面后重新发起 GitHub 登录'));
  238. setGithubButtonDisabled(true);
  239. }, 20000);
  240. try {
  241. onGitHubOAuthClicked(status.github_client_id);
  242. } finally {
  243. setTimeout(() => setGithubLoading(false), 3000);
  244. }
  245. };
  246. const handleOIDCClick = () => {
  247. setOidcLoading(true);
  248. try {
  249. onOIDCClicked(status.oidc_authorization_endpoint, status.oidc_client_id);
  250. } finally {
  251. setTimeout(() => setOidcLoading(false), 3000);
  252. }
  253. };
  254. const handleLinuxDOClick = () => {
  255. setLinuxdoLoading(true);
  256. try {
  257. onLinuxDOOAuthClicked(status.linuxdo_client_id);
  258. } finally {
  259. setTimeout(() => setLinuxdoLoading(false), 3000);
  260. }
  261. };
  262. const handleEmailRegisterClick = () => {
  263. setEmailRegisterLoading(true);
  264. setShowEmailRegister(true);
  265. setEmailRegisterLoading(false);
  266. };
  267. const handleOtherRegisterOptionsClick = () => {
  268. setOtherRegisterOptionsLoading(true);
  269. setShowEmailRegister(false);
  270. setOtherRegisterOptionsLoading(false);
  271. };
  272. const onTelegramLoginClicked = async (response) => {
  273. const fields = [
  274. 'id',
  275. 'first_name',
  276. 'last_name',
  277. 'username',
  278. 'photo_url',
  279. 'auth_date',
  280. 'hash',
  281. 'lang',
  282. ];
  283. const params = {};
  284. fields.forEach((field) => {
  285. if (response[field]) {
  286. params[field] = response[field];
  287. }
  288. });
  289. try {
  290. const res = await API.get(`/api/oauth/telegram/login`, { params });
  291. const { success, message, data } = res.data;
  292. if (success) {
  293. userDispatch({ type: 'login', payload: data });
  294. localStorage.setItem('user', JSON.stringify(data));
  295. showSuccess('登录成功!');
  296. setUserData(data);
  297. updateAPI();
  298. navigate('/');
  299. } else {
  300. showError(message);
  301. }
  302. } catch (error) {
  303. showError('登录失败,请重试');
  304. }
  305. };
  306. const renderOAuthOptions = () => {
  307. return (
  308. <div className='flex flex-col items-center'>
  309. <div className='w-full max-w-md'>
  310. <div className='flex items-center justify-center mb-6 gap-2'>
  311. <img src={logo} alt='Logo' className='h-10 rounded-full' />
  312. <Title heading={3} className='!text-gray-800'>
  313. {systemName}
  314. </Title>
  315. </div>
  316. <Card className='border-0 !rounded-2xl overflow-hidden'>
  317. <div className='flex justify-center pt-6 pb-2'>
  318. <Title heading={3} className='text-gray-800 dark:text-gray-200'>
  319. {t('注 册')}
  320. </Title>
  321. </div>
  322. <div className='px-2 py-8'>
  323. <div className='space-y-3'>
  324. {status.wechat_login && (
  325. <Button
  326. theme='outline'
  327. className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
  328. type='tertiary'
  329. icon={
  330. <Icon svg={<WeChatIcon />} style={{ color: '#07C160' }} />
  331. }
  332. onClick={onWeChatLoginClicked}
  333. loading={wechatLoading}
  334. >
  335. <span className='ml-3'>{t('使用 微信 继续')}</span>
  336. </Button>
  337. )}
  338. {status.github_oauth && (
  339. <Button
  340. theme='outline'
  341. className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
  342. type='tertiary'
  343. icon={<IconGithubLogo size='large' />}
  344. onClick={handleGitHubClick}
  345. loading={githubLoading}
  346. disabled={githubButtonDisabled}
  347. >
  348. <span className='ml-3'>{githubButtonText}</span>
  349. </Button>
  350. )}
  351. {status.oidc_enabled && (
  352. <Button
  353. theme='outline'
  354. className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
  355. type='tertiary'
  356. icon={<OIDCIcon style={{ color: '#1877F2' }} />}
  357. onClick={handleOIDCClick}
  358. loading={oidcLoading}
  359. >
  360. <span className='ml-3'>{t('使用 OIDC 继续')}</span>
  361. </Button>
  362. )}
  363. {status.linuxdo_oauth && (
  364. <Button
  365. theme='outline'
  366. className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
  367. type='tertiary'
  368. icon={
  369. <LinuxDoIcon
  370. style={{
  371. color: '#E95420',
  372. width: '20px',
  373. height: '20px',
  374. }}
  375. />
  376. }
  377. onClick={handleLinuxDOClick}
  378. loading={linuxdoLoading}
  379. >
  380. <span className='ml-3'>{t('使用 LinuxDO 继续')}</span>
  381. </Button>
  382. )}
  383. {status.telegram_oauth && (
  384. <div className='flex justify-center my-2'>
  385. <TelegramLoginButton
  386. dataOnauth={onTelegramLoginClicked}
  387. botName={status.telegram_bot_name}
  388. />
  389. </div>
  390. )}
  391. <Divider margin='12px' align='center'>
  392. {t('或')}
  393. </Divider>
  394. <Button
  395. theme='solid'
  396. type='primary'
  397. className='w-full h-12 flex items-center justify-center bg-black text-white !rounded-full hover:bg-gray-800 transition-colors'
  398. icon={<IconMail size='large' />}
  399. onClick={handleEmailRegisterClick}
  400. loading={emailRegisterLoading}
  401. >
  402. <span className='ml-3'>{t('使用 用户名 注册')}</span>
  403. </Button>
  404. </div>
  405. <div className='mt-6 text-center text-sm'>
  406. <Text>
  407. {t('已有账户?')}{' '}
  408. <Link
  409. to='/login'
  410. className='text-blue-600 hover:text-blue-800 font-medium'
  411. >
  412. {t('登录')}
  413. </Link>
  414. </Text>
  415. </div>
  416. </div>
  417. </Card>
  418. </div>
  419. </div>
  420. );
  421. };
  422. const renderEmailRegisterForm = () => {
  423. return (
  424. <div className='flex flex-col items-center'>
  425. <div className='w-full max-w-md'>
  426. <div className='flex items-center justify-center mb-6 gap-2'>
  427. <img src={logo} alt='Logo' className='h-10 rounded-full' />
  428. <Title heading={3} className='!text-gray-800'>
  429. {systemName}
  430. </Title>
  431. </div>
  432. <Card className='border-0 !rounded-2xl overflow-hidden'>
  433. <div className='flex justify-center pt-6 pb-2'>
  434. <Title heading={3} className='text-gray-800 dark:text-gray-200'>
  435. {t('注 册')}
  436. </Title>
  437. </div>
  438. <div className='px-2 py-8'>
  439. <Form className='space-y-3'>
  440. <Form.Input
  441. field='username'
  442. label={t('用户名')}
  443. placeholder={t('请输入用户名')}
  444. name='username'
  445. onChange={(value) => handleChange('username', value)}
  446. prefix={<IconUser />}
  447. />
  448. <Form.Input
  449. field='password'
  450. label={t('密码')}
  451. placeholder={t('输入密码,最短 8 位,最长 20 位')}
  452. name='password'
  453. mode='password'
  454. onChange={(value) => handleChange('password', value)}
  455. prefix={<IconLock />}
  456. />
  457. <Form.Input
  458. field='password2'
  459. label={t('确认密码')}
  460. placeholder={t('确认密码')}
  461. name='password2'
  462. mode='password'
  463. onChange={(value) => handleChange('password2', value)}
  464. prefix={<IconLock />}
  465. />
  466. {showEmailVerification && (
  467. <>
  468. <Form.Input
  469. field='email'
  470. label={t('邮箱')}
  471. placeholder={t('输入邮箱地址')}
  472. name='email'
  473. type='email'
  474. onChange={(value) => handleChange('email', value)}
  475. prefix={<IconMail />}
  476. suffix={
  477. <Button
  478. onClick={sendVerificationCode}
  479. loading={verificationCodeLoading}
  480. disabled={disableButton || verificationCodeLoading}
  481. >
  482. {disableButton
  483. ? `${t('重新发送')} (${countdown})`
  484. : t('获取验证码')}
  485. </Button>
  486. }
  487. />
  488. <Form.Input
  489. field='verification_code'
  490. label={t('验证码')}
  491. placeholder={t('输入验证码')}
  492. name='verification_code'
  493. onChange={(value) =>
  494. handleChange('verification_code', value)
  495. }
  496. prefix={<IconKey />}
  497. />
  498. </>
  499. )}
  500. {(hasUserAgreement || hasPrivacyPolicy) && (
  501. <div className='pt-4'>
  502. <Checkbox
  503. checked={agreedToTerms}
  504. onChange={(e) => setAgreedToTerms(e.target.checked)}
  505. >
  506. <Text size='small' className='text-gray-600'>
  507. {t('我已阅读并同意')}
  508. {hasUserAgreement && (
  509. <>
  510. <a
  511. href='/user-agreement'
  512. target='_blank'
  513. rel='noopener noreferrer'
  514. className='text-blue-600 hover:text-blue-800 mx-1'
  515. >
  516. {t('用户协议')}
  517. </a>
  518. </>
  519. )}
  520. {hasUserAgreement && hasPrivacyPolicy && t('和')}
  521. {hasPrivacyPolicy && (
  522. <>
  523. <a
  524. href='/privacy-policy'
  525. target='_blank'
  526. rel='noopener noreferrer'
  527. className='text-blue-600 hover:text-blue-800 mx-1'
  528. >
  529. {t('隐私政策')}
  530. </a>
  531. </>
  532. )}
  533. </Text>
  534. </Checkbox>
  535. </div>
  536. )}
  537. <div className='space-y-2 pt-2'>
  538. <Button
  539. theme='solid'
  540. className='w-full !rounded-full'
  541. type='primary'
  542. htmlType='submit'
  543. onClick={handleSubmit}
  544. loading={registerLoading}
  545. disabled={(hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms}
  546. >
  547. {t('注册')}
  548. </Button>
  549. </div>
  550. </Form>
  551. {(status.github_oauth ||
  552. status.oidc_enabled ||
  553. status.wechat_login ||
  554. status.linuxdo_oauth ||
  555. status.telegram_oauth) && (
  556. <>
  557. <Divider margin='12px' align='center'>
  558. {t('或')}
  559. </Divider>
  560. <div className='mt-4 text-center'>
  561. <Button
  562. theme='outline'
  563. type='tertiary'
  564. className='w-full !rounded-full'
  565. onClick={handleOtherRegisterOptionsClick}
  566. loading={otherRegisterOptionsLoading}
  567. >
  568. {t('其他注册选项')}
  569. </Button>
  570. </div>
  571. </>
  572. )}
  573. <div className='mt-6 text-center text-sm'>
  574. <Text>
  575. {t('已有账户?')}{' '}
  576. <Link
  577. to='/login'
  578. className='text-blue-600 hover:text-blue-800 font-medium'
  579. >
  580. {t('登录')}
  581. </Link>
  582. </Text>
  583. </div>
  584. </div>
  585. </Card>
  586. </div>
  587. </div>
  588. );
  589. };
  590. const renderWeChatLoginModal = () => {
  591. return (
  592. <Modal
  593. title={t('微信扫码登录')}
  594. visible={showWeChatLoginModal}
  595. maskClosable={true}
  596. onOk={onSubmitWeChatVerificationCode}
  597. onCancel={() => setShowWeChatLoginModal(false)}
  598. okText={t('登录')}
  599. centered={true}
  600. okButtonProps={{
  601. loading: wechatCodeSubmitLoading,
  602. }}
  603. >
  604. <div className='flex flex-col items-center'>
  605. <img src={status.wechat_qrcode} alt='微信二维码' className='mb-4' />
  606. </div>
  607. <div className='text-center mb-4'>
  608. <p>
  609. {t('微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)')}
  610. </p>
  611. </div>
  612. <Form>
  613. <Form.Input
  614. field='wechat_verification_code'
  615. placeholder={t('验证码')}
  616. label={t('验证码')}
  617. value={inputs.wechat_verification_code}
  618. onChange={(value) =>
  619. handleChange('wechat_verification_code', value)
  620. }
  621. />
  622. </Form>
  623. </Modal>
  624. );
  625. };
  626. return (
  627. <div className='relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
  628. {/* 背景模糊晕染球 */}
  629. <div
  630. className='blur-ball blur-ball-indigo'
  631. style={{ top: '-80px', right: '-80px', transform: 'none' }}
  632. />
  633. <div
  634. className='blur-ball blur-ball-teal'
  635. style={{ top: '50%', left: '-120px' }}
  636. />
  637. <div className='w-full max-w-sm mt-[60px]'>
  638. {showEmailRegister ||
  639. !(
  640. status.github_oauth ||
  641. status.oidc_enabled ||
  642. status.wechat_login ||
  643. status.linuxdo_oauth ||
  644. status.telegram_oauth
  645. )
  646. ? renderEmailRegisterForm()
  647. : renderOAuthOptions()}
  648. {renderWeChatLoginModal()}
  649. {turnstileEnabled && (
  650. <div className='flex justify-center mt-6'>
  651. <Turnstile
  652. sitekey={turnstileSiteKey}
  653. onVerify={(token) => {
  654. setTurnstileToken(token);
  655. }}
  656. />
  657. </div>
  658. )}
  659. </div>
  660. </div>
  661. );
  662. };
  663. export default RegisterForm;