| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import React, { useEffect, useState } from 'react'; |
| | import { useTranslation } from 'react-i18next'; |
| | import { |
| | Modal, |
| | Button, |
| | Input, |
| | Typography, |
| | Tabs, |
| | TabPane, |
| | Space, |
| | Spin, |
| | } from '@douyinfe/semi-ui'; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const SecureVerificationModal = ({ |
| | visible, |
| | verificationMethods, |
| | verificationState, |
| | onVerify, |
| | onCancel, |
| | onCodeChange, |
| | onMethodSwitch, |
| | title, |
| | description, |
| | }) => { |
| | const { t } = useTranslation(); |
| | const [isAnimating, setIsAnimating] = useState(false); |
| | const [verifySuccess, setVerifySuccess] = useState(false); |
| |
|
| | const { has2FA, hasPasskey, passkeySupported } = verificationMethods; |
| | const { method, loading, code } = verificationState; |
| |
|
| | useEffect(() => { |
| | if (visible) { |
| | setIsAnimating(true); |
| | setVerifySuccess(false); |
| | } else { |
| | setIsAnimating(false); |
| | } |
| | }, [visible]); |
| |
|
| | const handleKeyDown = (e) => { |
| | if (e.key === 'Enter' && code.trim() && !loading && method === '2fa') { |
| | onVerify(method, code); |
| | } |
| | if (e.key === 'Escape' && !loading) { |
| | onCancel(); |
| | } |
| | }; |
| |
|
| | |
| | if (visible && !has2FA && !hasPasskey) { |
| | return ( |
| | <Modal |
| | title={title || t('安全验证')} |
| | visible={visible} |
| | onCancel={onCancel} |
| | footer={<Button onClick={onCancel}>{t('确定')}</Button>} |
| | width={500} |
| | style={{ maxWidth: '90vw' }} |
| | > |
| | <div className='text-center py-6'> |
| | <div className='mb-4'> |
| | <svg |
| | className='w-16 h-16 text-yellow-500 mx-auto mb-4' |
| | fill='currentColor' |
| | viewBox='0 0 20 20' |
| | > |
| | <path |
| | fillRule='evenodd' |
| | d='M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z' |
| | clipRule='evenodd' |
| | /> |
| | </svg> |
| | </div> |
| | <Typography.Title heading={4} className='mb-2'> |
| | {t('需要安全验证')} |
| | </Typography.Title> |
| | <Typography.Text type='tertiary'> |
| | {t('您需要先启用两步验证或 Passkey 才能查看敏感信息。')} |
| | </Typography.Text> |
| | <br /> |
| | <Typography.Text type='tertiary'> |
| | {t('请前往个人设置 → 安全设置进行配置。')} |
| | </Typography.Text> |
| | </div> |
| | </Modal> |
| | ); |
| | } |
| |
|
| | return ( |
| | <Modal |
| | title={title || t('安全验证')} |
| | visible={visible} |
| | onCancel={loading ? undefined : onCancel} |
| | closeOnEsc={!loading} |
| | footer={null} |
| | width={460} |
| | centered |
| | style={{ |
| | maxWidth: 'calc(100vw - 32px)', |
| | }} |
| | bodyStyle={{ |
| | padding: '20px 24px', |
| | }} |
| | > |
| | <div style={{ width: '100%' }}> |
| | {/* 描述信息 */} |
| | {description && ( |
| | <Typography.Paragraph |
| | type='tertiary' |
| | style={{ |
| | margin: '0 0 20px 0', |
| | fontSize: '14px', |
| | lineHeight: '1.6', |
| | }} |
| | > |
| | {description} |
| | </Typography.Paragraph> |
| | )} |
| | |
| | {/* 验证方式选择 */} |
| | <Tabs |
| | activeKey={method} |
| | onChange={onMethodSwitch} |
| | type='line' |
| | size='default' |
| | style={{ margin: 0 }} |
| | > |
| | {has2FA && ( |
| | <TabPane tab={t('两步验证')} itemKey='2fa'> |
| | <div style={{ paddingTop: '20px' }}> |
| | <div style={{ marginBottom: '12px' }}> |
| | <Input |
| | placeholder={t('请输入6位验证码或8位备用码')} |
| | value={code} |
| | onChange={onCodeChange} |
| | size='large' |
| | maxLength={8} |
| | onKeyDown={handleKeyDown} |
| | autoFocus={method === '2fa'} |
| | disabled={loading} |
| | prefix={ |
| | <svg |
| | style={{ |
| | width: 16, |
| | height: 16, |
| | marginRight: 8, |
| | flexShrink: 0, |
| | }} |
| | fill='currentColor' |
| | viewBox='0 0 20 20' |
| | > |
| | <path |
| | fillRule='evenodd' |
| | d='M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-4a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z' |
| | clipRule='evenodd' |
| | /> |
| | </svg> |
| | } |
| | style={{ width: '100%' }} |
| | /> |
| | </div> |
| | |
| | <Typography.Text |
| | type='tertiary' |
| | size='small' |
| | style={{ |
| | display: 'block', |
| | marginBottom: '20px', |
| | fontSize: '13px', |
| | lineHeight: '1.5', |
| | }} |
| | > |
| | {t('从认证器应用中获取验证码,或使用备用码')} |
| | </Typography.Text> |
| | |
| | <div |
| | style={{ |
| | display: 'flex', |
| | justifyContent: 'flex-end', |
| | gap: '8px', |
| | flexWrap: 'wrap', |
| | }} |
| | > |
| | <Button onClick={onCancel} disabled={loading}> |
| | {t('取消')} |
| | </Button> |
| | <Button |
| | theme='solid' |
| | type='primary' |
| | loading={loading} |
| | disabled={!code.trim() || loading} |
| | onClick={() => onVerify(method, code)} |
| | > |
| | {t('验证')} |
| | </Button> |
| | </div> |
| | </div> |
| | </TabPane> |
| | )} |
| | |
| | {hasPasskey && passkeySupported && ( |
| | <TabPane tab={t('Passkey')} itemKey='passkey'> |
| | <div style={{ paddingTop: '20px' }}> |
| | <div |
| | style={{ |
| | textAlign: 'center', |
| | padding: '24px 16px', |
| | marginBottom: '20px', |
| | }} |
| | > |
| | <div |
| | style={{ |
| | width: 56, |
| | height: 56, |
| | margin: '0 auto 16px', |
| | display: 'flex', |
| | alignItems: 'center', |
| | justifyContent: 'center', |
| | borderRadius: '50%', |
| | background: 'var(--semi-color-primary-light-default)', |
| | }} |
| | > |
| | <svg |
| | style={{ |
| | width: 28, |
| | height: 28, |
| | color: 'var(--semi-color-primary)', |
| | }} |
| | fill='currentColor' |
| | viewBox='0 0 20 20' |
| | > |
| | <path |
| | fillRule='evenodd' |
| | d='M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-4a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z' |
| | clipRule='evenodd' |
| | /> |
| | </svg> |
| | </div> |
| | <Typography.Title |
| | heading={5} |
| | style={{ margin: '0 0 8px', fontSize: '16px' }} |
| | > |
| | {t('使用 Passkey 验证')} |
| | </Typography.Title> |
| | <Typography.Text |
| | type='tertiary' |
| | style={{ |
| | display: 'block', |
| | margin: 0, |
| | fontSize: '13px', |
| | lineHeight: '1.5', |
| | }} |
| | > |
| | {t('点击验证按钮,使用您的生物特征或安全密钥')} |
| | </Typography.Text> |
| | </div> |
| | |
| | <div |
| | style={{ |
| | display: 'flex', |
| | justifyContent: 'flex-end', |
| | gap: '8px', |
| | flexWrap: 'wrap', |
| | }} |
| | > |
| | <Button onClick={onCancel} disabled={loading}> |
| | {t('取消')} |
| | </Button> |
| | <Button |
| | theme='solid' |
| | type='primary' |
| | loading={loading} |
| | disabled={loading} |
| | onClick={() => onVerify(method)} |
| | > |
| | {t('验证 Passkey')} |
| | </Button> |
| | </div> |
| | </div> |
| | </TabPane> |
| | )} |
| | </Tabs> |
| | </div> |
| | </Modal> |
| | ); |
| | }; |
| |
|
| | export default SecureVerificationModal; |
| |
|