PasswordResetForm.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import React, { useEffect, useState } from 'react';
  2. import { API, getLogo, showError, showInfo, showSuccess, getSystemName } from '../../helpers';
  3. import Turnstile from 'react-turnstile';
  4. import { Button, Card, Form, Typography } from '@douyinfe/semi-ui';
  5. import { IconMail } from '@douyinfe/semi-icons';
  6. import { Link } from 'react-router-dom';
  7. import { useTranslation } from 'react-i18next';
  8. const { Text, Title } = Typography;
  9. const PasswordResetForm = () => {
  10. const { t } = useTranslation();
  11. const [inputs, setInputs] = useState({
  12. email: '',
  13. });
  14. const { email } = inputs;
  15. const [loading, setLoading] = useState(false);
  16. const [turnstileEnabled, setTurnstileEnabled] = useState(false);
  17. const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
  18. const [turnstileToken, setTurnstileToken] = useState('');
  19. const [disableButton, setDisableButton] = useState(false);
  20. const [countdown, setCountdown] = useState(30);
  21. const logo = getLogo();
  22. const systemName = getSystemName();
  23. useEffect(() => {
  24. let status = localStorage.getItem('status');
  25. if (status) {
  26. status = JSON.parse(status);
  27. if (status.turnstile_check) {
  28. setTurnstileEnabled(true);
  29. setTurnstileSiteKey(status.turnstile_site_key);
  30. }
  31. }
  32. }, []);
  33. useEffect(() => {
  34. let countdownInterval = null;
  35. if (disableButton && countdown > 0) {
  36. countdownInterval = setInterval(() => {
  37. setCountdown(countdown - 1);
  38. }, 1000);
  39. } else if (countdown === 0) {
  40. setDisableButton(false);
  41. setCountdown(30);
  42. }
  43. return () => clearInterval(countdownInterval);
  44. }, [disableButton, countdown]);
  45. function handleChange(value) {
  46. setInputs((inputs) => ({ ...inputs, email: value }));
  47. }
  48. async function handleSubmit(e) {
  49. if (!email) {
  50. showError(t('请输入邮箱地址'));
  51. return;
  52. }
  53. if (turnstileEnabled && turnstileToken === '') {
  54. showInfo(t('请稍后几秒重试,Turnstile 正在检查用户环境!'));
  55. return;
  56. }
  57. setDisableButton(true);
  58. setLoading(true);
  59. const res = await API.get(
  60. `/api/reset_password?email=${email}&turnstile=${turnstileToken}`,
  61. );
  62. const { success, message } = res.data;
  63. if (success) {
  64. showSuccess(t('重置邮件发送成功,请检查邮箱!'));
  65. setInputs({ ...inputs, email: '' });
  66. } else {
  67. showError(message);
  68. }
  69. setLoading(false);
  70. }
  71. return (
  72. <div className="relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
  73. {/* 背景模糊晕染球 */}
  74. <div className="blur-ball blur-ball-indigo" style={{ top: '-80px', right: '-80px', transform: 'none' }} />
  75. <div className="blur-ball blur-ball-teal" style={{ top: '50%', left: '-120px' }} />
  76. <div className="w-full max-w-sm mt-[64px]">
  77. <div className="flex flex-col items-center">
  78. <div className="w-full max-w-md">
  79. <div className="flex items-center justify-center mb-6 gap-2">
  80. <img src={logo} alt="Logo" className="h-10 rounded-full" />
  81. <Title heading={3} className='!text-gray-800'>{systemName}</Title>
  82. </div>
  83. <Card className="shadow-xl border-0 !rounded-2xl overflow-hidden">
  84. <div className="flex justify-center pt-6 pb-2">
  85. <Title heading={3} className="text-gray-800 dark:text-gray-200">{t('密码重置')}</Title>
  86. </div>
  87. <div className="px-2 py-8">
  88. <Form className="space-y-3">
  89. <Form.Input
  90. field="email"
  91. label={t('邮箱')}
  92. placeholder={t('请输入您的邮箱地址')}
  93. name="email"
  94. size="large"
  95. className="!rounded-md"
  96. value={email}
  97. onChange={handleChange}
  98. prefix={<IconMail />}
  99. />
  100. <div className="space-y-2 pt-2">
  101. <Button
  102. theme="solid"
  103. className="w-full !rounded-full"
  104. type="primary"
  105. htmlType="submit"
  106. size="large"
  107. onClick={handleSubmit}
  108. loading={loading}
  109. disabled={disableButton}
  110. >
  111. {disableButton ? `${t('重试')} (${countdown})` : t('提交')}
  112. </Button>
  113. </div>
  114. </Form>
  115. <div className="mt-6 text-center text-sm">
  116. <Text>{t('想起来了?')} <Link to="/login" className="text-blue-600 hover:text-blue-800 font-medium">{t('登录')}</Link></Text>
  117. </div>
  118. </div>
  119. </Card>
  120. {turnstileEnabled && (
  121. <div className="flex justify-center mt-6">
  122. <Turnstile
  123. sitekey={turnstileSiteKey}
  124. onVerify={(token) => {
  125. setTurnstileToken(token);
  126. }}
  127. />
  128. </div>
  129. )}
  130. </div>
  131. </div>
  132. </div>
  133. </div>
  134. );
  135. };
  136. export default PasswordResetForm;