RegisterForm.js 14 KB

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