PersonalSetting.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. import React, {useContext, useEffect, useState} from 'react';
  2. import {useNavigate} from 'react-router-dom';
  3. import {
  4. API,
  5. copy,
  6. isRoot,
  7. showError,
  8. showInfo,
  9. showSuccess,
  10. } from '../helpers';
  11. import Turnstile from 'react-turnstile';
  12. import {UserContext} from '../context/User';
  13. import {onGitHubOAuthClicked, onLinuxDOOAuthClicked} from './utils';
  14. import {
  15. Avatar,
  16. Banner,
  17. Button,
  18. Card,
  19. Descriptions,
  20. Image,
  21. Input,
  22. InputNumber,
  23. Layout,
  24. Modal,
  25. Space,
  26. Tag,
  27. Typography,
  28. Collapsible,
  29. } from '@douyinfe/semi-ui';
  30. import {
  31. getQuotaPerUnit,
  32. renderQuota,
  33. renderQuotaWithPrompt,
  34. stringToColor,
  35. } from '../helpers/render';
  36. import TelegramLoginButton from 'react-telegram-login';
  37. import { useTranslation } from 'react-i18next';
  38. const PersonalSetting = () => {
  39. const [userState, userDispatch] = useContext(UserContext);
  40. let navigate = useNavigate();
  41. const { t } = useTranslation();
  42. const [inputs, setInputs] = useState({
  43. wechat_verification_code: '',
  44. email_verification_code: '',
  45. email: '',
  46. self_account_deletion_confirmation: '',
  47. set_new_password: '',
  48. set_new_password_confirmation: '',
  49. });
  50. const [status, setStatus] = useState({});
  51. const [showChangePasswordModal, setShowChangePasswordModal] = useState(false);
  52. const [showWeChatBindModal, setShowWeChatBindModal] = useState(false);
  53. const [showEmailBindModal, setShowEmailBindModal] = useState(false);
  54. const [showAccountDeleteModal, setShowAccountDeleteModal] = useState(false);
  55. const [turnstileEnabled, setTurnstileEnabled] = useState(false);
  56. const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
  57. const [turnstileToken, setTurnstileToken] = useState('');
  58. const [loading, setLoading] = useState(false);
  59. const [disableButton, setDisableButton] = useState(false);
  60. const [countdown, setCountdown] = useState(30);
  61. const [affLink, setAffLink] = useState('');
  62. const [systemToken, setSystemToken] = useState('');
  63. const [models, setModels] = useState([]);
  64. const [openTransfer, setOpenTransfer] = useState(false);
  65. const [transferAmount, setTransferAmount] = useState(0);
  66. const [isModelsExpanded, setIsModelsExpanded] = useState(false);
  67. const MODELS_DISPLAY_COUNT = 10; // 默认显示的模型数量
  68. useEffect(() => {
  69. // let user = localStorage.getItem('user');
  70. // if (user) {
  71. // userDispatch({ type: 'login', payload: user });
  72. // }
  73. // console.log(localStorage.getItem('user'))
  74. let status = localStorage.getItem('status');
  75. if (status) {
  76. status = JSON.parse(status);
  77. setStatus(status);
  78. if (status.turnstile_check) {
  79. setTurnstileEnabled(true);
  80. setTurnstileSiteKey(status.turnstile_site_key);
  81. }
  82. }
  83. getUserData().then((res) => {
  84. console.log(userState);
  85. });
  86. loadModels().then();
  87. getAffLink().then();
  88. setTransferAmount(getQuotaPerUnit());
  89. }, []);
  90. useEffect(() => {
  91. let countdownInterval = null;
  92. if (disableButton && countdown > 0) {
  93. countdownInterval = setInterval(() => {
  94. setCountdown(countdown - 1);
  95. }, 1000);
  96. } else if (countdown === 0) {
  97. setDisableButton(false);
  98. setCountdown(30);
  99. }
  100. return () => clearInterval(countdownInterval); // Clean up on unmount
  101. }, [disableButton, countdown]);
  102. const handleInputChange = (name, value) => {
  103. setInputs((inputs) => ({...inputs, [name]: value}));
  104. };
  105. const generateAccessToken = async () => {
  106. const res = await API.get('/api/user/token');
  107. const {success, message, data} = res.data;
  108. if (success) {
  109. setSystemToken(data);
  110. await copy(data);
  111. showSuccess(t('令牌已重置并已复制到剪贴板'));
  112. } else {
  113. showError(message);
  114. }
  115. };
  116. const getAffLink = async () => {
  117. const res = await API.get('/api/user/aff');
  118. const {success, message, data} = res.data;
  119. if (success) {
  120. let link = `${window.location.origin}/register?aff=${data}`;
  121. setAffLink(link);
  122. } else {
  123. showError(message);
  124. }
  125. };
  126. const getUserData = async () => {
  127. let res = await API.get(`/api/user/self`);
  128. const {success, message, data} = res.data;
  129. if (success) {
  130. userDispatch({type: 'login', payload: data});
  131. } else {
  132. showError(message);
  133. }
  134. };
  135. const loadModels = async () => {
  136. let res = await API.get(`/api/user/models`);
  137. const {success, message, data} = res.data;
  138. if (success) {
  139. setModels(data);
  140. console.log(data);
  141. } else {
  142. showError(message);
  143. }
  144. };
  145. const handleAffLinkClick = async (e) => {
  146. e.target.select();
  147. await copy(e.target.value);
  148. showSuccess(t('邀请链接已复制到剪切板'));
  149. };
  150. const handleSystemTokenClick = async (e) => {
  151. e.target.select();
  152. await copy(e.target.value);
  153. showSuccess(t('系统令牌已复制到剪切板'));
  154. };
  155. const deleteAccount = async () => {
  156. if (inputs.self_account_deletion_confirmation !== userState.user.username) {
  157. showError(t('请输入你的账户名以确认删除!'));
  158. return;
  159. }
  160. const res = await API.delete('/api/user/self');
  161. const {success, message} = res.data;
  162. if (success) {
  163. showSuccess(t('账户已删除!'));
  164. await API.get('/api/user/logout');
  165. userDispatch({type: 'logout'});
  166. localStorage.removeItem('user');
  167. navigate('/login');
  168. } else {
  169. showError(message);
  170. }
  171. };
  172. const bindWeChat = async () => {
  173. if (inputs.wechat_verification_code === '') return;
  174. const res = await API.get(
  175. `/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}`,
  176. );
  177. const {success, message} = res.data;
  178. if (success) {
  179. showSuccess(t('微信账户绑定成功!'));
  180. setShowWeChatBindModal(false);
  181. } else {
  182. showError(message);
  183. }
  184. };
  185. const changePassword = async () => {
  186. if (inputs.set_new_password !== inputs.set_new_password_confirmation) {
  187. showError(t('两次输入的密码不一致!'));
  188. return;
  189. }
  190. const res = await API.put(`/api/user/self`, {
  191. password: inputs.set_new_password,
  192. });
  193. const {success, message} = res.data;
  194. if (success) {
  195. showSuccess(t('密码修改成功!'));
  196. setShowWeChatBindModal(false);
  197. } else {
  198. showError(message);
  199. }
  200. setShowChangePasswordModal(false);
  201. };
  202. const transfer = async () => {
  203. if (transferAmount < getQuotaPerUnit()) {
  204. showError(t('划转金额最低为') + ' ' + renderQuota(getQuotaPerUnit()));
  205. return;
  206. }
  207. const res = await API.post(`/api/user/aff_transfer`, {
  208. quota: transferAmount,
  209. });
  210. const {success, message} = res.data;
  211. if (success) {
  212. showSuccess(message);
  213. setOpenTransfer(false);
  214. getUserData().then();
  215. } else {
  216. showError(message);
  217. }
  218. };
  219. const sendVerificationCode = async () => {
  220. if (inputs.email === '') {
  221. showError(t('请输入邮箱!'));
  222. return;
  223. }
  224. setDisableButton(true);
  225. if (turnstileEnabled && turnstileToken === '') {
  226. showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
  227. return;
  228. }
  229. setLoading(true);
  230. const res = await API.get(
  231. `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`,
  232. );
  233. const {success, message} = res.data;
  234. if (success) {
  235. showSuccess(t('验证码发送成功,请检查邮箱!'));
  236. } else {
  237. showError(message);
  238. }
  239. setLoading(false);
  240. };
  241. const bindEmail = async () => {
  242. if (inputs.email_verification_code === '') {
  243. showError(t('请输入邮箱验证码!'));
  244. return;
  245. }
  246. setLoading(true);
  247. const res = await API.get(
  248. `/api/oauth/email/bind?email=${inputs.email}&code=${inputs.email_verification_code}`,
  249. );
  250. const {success, message} = res.data;
  251. if (success) {
  252. showSuccess(t('邮箱账户绑定成功!'));
  253. setShowEmailBindModal(false);
  254. userState.user.email = inputs.email;
  255. } else {
  256. showError(message);
  257. }
  258. setLoading(false);
  259. };
  260. const getUsername = () => {
  261. if (userState.user) {
  262. return userState.user.username;
  263. } else {
  264. return 'null';
  265. }
  266. };
  267. const handleCancel = () => {
  268. setOpenTransfer(false);
  269. };
  270. const copyText = async (text) => {
  271. if (await copy(text)) {
  272. showSuccess(t('已复制:') + text);
  273. } else {
  274. // setSearchKeyword(text);
  275. Modal.error({title: t('无法复制到剪贴板,请手动复制'), content: text});
  276. }
  277. };
  278. return (
  279. <div>
  280. <Layout>
  281. <Layout.Content>
  282. <Modal
  283. title={t('请输入要划转的数量')}
  284. visible={openTransfer}
  285. onOk={transfer}
  286. onCancel={handleCancel}
  287. maskClosable={false}
  288. size={'small'}
  289. centered={true}
  290. >
  291. <div style={{marginTop: 20}}>
  292. <Typography.Text>{t('可用额度')}{renderQuotaWithPrompt(userState?.user?.aff_quota)}</Typography.Text>
  293. <Input
  294. style={{marginTop: 5}}
  295. value={userState?.user?.aff_quota}
  296. disabled={true}
  297. ></Input>
  298. </div>
  299. <div style={{marginTop: 20}}>
  300. <Typography.Text>
  301. {t('划转额度')}{renderQuotaWithPrompt(transferAmount)} {t('最低') + renderQuota(getQuotaPerUnit())}
  302. </Typography.Text>
  303. <div>
  304. <InputNumber
  305. min={0}
  306. style={{marginTop: 5}}
  307. value={transferAmount}
  308. onChange={(value) => setTransferAmount(value)}
  309. disabled={false}
  310. ></InputNumber>
  311. </div>
  312. </div>
  313. </Modal>
  314. <div style={{marginTop: 20}}>
  315. <Card
  316. title={
  317. <Card.Meta
  318. avatar={
  319. <Avatar
  320. size='default'
  321. color={stringToColor(getUsername())}
  322. style={{marginRight: 4}}
  323. >
  324. {typeof getUsername() === 'string' &&
  325. getUsername().slice(0, 1)}
  326. </Avatar>
  327. }
  328. title={<Typography.Text>{getUsername()}</Typography.Text>}
  329. description={
  330. isRoot() ? (
  331. <Tag color='red'>{t('管理员')}</Tag>
  332. ) : (
  333. <Tag color='blue'>{t('普通用户')}</Tag>
  334. )
  335. }
  336. ></Card.Meta>
  337. }
  338. headerExtraContent={
  339. <>
  340. <Space vertical align='start'>
  341. <Tag color='green'>{'ID: ' + userState?.user?.id}</Tag>
  342. <Tag color='blue'>{userState?.user?.group}</Tag>
  343. </Space>
  344. </>
  345. }
  346. footer={
  347. <>
  348. <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
  349. <Typography.Title heading={6}>{t('可用模型')}</Typography.Title>
  350. </div>
  351. <div style={{marginTop: 10}}>
  352. {models.length <= MODELS_DISPLAY_COUNT ? (
  353. <Space wrap>
  354. {models.map((model) => (
  355. <Tag
  356. key={model}
  357. color='cyan'
  358. onClick={() => {
  359. copyText(model);
  360. }}
  361. >
  362. {model}
  363. </Tag>
  364. ))}
  365. </Space>
  366. ) : (
  367. <>
  368. <Collapsible isOpen={isModelsExpanded}>
  369. <Space wrap>
  370. {models.map((model) => (
  371. <Tag
  372. key={model}
  373. color='cyan'
  374. onClick={() => {
  375. copyText(model);
  376. }}
  377. >
  378. {model}
  379. </Tag>
  380. ))}
  381. <Tag
  382. color='blue'
  383. type="light"
  384. style={{ cursor: 'pointer' }}
  385. onClick={() => setIsModelsExpanded(false)}
  386. >
  387. {t('收起')}
  388. </Tag>
  389. </Space>
  390. </Collapsible>
  391. {!isModelsExpanded && (
  392. <Space wrap>
  393. {models.slice(0, MODELS_DISPLAY_COUNT).map((model) => (
  394. <Tag
  395. key={model}
  396. color='cyan'
  397. onClick={() => {
  398. copyText(model);
  399. }}
  400. >
  401. {model}
  402. </Tag>
  403. ))}
  404. <Tag
  405. color='blue'
  406. type="light"
  407. style={{ cursor: 'pointer' }}
  408. onClick={() => setIsModelsExpanded(true)}
  409. >
  410. {t('更多')} {models.length - MODELS_DISPLAY_COUNT} {t('个模型')}
  411. </Tag>
  412. </Space>
  413. )}
  414. </>
  415. )}
  416. </div>
  417. </>
  418. }
  419. >
  420. <Descriptions row>
  421. <Descriptions.Item itemKey={t('当前余额')}>
  422. {renderQuota(userState?.user?.quota)}
  423. </Descriptions.Item>
  424. <Descriptions.Item itemKey={t('历史消耗')}>
  425. {renderQuota(userState?.user?.used_quota)}
  426. </Descriptions.Item>
  427. <Descriptions.Item itemKey={t('请求次数')}>
  428. {userState.user?.request_count}
  429. </Descriptions.Item>
  430. </Descriptions>
  431. </Card>
  432. <Card
  433. style={{marginTop: 10}}
  434. footer={
  435. <div>
  436. <Typography.Text>{t('邀请链接')}</Typography.Text>
  437. <Input
  438. style={{marginTop: 10}}
  439. value={affLink}
  440. onClick={handleAffLinkClick}
  441. readOnly
  442. />
  443. </div>
  444. }
  445. >
  446. <Typography.Title heading={6}>{t('邀请信息')}</Typography.Title>
  447. <div style={{marginTop: 10}}>
  448. <Descriptions row>
  449. <Descriptions.Item itemKey={t('待使用收益')}>
  450. <span style={{color: 'rgba(var(--semi-red-5), 1)'}}>
  451. {renderQuota(userState?.user?.aff_quota)}
  452. </span>
  453. <Button
  454. type={'secondary'}
  455. onClick={() => setOpenTransfer(true)}
  456. size={'small'}
  457. style={{marginLeft: 10}}
  458. >
  459. {t('划转')}
  460. </Button>
  461. </Descriptions.Item>
  462. <Descriptions.Item itemKey={t('总收益')}>
  463. {renderQuota(userState?.user?.aff_history_quota)}
  464. </Descriptions.Item>
  465. <Descriptions.Item itemKey={t('邀请人数')}>
  466. {userState?.user?.aff_count}
  467. </Descriptions.Item>
  468. </Descriptions>
  469. </div>
  470. </Card>
  471. <Card style={{marginTop: 10}}>
  472. <Typography.Title heading={6}>{t('个人信息')}</Typography.Title>
  473. <div style={{marginTop: 20}}>
  474. <Typography.Text strong>{t('邮箱')}</Typography.Text>
  475. <div
  476. style={{display: 'flex', justifyContent: 'space-between'}}
  477. >
  478. <div>
  479. <Input
  480. value={
  481. userState.user && userState.user.email !== ''
  482. ? userState.user.email
  483. : t('未绑定')
  484. }
  485. readonly={true}
  486. ></Input>
  487. </div>
  488. <div>
  489. <Button
  490. onClick={() => {
  491. setShowEmailBindModal(true);
  492. }}
  493. >
  494. {userState.user && userState.user.email !== ''
  495. ? t('修改绑定')
  496. : t('绑定邮箱')}
  497. </Button>
  498. </div>
  499. </div>
  500. </div>
  501. <div style={{marginTop: 10}}>
  502. <Typography.Text strong>{t('微信')}</Typography.Text>
  503. <div
  504. style={{display: 'flex', justifyContent: 'space-between'}}
  505. >
  506. <div>
  507. <Input
  508. value={
  509. userState.user && userState.user.wechat_id !== ''
  510. ? t('已绑定')
  511. : t('未绑定')
  512. }
  513. readonly={true}
  514. ></Input>
  515. </div>
  516. <div>
  517. <Button
  518. disabled={
  519. (userState.user && userState.user.wechat_id !== '') ||
  520. !status.wechat_login
  521. }
  522. >
  523. {status.wechat_login ? t('绑定') : t('未启用')}
  524. </Button>
  525. </div>
  526. </div>
  527. </div>
  528. <div style={{marginTop: 10}}>
  529. <Typography.Text strong>{t('GitHub')}</Typography.Text>
  530. <div
  531. style={{display: 'flex', justifyContent: 'space-between'}}
  532. >
  533. <div>
  534. <Input
  535. value={
  536. userState.user && userState.user.github_id !== ''
  537. ? userState.user.github_id
  538. : t('未绑定')
  539. }
  540. readonly={true}
  541. ></Input>
  542. </div>
  543. <div>
  544. <Button
  545. onClick={() => {
  546. onGitHubOAuthClicked(status.github_client_id);
  547. }}
  548. disabled={
  549. (userState.user && userState.user.github_id !== '') ||
  550. !status.github_oauth
  551. }
  552. >
  553. {status.github_oauth ? t('绑定') : t('未启用')}
  554. </Button>
  555. </div>
  556. </div>
  557. </div>
  558. <div style={{marginTop: 10}}>
  559. <Typography.Text strong>{t('Telegram')}</Typography.Text>
  560. <div
  561. style={{display: 'flex', justifyContent: 'space-between'}}
  562. >
  563. <div>
  564. <Input
  565. value={
  566. userState.user && userState.user.telegram_id !== ''
  567. ? userState.user.telegram_id
  568. : t('未绑定')
  569. }
  570. readonly={true}
  571. ></Input>
  572. </div>
  573. <div>
  574. {status.telegram_oauth ? (
  575. userState.user.telegram_id !== '' ? (
  576. <Button disabled={true}>{t('已绑定')}</Button>
  577. ) : (
  578. <TelegramLoginButton
  579. dataAuthUrl='/api/oauth/telegram/bind'
  580. botName={status.telegram_bot_name}
  581. />
  582. )
  583. ) : (
  584. <Button disabled={true}>{t('未启用')}</Button>
  585. )}
  586. </div>
  587. </div>
  588. </div>
  589. <div style={{marginTop: 10}}>
  590. <Typography.Text strong>{t('LinuxDO')}</Typography.Text>
  591. <div
  592. style={{display: 'flex', justifyContent: 'space-between'}}
  593. >
  594. <div>
  595. <Input
  596. value={
  597. userState.user && userState.user.linux_do_id !== ''
  598. ? userState.user.linux_do_id
  599. : t('未绑定')
  600. }
  601. readonly={true}
  602. ></Input>
  603. </div>
  604. <div>
  605. <Button
  606. onClick={() => {
  607. onLinuxDOOAuthClicked(status.linuxdo_client_id);
  608. }}
  609. disabled={
  610. (userState.user && userState.user.linux_do_id !== '') ||
  611. !status.linuxdo_oauth
  612. }
  613. >
  614. {status.linuxdo_oauth ? t('绑定') : t('未启用')}
  615. </Button>
  616. </div>
  617. </div>
  618. </div>
  619. <div style={{marginTop: 10}}>
  620. <Space>
  621. <Button onClick={generateAccessToken}>
  622. {t('生成系统访问令牌')}
  623. </Button>
  624. <Button
  625. onClick={() => {
  626. setShowChangePasswordModal(true);
  627. }}
  628. >
  629. {t('修改密码')}
  630. </Button>
  631. <Button
  632. type={'danger'}
  633. onClick={() => {
  634. setShowAccountDeleteModal(true);
  635. }}
  636. >
  637. {t('删除个人账户')}
  638. </Button>
  639. </Space>
  640. {systemToken && (
  641. <Input
  642. readOnly
  643. value={systemToken}
  644. onClick={handleSystemTokenClick}
  645. style={{marginTop: '10px'}}
  646. />
  647. )}
  648. {status.wechat_login && (
  649. <Button
  650. onClick={() => {
  651. setShowWeChatBindModal(true);
  652. }}
  653. >
  654. {t('绑定微信账号')}
  655. </Button>
  656. )}
  657. <Modal
  658. onCancel={() => setShowWeChatBindModal(false)}
  659. // onOpen={() => setShowWeChatBindModal(true)}
  660. visible={showWeChatBindModal}
  661. size={'small'}
  662. >
  663. <Image src={status.wechat_qrcode}/>
  664. <div style={{textAlign: 'center'}}>
  665. <p>
  666. 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
  667. </p>
  668. </div>
  669. <Input
  670. placeholder='验证码'
  671. name='wechat_verification_code'
  672. value={inputs.wechat_verification_code}
  673. onChange={(v) =>
  674. handleInputChange('wechat_verification_code', v)
  675. }
  676. />
  677. <Button color='' fluid size='large' onClick={bindWeChat}>
  678. {t('绑定')}
  679. </Button>
  680. </Modal>
  681. </div>
  682. </Card>
  683. <Modal
  684. onCancel={() => setShowEmailBindModal(false)}
  685. // onOpen={() => setShowEmailBindModal(true)}
  686. onOk={bindEmail}
  687. visible={showEmailBindModal}
  688. size={'small'}
  689. centered={true}
  690. maskClosable={false}
  691. >
  692. <Typography.Title heading={6}>{t('绑定邮箱地址')}</Typography.Title>
  693. <div
  694. style={{
  695. marginTop: 20,
  696. display: 'flex',
  697. justifyContent: 'space-between',
  698. }}
  699. >
  700. <Input
  701. fluid
  702. placeholder='输入邮箱地址'
  703. onChange={(value) => handleInputChange('email', value)}
  704. name='email'
  705. type='email'
  706. />
  707. <Button
  708. onClick={sendVerificationCode}
  709. disabled={disableButton || loading}
  710. >
  711. {disableButton ? `重新发送 (${countdown})` : '获取验证码'}
  712. </Button>
  713. </div>
  714. <div style={{marginTop: 10}}>
  715. <Input
  716. fluid
  717. placeholder='验证码'
  718. name='email_verification_code'
  719. value={inputs.email_verification_code}
  720. onChange={(value) =>
  721. handleInputChange('email_verification_code', value)
  722. }
  723. />
  724. </div>
  725. {turnstileEnabled ? (
  726. <Turnstile
  727. sitekey={turnstileSiteKey}
  728. onVerify={(token) => {
  729. setTurnstileToken(token);
  730. }}
  731. />
  732. ) : (
  733. <></>
  734. )}
  735. </Modal>
  736. <Modal
  737. onCancel={() => setShowAccountDeleteModal(false)}
  738. visible={showAccountDeleteModal}
  739. size={'small'}
  740. centered={true}
  741. onOk={deleteAccount}
  742. >
  743. <div style={{marginTop: 20}}>
  744. <Banner
  745. type='danger'
  746. description='您正在删除自己的帐户,将清空所有数据且不可恢复'
  747. closeIcon={null}
  748. />
  749. </div>
  750. <div style={{marginTop: 20}}>
  751. <Input
  752. placeholder={`输入你的账户名 ${userState?.user?.username} 以确认删除`}
  753. name='self_account_deletion_confirmation'
  754. value={inputs.self_account_deletion_confirmation}
  755. onChange={(value) =>
  756. handleInputChange(
  757. 'self_account_deletion_confirmation',
  758. value,
  759. )
  760. }
  761. />
  762. {turnstileEnabled ? (
  763. <Turnstile
  764. sitekey={turnstileSiteKey}
  765. onVerify={(token) => {
  766. setTurnstileToken(token);
  767. }}
  768. />
  769. ) : (
  770. <></>
  771. )}
  772. </div>
  773. </Modal>
  774. <Modal
  775. onCancel={() => setShowChangePasswordModal(false)}
  776. visible={showChangePasswordModal}
  777. size={'small'}
  778. centered={true}
  779. onOk={changePassword}
  780. >
  781. <div style={{marginTop: 20}}>
  782. <Input
  783. name='set_new_password'
  784. placeholder={t('新密码')}
  785. value={inputs.set_new_password}
  786. onChange={(value) =>
  787. handleInputChange('set_new_password', value)
  788. }
  789. />
  790. <Input
  791. style={{marginTop: 20}}
  792. name='set_new_password_confirmation'
  793. placeholder={t('确认新密码')}
  794. value={inputs.set_new_password_confirmation}
  795. onChange={(value) =>
  796. handleInputChange('set_new_password_confirmation', value)
  797. }
  798. />
  799. {turnstileEnabled ? (
  800. <Turnstile
  801. sitekey={turnstileSiteKey}
  802. onVerify={(token) => {
  803. setTurnstileToken(token);
  804. }}
  805. />
  806. ) : (
  807. <></>
  808. )}
  809. </div>
  810. </Modal>
  811. </div>
  812. </Layout.Content>
  813. </Layout>
  814. </div>
  815. );
  816. };
  817. export default PersonalSetting;