PersonalSetting.js 35 KB

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