PersonalSetting.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  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. footer={
  375. <div>
  376. <Typography.Text>邀请链接</Typography.Text>
  377. <Input
  378. style={{ marginTop: 10 }}
  379. value={affLink}
  380. onClick={handleAffLinkClick}
  381. readOnly
  382. />
  383. </div>
  384. }
  385. >
  386. <Typography.Title heading={6}>邀请信息</Typography.Title>
  387. <div style={{ marginTop: 10 }}>
  388. <Descriptions row>
  389. <Descriptions.Item itemKey='待使用收益'>
  390. <span style={{ color: 'rgba(var(--semi-red-5), 1)' }}>
  391. {renderQuota(userState?.user?.aff_quota)}
  392. </span>
  393. <Button
  394. type={'secondary'}
  395. onClick={() => setOpenTransfer(true)}
  396. size={'small'}
  397. style={{ marginLeft: 10 }}
  398. >
  399. 划转
  400. </Button>
  401. </Descriptions.Item>
  402. <Descriptions.Item itemKey='总收益'>
  403. {renderQuota(userState?.user?.aff_history_quota)}
  404. </Descriptions.Item>
  405. <Descriptions.Item itemKey='邀请人数'>
  406. {userState?.user?.aff_count}
  407. </Descriptions.Item>
  408. </Descriptions>
  409. </div>
  410. </Card>
  411. <Card>
  412. <Typography.Title heading={6}>个人信息</Typography.Title>
  413. <div style={{ marginTop: 20 }}>
  414. <Typography.Text strong>邮箱</Typography.Text>
  415. <div
  416. style={{ display: 'flex', justifyContent: 'space-between' }}
  417. >
  418. <div>
  419. <Input
  420. value={
  421. userState.user && userState.user.email !== ''
  422. ? userState.user.email
  423. : '未绑定'
  424. }
  425. readonly={true}
  426. ></Input>
  427. </div>
  428. <div>
  429. <Button
  430. onClick={() => {
  431. setShowEmailBindModal(true);
  432. }}
  433. >
  434. {userState.user && userState.user.email !== ''
  435. ? '修改绑定'
  436. : '绑定邮箱'}
  437. </Button>
  438. </div>
  439. </div>
  440. </div>
  441. <div style={{ marginTop: 10 }}>
  442. <Typography.Text strong>微信</Typography.Text>
  443. <div
  444. style={{ display: 'flex', justifyContent: 'space-between' }}
  445. >
  446. <div>
  447. <Input
  448. value={
  449. userState.user && userState.user.wechat_id !== ''
  450. ? '已绑定'
  451. : '未绑定'
  452. }
  453. readonly={true}
  454. ></Input>
  455. </div>
  456. <div>
  457. <Button
  458. disabled={
  459. (userState.user && userState.user.wechat_id !== '') ||
  460. !status.wechat_login
  461. }
  462. >
  463. {status.wechat_login ? '绑定' : '未启用'}
  464. </Button>
  465. </div>
  466. </div>
  467. </div>
  468. <div style={{ marginTop: 10 }}>
  469. <Typography.Text strong>GitHub</Typography.Text>
  470. <div
  471. style={{ display: 'flex', justifyContent: 'space-between' }}
  472. >
  473. <div>
  474. <Input
  475. value={
  476. userState.user && userState.user.github_id !== ''
  477. ? userState.user.github_id
  478. : '未绑定'
  479. }
  480. readonly={true}
  481. ></Input>
  482. </div>
  483. <div>
  484. <Button
  485. onClick={() => {
  486. onGitHubOAuthClicked(status.github_client_id);
  487. }}
  488. disabled={
  489. (userState.user && userState.user.github_id !== '') ||
  490. !status.github_oauth
  491. }
  492. >
  493. {status.github_oauth ? '绑定' : '未启用'}
  494. </Button>
  495. </div>
  496. </div>
  497. </div>
  498. <div style={{ marginTop: 10 }}>
  499. <Typography.Text strong>Telegram</Typography.Text>
  500. <div
  501. style={{ display: 'flex', justifyContent: 'space-between' }}
  502. >
  503. <div>
  504. <Input
  505. value={
  506. userState.user && userState.user.telegram_id !== ''
  507. ? userState.user.telegram_id
  508. : '未绑定'
  509. }
  510. readonly={true}
  511. ></Input>
  512. </div>
  513. <div>
  514. {status.telegram_oauth ? (
  515. userState.user.telegram_id !== '' ? (
  516. <Button disabled={true}>已绑定</Button>
  517. ) : (
  518. <TelegramLoginButton
  519. dataAuthUrl='/api/oauth/telegram/bind'
  520. botName={status.telegram_bot_name}
  521. />
  522. )
  523. ) : (
  524. <Button disabled={true}>未启用</Button>
  525. )}
  526. </div>
  527. </div>
  528. </div>
  529. <div style={{ marginTop: 10 }}>
  530. <Typography.Text strong>LinuxDO</Typography.Text>
  531. <div
  532. style={{ display: 'flex', justifyContent: 'space-between' }}
  533. >
  534. <div>
  535. <Input
  536. value={
  537. userState.user && userState.user.linux_do_id !== ''
  538. ? userState.user.linux_do_id
  539. : '未绑定'
  540. }
  541. readonly={true}
  542. ></Input>
  543. </div>
  544. <div>
  545. <Button
  546. onClick={() => {
  547. onLinuxDOOAuthClicked(status.linuxdo_client_id);
  548. }}
  549. disabled={
  550. (userState.user && userState.user.linux_do_id !== '') ||
  551. !status.linuxdo_oauth
  552. }
  553. >
  554. {status.linuxdo_oauth ? '绑定' : '未启用'}
  555. </Button>
  556. </div>
  557. </div>
  558. </div>
  559. <div style={{ marginTop: 10 }}>
  560. <Space>
  561. <Button onClick={generateAccessToken}>
  562. 生成系统访问令牌
  563. </Button>
  564. <Button
  565. onClick={() => {
  566. setShowChangePasswordModal(true);
  567. }}
  568. >
  569. 修改密码
  570. </Button>
  571. <Button
  572. type={'danger'}
  573. onClick={() => {
  574. setShowAccountDeleteModal(true);
  575. }}
  576. >
  577. 删除个人账户
  578. </Button>
  579. </Space>
  580. {systemToken && (
  581. <Input
  582. readOnly
  583. value={systemToken}
  584. onClick={handleSystemTokenClick}
  585. style={{ marginTop: '10px' }}
  586. />
  587. )}
  588. {status.wechat_login && (
  589. <Button
  590. onClick={() => {
  591. setShowWeChatBindModal(true);
  592. }}
  593. >
  594. 绑定微信账号
  595. </Button>
  596. )}
  597. <Modal
  598. onCancel={() => setShowWeChatBindModal(false)}
  599. // onOpen={() => setShowWeChatBindModal(true)}
  600. visible={showWeChatBindModal}
  601. size={'small'}
  602. >
  603. <Image src={status.wechat_qrcode} />
  604. <div style={{ textAlign: 'center' }}>
  605. <p>
  606. 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
  607. </p>
  608. </div>
  609. <Input
  610. placeholder='验证码'
  611. name='wechat_verification_code'
  612. value={inputs.wechat_verification_code}
  613. onChange={(v) =>
  614. handleInputChange('wechat_verification_code', v)
  615. }
  616. />
  617. <Button color='' fluid size='large' onClick={bindWeChat}>
  618. 绑定
  619. </Button>
  620. </Modal>
  621. </div>
  622. </Card>
  623. <Modal
  624. onCancel={() => setShowEmailBindModal(false)}
  625. // onOpen={() => setShowEmailBindModal(true)}
  626. onOk={bindEmail}
  627. visible={showEmailBindModal}
  628. size={'small'}
  629. centered={true}
  630. maskClosable={false}
  631. >
  632. <Typography.Title heading={6}>绑定邮箱地址</Typography.Title>
  633. <div
  634. style={{
  635. marginTop: 20,
  636. display: 'flex',
  637. justifyContent: 'space-between',
  638. }}
  639. >
  640. <Input
  641. fluid
  642. placeholder='输入邮箱地址'
  643. onChange={(value) => handleInputChange('email', value)}
  644. name='email'
  645. type='email'
  646. />
  647. <Button
  648. onClick={sendVerificationCode}
  649. disabled={disableButton || loading}
  650. >
  651. {disableButton ? `重新发送 (${countdown})` : '获取验证码'}
  652. </Button>
  653. </div>
  654. <div style={{ marginTop: 10 }}>
  655. <Input
  656. fluid
  657. placeholder='验证码'
  658. name='email_verification_code'
  659. value={inputs.email_verification_code}
  660. onChange={(value) =>
  661. handleInputChange('email_verification_code', value)
  662. }
  663. />
  664. </div>
  665. {turnstileEnabled ? (
  666. <Turnstile
  667. sitekey={turnstileSiteKey}
  668. onVerify={(token) => {
  669. setTurnstileToken(token);
  670. }}
  671. />
  672. ) : (
  673. <></>
  674. )}
  675. </Modal>
  676. <Modal
  677. onCancel={() => setShowAccountDeleteModal(false)}
  678. visible={showAccountDeleteModal}
  679. size={'small'}
  680. centered={true}
  681. onOk={deleteAccount}
  682. >
  683. <div style={{ marginTop: 20 }}>
  684. <Banner
  685. type='danger'
  686. description='您正在删除自己的帐户,将清空所有数据且不可恢复'
  687. closeIcon={null}
  688. />
  689. </div>
  690. <div style={{ marginTop: 20 }}>
  691. <Input
  692. placeholder={`输入你的账户名 ${userState?.user?.username} 以确认删除`}
  693. name='self_account_deletion_confirmation'
  694. value={inputs.self_account_deletion_confirmation}
  695. onChange={(value) =>
  696. handleInputChange(
  697. 'self_account_deletion_confirmation',
  698. value,
  699. )
  700. }
  701. />
  702. {turnstileEnabled ? (
  703. <Turnstile
  704. sitekey={turnstileSiteKey}
  705. onVerify={(token) => {
  706. setTurnstileToken(token);
  707. }}
  708. />
  709. ) : (
  710. <></>
  711. )}
  712. </div>
  713. </Modal>
  714. <Modal
  715. onCancel={() => setShowChangePasswordModal(false)}
  716. visible={showChangePasswordModal}
  717. size={'small'}
  718. centered={true}
  719. onOk={changePassword}
  720. >
  721. <div style={{ marginTop: 20 }}>
  722. <Input
  723. name='set_new_password'
  724. placeholder='新密码'
  725. value={inputs.set_new_password}
  726. onChange={(value) =>
  727. handleInputChange('set_new_password', value)
  728. }
  729. />
  730. <Input
  731. style={{ marginTop: 20 }}
  732. name='set_new_password_confirmation'
  733. placeholder='确认新密码'
  734. value={inputs.set_new_password_confirmation}
  735. onChange={(value) =>
  736. handleInputChange('set_new_password_confirmation', value)
  737. }
  738. />
  739. {turnstileEnabled ? (
  740. <Turnstile
  741. sitekey={turnstileSiteKey}
  742. onVerify={(token) => {
  743. setTurnstileToken(token);
  744. }}
  745. />
  746. ) : (
  747. <></>
  748. )}
  749. </div>
  750. </Modal>
  751. </div>
  752. </Layout.Content>
  753. </Layout>
  754. </div>
  755. );
  756. };
  757. export default PersonalSetting;