File size: 4,804 Bytes
4674012
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
Copyright (C) 2025 QuantumNous

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

For commercial licensing, please contact support@quantumnous.com
*/

import React from 'react';
import { useTranslation } from 'react-i18next';
import { Modal, Button, Input, Typography } from '@douyinfe/semi-ui';

/**
 * 可复用的两步验证模态框组件
 * @param {Object} props
 * @param {boolean} props.visible - 是否显示模态框
 * @param {string} props.code - 验证码值
 * @param {boolean} props.loading - 是否正在验证
 * @param {Function} props.onCodeChange - 验证码变化回调
 * @param {Function} props.onVerify - 验证回调
 * @param {Function} props.onCancel - 取消回调
 * @param {string} props.title - 模态框标题
 * @param {string} props.description - 验证描述文本
 * @param {string} props.placeholder - 输入框占位文本
 */
const TwoFactorAuthModal = ({
  visible,
  code,
  loading,
  onCodeChange,
  onVerify,
  onCancel,
  title,
  description,
  placeholder,
}) => {
  const { t } = useTranslation();

  const handleKeyDown = (e) => {
    if (e.key === 'Enter' && code && !loading) {
      onVerify();
    }
  };

  return (
    <Modal
      title={
        <div className='flex items-center'>
          <div className='w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center mr-3'>
            <svg
              className='w-4 h-4 text-blue-600 dark:text-blue-400'
              fill='currentColor'
              viewBox='0 0 20 20'
            >
              <path
                fillRule='evenodd'
                d='M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z'
                clipRule='evenodd'
              />
            </svg>
          </div>
          {title || t('安全验证')}
        </div>
      }
      visible={visible}
      onCancel={onCancel}
      footer={
        <>
          <Button onClick={onCancel}>{t('取消')}</Button>
          <Button
            type='primary'
            loading={loading}
            disabled={!code || loading}
            onClick={onVerify}
          >
            {t('验证')}
          </Button>
        </>
      }
      width={500}
      style={{ maxWidth: '90vw' }}
    >
      <div className='space-y-6'>
        {/* 安全提示 */}
        <div className='bg-blue-50 dark:bg-blue-900 rounded-lg p-4'>
          <div className='flex items-start'>
            <svg
              className='w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 mr-3 flex-shrink-0'
              fill='currentColor'
              viewBox='0 0 20 20'
            >
              <path
                fillRule='evenodd'
                d='M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z'
                clipRule='evenodd'
              />
            </svg>
            <div>
              <Typography.Text
                strong
                className='text-blue-800 dark:text-blue-200'
              >
                {t('安全验证')}
              </Typography.Text>
              <Typography.Text className='block text-blue-700 dark:text-blue-300 text-sm mt-1'>
                {description || t('为了保护账户安全,请验证您的两步验证码。')}
              </Typography.Text>
            </div>
          </div>
        </div>

        {/* 验证码输入 */}
        <div>
          <Typography.Text strong className='block mb-2'>
            {t('验证身份')}
          </Typography.Text>
          <Input
            placeholder={placeholder || t('请输入认证器验证码或备用码')}
            value={code}
            onChange={onCodeChange}
            size='large'
            maxLength={8}
            onKeyDown={handleKeyDown}
            autoFocus
          />
          <Typography.Text type='tertiary' size='small' className='mt-2 block'>
            {t(
              '支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。',
            )}
          </Typography.Text>
        </div>
      </div>
    </Modal>
  );
};

export default TwoFactorAuthModal;