/* 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 . For commercial licensing, please contact support@quantumnous.com */ import React from 'react'; import { Button, Card, Input, Space, Typography, Avatar, Tabs, TabPane, Popover, Modal, } from '@douyinfe/semi-ui'; import { IconMail, IconShield, IconGithubLogo, IconKey, IconLock, IconDelete, } from '@douyinfe/semi-icons'; import { SiTelegram, SiWechat, SiLinux, SiDiscord } from 'react-icons/si'; import { UserPlus, ShieldCheck } from 'lucide-react'; import TelegramLoginButton from 'react-telegram-login'; import { API, showError, showSuccess, onGitHubOAuthClicked, onOIDCClicked, onLinuxDOOAuthClicked, onDiscordOAuthClicked, onCustomOAuthClicked, getOAuthProviderIcon, } from '../../../../helpers'; import TwoFASetting from '../components/TwoFASetting'; const AccountManagement = ({ t, userState, status, systemToken, setShowEmailBindModal, setShowWeChatBindModal, generateAccessToken, handleSystemTokenClick, setShowChangePasswordModal, setShowAccountDeleteModal, passkeyStatus, passkeySupported, passkeyRegisterLoading, passkeyDeleteLoading, onPasskeyRegister, onPasskeyDelete, }) => { const renderAccountInfo = (accountId, label) => { if (!accountId || accountId === '') { return {t('未绑定')}; } const popContent = (
{accountId} {label ? (
{label}
) : null}
); return ( {accountId} ); }; const isBound = (accountId) => Boolean(accountId); const [showTelegramBindModal, setShowTelegramBindModal] = React.useState(false); const [customOAuthBindings, setCustomOAuthBindings] = React.useState([]); const [customOAuthLoading, setCustomOAuthLoading] = React.useState({}); // Fetch custom OAuth bindings const loadCustomOAuthBindings = async () => { try { const res = await API.get('/api/user/oauth/bindings'); if (res.data.success) { setCustomOAuthBindings(res.data.data || []); } else { showError(res.data.message || t('获取绑定信息失败')); } } catch (error) { showError(error.response?.data?.message || error.message || t('获取绑定信息失败')); } }; // Unbind custom OAuth provider const handleUnbindCustomOAuth = async (providerId, providerName) => { Modal.confirm({ title: t('确认解绑'), content: t('确定要解绑 {{name}} 吗?', { name: providerName }), okText: t('确认'), cancelText: t('取消'), onOk: async () => { setCustomOAuthLoading((prev) => ({ ...prev, [providerId]: true })); try { const res = await API.delete(`/api/user/oauth/bindings/${providerId}`); if (res.data.success) { showSuccess(t('解绑成功')); await loadCustomOAuthBindings(); } else { showError(res.data.message); } } catch (error) { showError(error.response?.data?.message || error.message || t('操作失败')); } finally { setCustomOAuthLoading((prev) => ({ ...prev, [providerId]: false })); } }, }); }; // Handle bind custom OAuth const handleBindCustomOAuth = (provider) => { onCustomOAuthClicked(provider); }; // Check if custom OAuth provider is bound const isCustomOAuthBound = (providerId) => { const normalizedId = Number(providerId); return customOAuthBindings.some((b) => Number(b.provider_id) === normalizedId); }; // Get binding info for a provider const getCustomOAuthBinding = (providerId) => { const normalizedId = Number(providerId); return customOAuthBindings.find((b) => Number(b.provider_id) === normalizedId); }; React.useEffect(() => { loadCustomOAuthBindings(); }, []); const passkeyEnabled = passkeyStatus?.enabled; const lastUsedLabel = passkeyStatus?.last_used_at ? new Date(passkeyStatus.last_used_at).toLocaleString() : t('尚未使用'); return ( {/* 卡片头部 */}
{t('账户管理')}
{t('账户绑定、安全设置和身份验证')}
{/* 账户绑定 Tab */} {t('账户绑定')} } itemKey='binding' >
{/* 邮箱绑定 */}
{t('邮箱')}
{renderAccountInfo( userState.user?.email, t('邮箱地址'), )}
{/* 微信绑定 */}
{t('微信')}
{!status.wechat_login ? t('未启用') : isBound(userState.user?.wechat_id) ? t('已绑定') : t('未绑定')}
{/* GitHub绑定 */}
{t('GitHub')}
{renderAccountInfo( userState.user?.github_id, t('GitHub ID'), )}
{/* Discord绑定 */}
{t('Discord')}
{renderAccountInfo( userState.user?.discord_id, t('Discord ID'), )}
{/* OIDC绑定 */}
{t('OIDC')}
{renderAccountInfo( userState.user?.oidc_id, t('OIDC ID'), )}
{/* Telegram绑定 */}
{t('Telegram')}
{renderAccountInfo( userState.user?.telegram_id, t('Telegram ID'), )}
{status.telegram_oauth ? ( isBound(userState.user?.telegram_id) ? ( ) : ( ) ) : ( )}
setShowTelegramBindModal(false)} footer={null} >
{t('点击下方按钮通过 Telegram 完成绑定')}
{/* LinuxDO绑定 */}
{t('LinuxDO')}
{renderAccountInfo( userState.user?.linux_do_id, t('LinuxDO ID'), )}
{/* 自定义 OAuth 提供商绑定 */} {status.custom_oauth_providers && status.custom_oauth_providers.map((provider) => { const bound = isCustomOAuthBound(provider.id); const binding = getCustomOAuthBinding(provider.id); return (
{getOAuthProviderIcon( provider.icon || binding?.provider_icon || '', 20, )}
{provider.name}
{bound ? renderAccountInfo( binding?.provider_user_id, t('{{name}} ID', { name: provider.name }), ) : t('未绑定')}
{bound ? ( ) : ( )}
); })}
{/* 安全设置 Tab */} {t('安全设置')} } itemKey='security' >
{/* 系统访问令牌 */}
{t('系统访问令牌')} {t('用于API调用的身份验证令牌,请妥善保管')} {systemToken && (
} />
)}
{/* 密码管理 */}
{t('密码管理')} {t('定期更改密码可以提高账户安全性')}
{/* Passkey 设置 */}
{t('Passkey 登录')} {passkeyEnabled ? t('已启用 Passkey,无需密码即可登录') : t('使用 Passkey 实现免密且更安全的登录体验')}
{t('最后使用时间')}:{lastUsedLabel}
{/*{passkeyEnabled && (*/} {/*
*/} {/* {t('备份支持')}:*/} {/* {passkeyStatus?.backup_eligible*/} {/* ? t('支持备份')*/} {/* : t('不支持')}*/} {/* ,{t('备份状态')}:*/} {/* {passkeyStatus?.backup_state ? t('已备份') : t('未备份')}*/} {/*
*/} {/*)}*/} {!passkeySupported && (
{t('当前设备不支持 Passkey')}
)}
{/* 两步验证设置 */} {/* 危险区域 */}
{t('删除账户')} {t('此操作不可逆,所有数据将被永久删除')}
); }; export default AccountManagement;