PersonalSetting.js 34 KB

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