♻️ refactor(web): migrate React modules from .js to .jsx and align entrypoint
- Rename React components/pages/utilities that contain JSX to `.jsx` across `web/src` - Update import paths and re-exports to match new `.jsx` extensions - Fix Vite entry by switching `web/index.html` from `/src/index.js` to `/src/index.jsx` - Verified remaining `.js` files are plain JS (hooks/helpers/constants) and do not require JSX - No runtime behavior changes; extension and reference alignment only Context: Resolves the Vite pre-transform error caused by the stale `/src/index.js` entry after migrating to `.jsx`.
This commit is contained in:
417
web/src/components/settings/PersonalSetting.jsx
Normal file
417
web/src/components/settings/PersonalSetting.jsx
Normal file
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
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, { useContext, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
API,
|
||||
copy,
|
||||
showError,
|
||||
showInfo,
|
||||
showSuccess
|
||||
} from '../../helpers';
|
||||
import { UserContext } from '../../context/User';
|
||||
import { Modal } from '@douyinfe/semi-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// 导入子组件
|
||||
import UserInfoHeader from './personal/components/UserInfoHeader';
|
||||
import AccountManagement from './personal/cards/AccountManagement';
|
||||
import NotificationSettings from './personal/cards/NotificationSettings';
|
||||
import EmailBindModal from './personal/modals/EmailBindModal';
|
||||
import WeChatBindModal from './personal/modals/WeChatBindModal';
|
||||
import AccountDeleteModal from './personal/modals/AccountDeleteModal';
|
||||
import ChangePasswordModal from './personal/modals/ChangePasswordModal';
|
||||
|
||||
const PersonalSetting = () => {
|
||||
const [userState, userDispatch] = useContext(UserContext);
|
||||
let navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [inputs, setInputs] = useState({
|
||||
wechat_verification_code: '',
|
||||
email_verification_code: '',
|
||||
email: '',
|
||||
self_account_deletion_confirmation: '',
|
||||
original_password: '',
|
||||
set_new_password: '',
|
||||
set_new_password_confirmation: '',
|
||||
});
|
||||
const [status, setStatus] = useState({});
|
||||
const [showChangePasswordModal, setShowChangePasswordModal] = useState(false);
|
||||
const [showWeChatBindModal, setShowWeChatBindModal] = useState(false);
|
||||
const [showEmailBindModal, setShowEmailBindModal] = useState(false);
|
||||
const [showAccountDeleteModal, setShowAccountDeleteModal] = useState(false);
|
||||
const [turnstileEnabled, setTurnstileEnabled] = useState(false);
|
||||
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
|
||||
const [turnstileToken, setTurnstileToken] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [disableButton, setDisableButton] = useState(false);
|
||||
const [countdown, setCountdown] = useState(30);
|
||||
const [systemToken, setSystemToken] = useState('');
|
||||
const [models, setModels] = useState([]);
|
||||
const [notificationSettings, setNotificationSettings] = useState({
|
||||
warningType: 'email',
|
||||
warningThreshold: 100000,
|
||||
webhookUrl: '',
|
||||
webhookSecret: '',
|
||||
notificationEmail: '',
|
||||
acceptUnsetModelRatioModel: false,
|
||||
recordIpLog: false,
|
||||
});
|
||||
const [modelsLoading, setModelsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let status = localStorage.getItem('status');
|
||||
if (status) {
|
||||
status = JSON.parse(status);
|
||||
setStatus(status);
|
||||
if (status.turnstile_check) {
|
||||
setTurnstileEnabled(true);
|
||||
setTurnstileSiteKey(status.turnstile_site_key);
|
||||
}
|
||||
}
|
||||
getUserData().then((res) => {
|
||||
console.log(userState);
|
||||
});
|
||||
loadModels().then();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let countdownInterval = null;
|
||||
if (disableButton && countdown > 0) {
|
||||
countdownInterval = setInterval(() => {
|
||||
setCountdown(countdown - 1);
|
||||
}, 1000);
|
||||
} else if (countdown === 0) {
|
||||
setDisableButton(false);
|
||||
setCountdown(30);
|
||||
}
|
||||
return () => clearInterval(countdownInterval); // Clean up on unmount
|
||||
}, [disableButton, countdown]);
|
||||
|
||||
useEffect(() => {
|
||||
if (userState?.user?.setting) {
|
||||
const settings = JSON.parse(userState.user.setting);
|
||||
setNotificationSettings({
|
||||
warningType: settings.notify_type || 'email',
|
||||
warningThreshold: settings.quota_warning_threshold || 500000,
|
||||
webhookUrl: settings.webhook_url || '',
|
||||
webhookSecret: settings.webhook_secret || '',
|
||||
notificationEmail: settings.notification_email || '',
|
||||
acceptUnsetModelRatioModel:
|
||||
settings.accept_unset_model_ratio_model || false,
|
||||
recordIpLog: settings.record_ip_log || false,
|
||||
});
|
||||
}
|
||||
}, [userState?.user?.setting]);
|
||||
|
||||
const handleInputChange = (name, value) => {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
};
|
||||
|
||||
const generateAccessToken = async () => {
|
||||
const res = await API.get('/api/user/token');
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setSystemToken(data);
|
||||
await copy(data);
|
||||
showSuccess(t('令牌已重置并已复制到剪贴板'));
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
};
|
||||
|
||||
const getUserData = async () => {
|
||||
let res = await API.get(`/api/user/self`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
};
|
||||
|
||||
const loadModels = async () => {
|
||||
setModelsLoading(true);
|
||||
|
||||
try {
|
||||
let res = await API.get(`/api/user/models`);
|
||||
const { success, message, data } = res.data;
|
||||
|
||||
if (success) {
|
||||
if (data != null) {
|
||||
setModels(data);
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('加载模型列表失败'));
|
||||
} finally {
|
||||
setModelsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSystemTokenClick = async (e) => {
|
||||
e.target.select();
|
||||
await copy(e.target.value);
|
||||
showSuccess(t('系统令牌已复制到剪切板'));
|
||||
};
|
||||
|
||||
const deleteAccount = async () => {
|
||||
if (inputs.self_account_deletion_confirmation !== userState.user.username) {
|
||||
showError(t('请输入你的账户名以确认删除!'));
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await API.delete('/api/user/self');
|
||||
const { success, message } = res.data;
|
||||
|
||||
if (success) {
|
||||
showSuccess(t('账户已删除!'));
|
||||
await API.get('/api/user/logout');
|
||||
userDispatch({ type: 'logout' });
|
||||
localStorage.removeItem('user');
|
||||
navigate('/login');
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
};
|
||||
|
||||
const bindWeChat = async () => {
|
||||
if (inputs.wechat_verification_code === '') return;
|
||||
const res = await API.get(
|
||||
`/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}`,
|
||||
);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(t('微信账户绑定成功!'));
|
||||
setShowWeChatBindModal(false);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
};
|
||||
|
||||
const changePassword = async () => {
|
||||
if (inputs.original_password === '') {
|
||||
showError(t('请输入原密码!'));
|
||||
return;
|
||||
}
|
||||
if (inputs.set_new_password === '') {
|
||||
showError(t('请输入新密码!'));
|
||||
return;
|
||||
}
|
||||
if (inputs.original_password === inputs.set_new_password) {
|
||||
showError(t('新密码需要和原密码不一致!'));
|
||||
return;
|
||||
}
|
||||
if (inputs.set_new_password !== inputs.set_new_password_confirmation) {
|
||||
showError(t('两次输入的密码不一致!'));
|
||||
return;
|
||||
}
|
||||
const res = await API.put(`/api/user/self`, {
|
||||
original_password: inputs.original_password,
|
||||
password: inputs.set_new_password,
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(t('密码修改成功!'));
|
||||
setShowWeChatBindModal(false);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setShowChangePasswordModal(false);
|
||||
};
|
||||
|
||||
const sendVerificationCode = async () => {
|
||||
if (inputs.email === '') {
|
||||
showError(t('请输入邮箱!'));
|
||||
return;
|
||||
}
|
||||
setDisableButton(true);
|
||||
if (turnstileEnabled && turnstileToken === '') {
|
||||
showInfo(t('请稍后几秒重试,Turnstile 正在检查用户环境!'));
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const res = await API.get(
|
||||
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`,
|
||||
);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(t('验证码发送成功,请检查邮箱!'));
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const bindEmail = async () => {
|
||||
if (inputs.email_verification_code === '') {
|
||||
showError(t('请输入邮箱验证码!'));
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const res = await API.get(
|
||||
`/api/oauth/email/bind?email=${inputs.email}&code=${inputs.email_verification_code}`,
|
||||
);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(t('邮箱账户绑定成功!'));
|
||||
setShowEmailBindModal(false);
|
||||
userState.user.email = inputs.email;
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const copyText = async (text) => {
|
||||
if (await copy(text)) {
|
||||
showSuccess(t('已复制:') + text);
|
||||
} else {
|
||||
// setSearchKeyword(text);
|
||||
Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text });
|
||||
}
|
||||
};
|
||||
|
||||
const handleNotificationSettingChange = (type, value) => {
|
||||
setNotificationSettings((prev) => ({
|
||||
...prev,
|
||||
[type]: value.target ? value.target.value !== undefined ? value.target.value : value.target.checked : value, // handle checkbox properly
|
||||
}));
|
||||
};
|
||||
|
||||
const saveNotificationSettings = async () => {
|
||||
try {
|
||||
const res = await API.put('/api/user/setting', {
|
||||
notify_type: notificationSettings.warningType,
|
||||
quota_warning_threshold: parseFloat(
|
||||
notificationSettings.warningThreshold,
|
||||
),
|
||||
webhook_url: notificationSettings.webhookUrl,
|
||||
webhook_secret: notificationSettings.webhookSecret,
|
||||
notification_email: notificationSettings.notificationEmail,
|
||||
accept_unset_model_ratio_model:
|
||||
notificationSettings.acceptUnsetModelRatioModel,
|
||||
record_ip_log: notificationSettings.recordIpLog,
|
||||
});
|
||||
|
||||
if (res.data.success) {
|
||||
showSuccess(t('设置保存成功'));
|
||||
await getUserData();
|
||||
} else {
|
||||
showError(res.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('设置保存失败'));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-[60px]">
|
||||
<div className="flex justify-center">
|
||||
<div className="w-full max-w-7xl mx-auto px-2">
|
||||
{/* 顶部用户信息区域 */}
|
||||
<UserInfoHeader t={t} userState={userState} />
|
||||
|
||||
{/* 账户管理和其他设置 */}
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 items-start gap-4 md:gap-6 mt-4 md:mt-6">
|
||||
{/* 左侧:账户管理设置 */}
|
||||
<AccountManagement
|
||||
t={t}
|
||||
userState={userState}
|
||||
status={status}
|
||||
systemToken={systemToken}
|
||||
setShowEmailBindModal={setShowEmailBindModal}
|
||||
setShowWeChatBindModal={setShowWeChatBindModal}
|
||||
generateAccessToken={generateAccessToken}
|
||||
handleSystemTokenClick={handleSystemTokenClick}
|
||||
setShowChangePasswordModal={setShowChangePasswordModal}
|
||||
setShowAccountDeleteModal={setShowAccountDeleteModal}
|
||||
/>
|
||||
|
||||
{/* 右侧:其他设置 */}
|
||||
<NotificationSettings
|
||||
t={t}
|
||||
notificationSettings={notificationSettings}
|
||||
handleNotificationSettingChange={handleNotificationSettingChange}
|
||||
saveNotificationSettings={saveNotificationSettings}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 模态框组件 */}
|
||||
<EmailBindModal
|
||||
t={t}
|
||||
showEmailBindModal={showEmailBindModal}
|
||||
setShowEmailBindModal={setShowEmailBindModal}
|
||||
inputs={inputs}
|
||||
handleInputChange={handleInputChange}
|
||||
sendVerificationCode={sendVerificationCode}
|
||||
bindEmail={bindEmail}
|
||||
disableButton={disableButton}
|
||||
loading={loading}
|
||||
countdown={countdown}
|
||||
turnstileEnabled={turnstileEnabled}
|
||||
turnstileSiteKey={turnstileSiteKey}
|
||||
setTurnstileToken={setTurnstileToken}
|
||||
/>
|
||||
|
||||
<WeChatBindModal
|
||||
t={t}
|
||||
showWeChatBindModal={showWeChatBindModal}
|
||||
setShowWeChatBindModal={setShowWeChatBindModal}
|
||||
inputs={inputs}
|
||||
handleInputChange={handleInputChange}
|
||||
bindWeChat={bindWeChat}
|
||||
status={status}
|
||||
/>
|
||||
|
||||
<AccountDeleteModal
|
||||
t={t}
|
||||
showAccountDeleteModal={showAccountDeleteModal}
|
||||
setShowAccountDeleteModal={setShowAccountDeleteModal}
|
||||
inputs={inputs}
|
||||
handleInputChange={handleInputChange}
|
||||
deleteAccount={deleteAccount}
|
||||
userState={userState}
|
||||
turnstileEnabled={turnstileEnabled}
|
||||
turnstileSiteKey={turnstileSiteKey}
|
||||
setTurnstileToken={setTurnstileToken}
|
||||
/>
|
||||
|
||||
<ChangePasswordModal
|
||||
t={t}
|
||||
showChangePasswordModal={showChangePasswordModal}
|
||||
setShowChangePasswordModal={setShowChangePasswordModal}
|
||||
inputs={inputs}
|
||||
handleInputChange={handleInputChange}
|
||||
changePassword={changePassword}
|
||||
turnstileEnabled={turnstileEnabled}
|
||||
turnstileSiteKey={turnstileSiteKey}
|
||||
setTurnstileToken={setTurnstileToken}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PersonalSetting;
|
||||
Reference in New Issue
Block a user