RegisterForm.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. import React, { useContext, useEffect, useState } from 'react';
  2. import { Link, useNavigate } from 'react-router-dom';
  3. import {
  4. API,
  5. getLogo,
  6. showError,
  7. showInfo,
  8. showSuccess,
  9. updateAPI,
  10. } from '../helpers';
  11. import Turnstile from 'react-turnstile';
  12. import {
  13. Button,
  14. Card,
  15. Divider,
  16. Form,
  17. Icon,
  18. Layout,
  19. Modal,
  20. } from '@douyinfe/semi-ui';
  21. import Title from '@douyinfe/semi-ui/lib/es/typography/title';
  22. import Text from '@douyinfe/semi-ui/lib/es/typography/text';
  23. import { IconGithubLogo } from '@douyinfe/semi-icons';
  24. import {
  25. onGitHubOAuthClicked,
  26. onLinuxDOOAuthClicked,
  27. onOIDCClicked,
  28. } from './utils.js';
  29. import OIDCIcon from './OIDCIcon.js';
  30. import LinuxDoIcon from './LinuxDoIcon.js';
  31. import WeChatIcon from './WeChatIcon.js';
  32. import TelegramLoginButton from 'react-telegram-login/src';
  33. import { setUserData } from '../helpers/data.js';
  34. import { UserContext } from '../context/User/index.js';
  35. import { useTranslation } from 'react-i18next';
  36. const RegisterForm = () => {
  37. const { t } = useTranslation();
  38. const [inputs, setInputs] = useState({
  39. username: '',
  40. password: '',
  41. password2: '',
  42. email: '',
  43. verification_code: '',
  44. });
  45. const { username, password, password2 } = inputs;
  46. const [showEmailVerification, setShowEmailVerification] = useState(false);
  47. const [userState, userDispatch] = useContext(UserContext);
  48. const [turnstileEnabled, setTurnstileEnabled] = useState(false);
  49. const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
  50. const [turnstileToken, setTurnstileToken] = useState('');
  51. const [loading, setLoading] = useState(false);
  52. const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
  53. const [status, setStatus] = useState({});
  54. let navigate = useNavigate();
  55. const logo = getLogo();
  56. let affCode = new URLSearchParams(window.location.search).get('aff');
  57. if (affCode) {
  58. localStorage.setItem('aff', affCode);
  59. }
  60. useEffect(() => {
  61. let status = localStorage.getItem('status');
  62. if (status) {
  63. status = JSON.parse(status);
  64. setStatus(status);
  65. setShowEmailVerification(status.email_verification);
  66. if (status.turnstile_check) {
  67. setTurnstileEnabled(true);
  68. setTurnstileSiteKey(status.turnstile_site_key);
  69. }
  70. }
  71. });
  72. const onWeChatLoginClicked = () => {
  73. setShowWeChatLoginModal(true);
  74. };
  75. const onSubmitWeChatVerificationCode = async () => {
  76. if (turnstileEnabled && turnstileToken === '') {
  77. showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
  78. return;
  79. }
  80. const res = await API.get(
  81. `/api/oauth/wechat?code=${inputs.wechat_verification_code}`,
  82. );
  83. const { success, message, data } = res.data;
  84. if (success) {
  85. userDispatch({ type: 'login', payload: data });
  86. localStorage.setItem('user', JSON.stringify(data));
  87. setUserData(data);
  88. updateAPI();
  89. navigate('/');
  90. showSuccess('登录成功!');
  91. setShowWeChatLoginModal(false);
  92. } else {
  93. showError(message);
  94. }
  95. };
  96. function handleChange(name, value) {
  97. setInputs((inputs) => ({ ...inputs, [name]: value }));
  98. }
  99. async function handleSubmit(e) {
  100. if (password.length < 8) {
  101. showInfo('密码长度不得小于 8 位!');
  102. return;
  103. }
  104. if (password !== password2) {
  105. showInfo('两次输入的密码不一致');
  106. return;
  107. }
  108. if (username && password) {
  109. if (turnstileEnabled && turnstileToken === '') {
  110. showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
  111. return;
  112. }
  113. setLoading(true);
  114. if (!affCode) {
  115. affCode = localStorage.getItem('aff');
  116. }
  117. inputs.aff_code = affCode;
  118. const res = await API.post(
  119. `/api/user/register?turnstile=${turnstileToken}`,
  120. inputs,
  121. );
  122. const { success, message } = res.data;
  123. if (success) {
  124. navigate('/login');
  125. showSuccess('注册成功!');
  126. } else {
  127. showError(message);
  128. }
  129. setLoading(false);
  130. }
  131. }
  132. const sendVerificationCode = async () => {
  133. if (inputs.email === '') return;
  134. if (turnstileEnabled && turnstileToken === '') {
  135. showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
  136. return;
  137. }
  138. setLoading(true);
  139. const res = await API.get(
  140. `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`,
  141. );
  142. const { success, message } = res.data;
  143. if (success) {
  144. showSuccess('验证码发送成功,请检查你的邮箱!');
  145. } else {
  146. showError(message);
  147. }
  148. setLoading(false);
  149. };
  150. const onTelegramLoginClicked = async (response) => {
  151. const fields = [
  152. 'id',
  153. 'first_name',
  154. 'last_name',
  155. 'username',
  156. 'photo_url',
  157. 'auth_date',
  158. 'hash',
  159. 'lang',
  160. ];
  161. const params = {};
  162. fields.forEach((field) => {
  163. if (response[field]) {
  164. params[field] = response[field];
  165. }
  166. });
  167. const res = await API.get(`/api/oauth/telegram/login`, { params });
  168. const { success, message, data } = res.data;
  169. if (success) {
  170. userDispatch({ type: 'login', payload: data });
  171. localStorage.setItem('user', JSON.stringify(data));
  172. showSuccess('登录成功!');
  173. setUserData(data);
  174. updateAPI();
  175. navigate('/');
  176. } else {
  177. showError(message);
  178. }
  179. };
  180. return (
  181. <div>
  182. <Layout>
  183. <Layout.Header></Layout.Header>
  184. <Layout.Content>
  185. <div
  186. style={{
  187. justifyContent: 'center',
  188. display: 'flex',
  189. marginTop: 120,
  190. }}
  191. >
  192. <div style={{ width: 500 }}>
  193. <Card>
  194. <Title heading={2} style={{ textAlign: 'center' }}>
  195. {t('新用户注册')}
  196. </Title>
  197. <Form size='large'>
  198. <Form.Input
  199. field={'username'}
  200. label={t('用户名')}
  201. placeholder={t('用户名')}
  202. name='username'
  203. onChange={(value) => handleChange('username', value)}
  204. />
  205. <Form.Input
  206. field={'password'}
  207. label={t('密码')}
  208. placeholder={t('输入密码,最短 8 位,最长 20 位')}
  209. name='password'
  210. type='password'
  211. onChange={(value) => handleChange('password', value)}
  212. />
  213. <Form.Input
  214. field={'password2'}
  215. label={t('确认密码')}
  216. placeholder={t('确认密码')}
  217. name='password2'
  218. type='password'
  219. onChange={(value) => handleChange('password2', value)}
  220. />
  221. {showEmailVerification ? (
  222. <>
  223. <Form.Input
  224. field={'email'}
  225. label={t('邮箱')}
  226. placeholder={t('输入邮箱地址')}
  227. onChange={(value) => handleChange('email', value)}
  228. name='email'
  229. type='email'
  230. suffix={
  231. <Button
  232. onClick={sendVerificationCode}
  233. disabled={loading}
  234. >
  235. {t('获取验证码')}
  236. </Button>
  237. }
  238. />
  239. <Form.Input
  240. field={'verification_code'}
  241. label={t('验证码')}
  242. placeholder={t('输入验证码')}
  243. onChange={(value) =>
  244. handleChange('verification_code', value)
  245. }
  246. name='verification_code'
  247. />
  248. </>
  249. ) : (
  250. <></>
  251. )}
  252. <Button
  253. theme='solid'
  254. style={{ width: '100%' }}
  255. type={'primary'}
  256. size='large'
  257. htmlType={'submit'}
  258. onClick={handleSubmit}
  259. >
  260. {t('注册')}
  261. </Button>
  262. </Form>
  263. <div
  264. style={{
  265. display: 'flex',
  266. justifyContent: 'space-between',
  267. marginTop: 20,
  268. }}
  269. >
  270. <Text>
  271. {t('已有账户?')}
  272. <Link to='/login'>{t('点击登录')}</Link>
  273. </Text>
  274. </div>
  275. {status.github_oauth ||
  276. status.oidc_enabled ||
  277. status.wechat_login ||
  278. status.telegram_oauth ||
  279. status.linuxdo_oauth ? (
  280. <>
  281. <Divider margin='12px' align='center'>
  282. {t('第三方登录')}
  283. </Divider>
  284. <div
  285. style={{
  286. display: 'flex',
  287. justifyContent: 'center',
  288. marginTop: 20,
  289. }}
  290. >
  291. {status.github_oauth ? (
  292. <Button
  293. type='primary'
  294. icon={<IconGithubLogo />}
  295. onClick={() =>
  296. onGitHubOAuthClicked(status.github_client_id)
  297. }
  298. />
  299. ) : (
  300. <></>
  301. )}
  302. {status.oidc_enabled ? (
  303. <Button
  304. type='primary'
  305. icon={<OIDCIcon />}
  306. onClick={() =>
  307. onOIDCClicked(
  308. status.oidc_authorization_endpoint,
  309. status.oidc_client_id,
  310. )
  311. }
  312. />
  313. ) : (
  314. <></>
  315. )}
  316. {status.linuxdo_oauth ? (
  317. <Button
  318. icon={<LinuxDoIcon />}
  319. onClick={() =>
  320. onLinuxDOOAuthClicked(status.linuxdo_client_id)
  321. }
  322. />
  323. ) : (
  324. <></>
  325. )}
  326. {status.wechat_login ? (
  327. <Button
  328. type='primary'
  329. style={{ color: 'rgba(var(--semi-green-5), 1)' }}
  330. icon={<Icon svg={<WeChatIcon />} />}
  331. onClick={onWeChatLoginClicked}
  332. />
  333. ) : (
  334. <></>
  335. )}
  336. </div>
  337. {status.telegram_oauth ? (
  338. <>
  339. <div
  340. style={{
  341. display: 'flex',
  342. justifyContent: 'center',
  343. marginTop: 5,
  344. }}
  345. >
  346. <TelegramLoginButton
  347. dataOnauth={onTelegramLoginClicked}
  348. botName={status.telegram_bot_name}
  349. />
  350. </div>
  351. </>
  352. ) : (
  353. <></>
  354. )}
  355. </>
  356. ) : (
  357. <></>
  358. )}
  359. </Card>
  360. <Modal
  361. title={t('微信扫码登录')}
  362. visible={showWeChatLoginModal}
  363. maskClosable={true}
  364. onOk={onSubmitWeChatVerificationCode}
  365. onCancel={() => setShowWeChatLoginModal(false)}
  366. okText={t('登录')}
  367. size={'small'}
  368. centered={true}
  369. >
  370. <div
  371. style={{
  372. display: 'flex',
  373. alignItem: 'center',
  374. flexDirection: 'column',
  375. }}
  376. >
  377. <img src={status.wechat_qrcode} />
  378. </div>
  379. <div style={{ textAlign: 'center' }}>
  380. <p>
  381. {t(
  382. '微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)',
  383. )}
  384. </p>
  385. </div>
  386. <Form size='large'>
  387. <Form.Input
  388. field={'wechat_verification_code'}
  389. placeholder={t('验证码')}
  390. label={t('验证码')}
  391. value={inputs.wechat_verification_code}
  392. onChange={(value) =>
  393. handleChange('wechat_verification_code', value)
  394. }
  395. />
  396. </Form>
  397. </Modal>
  398. {turnstileEnabled ? (
  399. <div
  400. style={{
  401. display: 'flex',
  402. justifyContent: 'center',
  403. marginTop: 20,
  404. }}
  405. >
  406. <Turnstile
  407. sitekey={turnstileSiteKey}
  408. onVerify={(token) => {
  409. setTurnstileToken(token);
  410. }}
  411. />
  412. </div>
  413. ) : (
  414. <></>
  415. )}
  416. </div>
  417. </div>
  418. </Layout.Content>
  419. </Layout>
  420. </div>
  421. );
  422. };
  423. export default RegisterForm;