/* 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, { useEffect, useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { API, showError, showSuccess, renderQuota, renderQuotaWithPrompt, getCurrencyConfig, } from '../../../../helpers'; import { quotaToDisplayAmount, displayAmountToQuota, } from '../../../../helpers/quota'; import { useIsMobile } from '../../../../hooks/common/useIsMobile'; import { Button, Modal, SideSheet, Space, Spin, Typography, Card, Tag, Form, Avatar, Row, Col, InputNumber, } from '@douyinfe/semi-ui'; import { IconUser, IconSave, IconClose, IconLink, IconUserGroup, IconPlus, } from '@douyinfe/semi-icons'; import UserBindingManagementModal from './UserBindingManagementModal'; const { Text, Title } = Typography; const EditUserModal = (props) => { const { t } = useTranslation(); const userId = props.editingUser.id; const [loading, setLoading] = useState(true); const [addQuotaModalOpen, setIsModalOpen] = useState(false); const [addQuotaLocal, setAddQuotaLocal] = useState(''); const [addAmountLocal, setAddAmountLocal] = useState(''); const isMobile = useIsMobile(); const [groupOptions, setGroupOptions] = useState([]); const [bindingModalVisible, setBindingModalVisible] = useState(false); const formApiRef = useRef(null); const isEdit = Boolean(userId); const getInitValues = () => ({ username: '', display_name: '', password: '', github_id: '', oidc_id: '', discord_id: '', wechat_id: '', telegram_id: '', linux_do_id: '', email: '', quota: 0, group: 'default', remark: '', }); const fetchGroups = async () => { try { let res = await API.get(`/api/group/`); setGroupOptions(res.data.data.map((g) => ({ label: g, value: g }))); } catch (e) { showError(e.message); } }; const handleCancel = () => props.handleClose(); const loadUser = async () => { setLoading(true); const url = userId ? `/api/user/${userId}` : `/api/user/self`; const res = await API.get(url); const { success, message, data } = res.data; if (success) { data.password = ''; formApiRef.current?.setValues({ ...getInitValues(), ...data }); } else { showError(message); } setLoading(false); }; useEffect(() => { loadUser(); if (userId) fetchGroups(); setBindingModalVisible(false); }, [props.editingUser.id]); const openBindingModal = () => { setBindingModalVisible(true); }; const closeBindingModal = () => { setBindingModalVisible(false); }; /* ----------------------- submit ----------------------- */ const submit = async (values) => { setLoading(true); let payload = { ...values }; if (typeof payload.quota === 'string') payload.quota = parseInt(payload.quota) || 0; if (userId) { payload.id = parseInt(userId); } const url = userId ? `/api/user/` : `/api/user/self`; const res = await API.put(url, payload); const { success, message } = res.data; if (success) { showSuccess(t('用户信息更新成功!')); props.refresh(); props.handleClose(); } else { showError(message); } setLoading(false); }; /* --------------------- quota helper -------------------- */ const addLocalQuota = () => { const current = parseInt(formApiRef.current?.getValue('quota') || 0); const delta = parseInt(addQuotaLocal) || 0; formApiRef.current?.setValue('quota', current + delta); }; /* --------------------------- UI --------------------------- */ return ( <> {t(isEdit ? '编辑' : '新建')} {isEdit ? t('编辑用户') : t('创建用户')} } bodyStyle={{ padding: 0 }} visible={props.visible} width={isMobile ? '100%' : 600} footer={ formApiRef.current?.submitForm()} icon={} loading={loading} > {t('提交')} } > {t('取消')} } closeIcon={null} onCancel={handleCancel} > (formApiRef.current = api)} onSubmit={submit} > {({ values }) => ( {/* 基本信息 */} {t('基本信息')} {t('用户的基本账户信息')} {/* 权限设置 */} {userId && ( {t('权限设置')} {t('用户分组和额度管理')} } onClick={() => setIsModalOpen(true)} /> )} {/* 绑定信息入口 */} {userId && ( {t('绑定信息')} {t('管理用户已绑定的第三方账户,支持筛选与解绑')} {t('管理绑定')} )} )} {/* 添加额度模态框 */} { addLocalQuota(); setIsModalOpen(false); setAddQuotaLocal(''); setAddAmountLocal(''); }} onCancel={() => { setIsModalOpen(false); }} closable={null} title={ {t('添加额度')} } > {(() => { const current = formApiRef.current?.getValue('quota') || 0; return ( {`${t('新额度:')}${renderQuota(current)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(current + parseInt(addQuotaLocal || 0))}`} ); })()} {getCurrencyConfig().type !== 'TOKENS' && ( {t('金额')} {' '} ({t('仅用于换算,实际保存的是额度')}) { setAddAmountLocal(val); setAddQuotaLocal( val != null && val !== '' ? displayAmountToQuota(Math.abs(val)) * Math.sign(val) : '', ); }} style={{ width: '100%' }} showClear /> )} {t('额度')} { setAddQuotaLocal(val); setAddAmountLocal( val != null && val !== '' ? Number( ( quotaToDisplayAmount(Math.abs(val)) * Math.sign(val) ).toFixed(2), ) : '', ); }} style={{ width: '100%' }} showClear step={500000} /> > ); }; export default EditUserModal;