RegisterForm.js 13 KB

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