PersonalSetting.js 39 KB

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