refactor(perferences): use center wrapper layout

This commit is contained in:
jialin
2026-06-29 19:44:28 +08:00
committed by jialin
parent b8d7873b77
commit f719c11606
19 changed files with 684 additions and 144 deletions
+7
View File
@@ -198,6 +198,8 @@ export default {
'common.table.user': 'User',
'common.settings.instructions': 'Instructions',
'common.settings.language': 'Language',
'common.settings.language.tips':
'Set the display language for the interface.',
'common.delete.confirm':
'Are you sure you want to delete the selected {type}?',
'common.delete.single.confirm':
@@ -251,6 +253,11 @@ export default {
'common.appearance.tips': 'Default follows system preference.',
'common.button.forgotpassword': 'Forgot password?',
'common.appearance.theme': 'Theme',
'common.appearance.description':
'Customize how the interface looks on your device.',
'common.security': 'Security',
'common.security.description':
'Manage the password used to sign in to your account.',
'common.page.wentwrong': 'Something went wrong.',
'common.page.refresh.tips':
'The page may need to be updated. Try refreshing it!',
+2
View File
@@ -28,6 +28,8 @@ export default {
'users.password.modify.title': 'Modify Password',
'users.password.modify.description':
"For your account's security, please change your initial password.",
'users.password.modify.tips':
'Regularly updating your password helps keep your account secure.',
'users.password.confirm': 'Confirm New Password',
'users.password.confirm.empty': 'Please confirm the new password.',
'users.password.confirm.error': 'The two passwords entered do not match.',
+6
View File
@@ -199,6 +199,7 @@ export default {
'common.table.user': 'ユーザー',
'common.settings.instructions': '手順',
'common.settings.language': '言語',
'common.settings.language.tips': 'インターフェースの表示言語を設定します。',
'common.delete.confirm': '選択した {type} を削除してもよろしいですか?',
'common.delete.single.confirm':
'<span style="font-size: 13px;font-weight: 700">{name}</span> を削除してもよろしいですか?',
@@ -251,6 +252,11 @@ export default {
'common.appearance.tips': 'Default follows system preference.',
'common.button.forgotpassword': 'Forgot password?',
'common.appearance.theme': 'Theme',
'common.appearance.description':
'デバイス上でのインターフェースの表示をカスタマイズします。',
'common.security': 'セキュリティ',
'common.security.description':
'アカウントへのログインに使用するパスワードを管理します。',
'common.page.wentwrong': 'Something went wrong.',
'common.page.refresh.tips':
'The page may need to be updated. Try refreshing it!',
+2
View File
@@ -29,6 +29,8 @@ export default {
'users.password.modify.title': 'パスワードを変更',
'users.password.modify.description':
'アカウントのセキュリティのため、初期パスワードを変更してください。',
'users.password.modify.tips':
'パスワードを定期的に更新すると、アカウントの安全を保てます。',
'users.password.confirm': '新しいパスワードを確認',
'users.password.confirm.empty': '新しいパスワードを確認してください。',
'users.password.confirm.error': '入力された2つのパスワードが一致しません。',
+6
View File
@@ -197,6 +197,7 @@ export default {
'common.table.user': 'Пользователь',
'common.settings.instructions': 'Инструкции',
'common.settings.language': 'Язык',
'common.settings.language.tips': 'Задайте язык отображения интерфейса.',
'common.delete.confirm': 'Вы уверены, что хотите удалить выбранный {type}?',
'common.delete.single.confirm':
'Вы уверены, что хотите удалить <span style="font-size: 13px;font-weight: 700">{name}</span>?',
@@ -250,6 +251,11 @@ export default {
'common.appearance.tips': 'По умолчанию соответствует системным настройкам.',
'common.button.forgotpassword': 'Забыли пароль?',
'common.appearance.theme': 'Тема',
'common.appearance.description':
'Настройте внешний вид интерфейса на вашем устройстве.',
'common.security': 'Безопасность',
'common.security.description':
'Управляйте паролем для входа в учётную запись.',
'common.page.wentwrong': 'Что-то пошло не так.',
'common.page.refresh.tips':
'Страница может нуждаться в обновлении. Попробуйте обновить её!',
+2
View File
@@ -29,6 +29,8 @@ export default {
'users.password.modify.title': 'Смена пароля',
'users.password.modify.description':
'В целях безопасности измените первоначальный пароль.',
'users.password.modify.tips':
'Регулярное обновление пароля помогает защитить вашу учётную запись.',
'users.password.confirm': 'Подтвердите новый пароль',
'users.password.confirm.empty': 'Подтвердите новый пароль',
'users.password.confirm.error': 'Пароли не совпадают',
+6
View File
@@ -199,6 +199,7 @@ export default {
'common.table.user': 'Kullanıcı',
'common.settings.instructions': 'Talimatlar',
'common.settings.language': 'Dil',
'common.settings.language.tips': 'Arayüzün görüntüleme dilini ayarlayın.',
'common.delete.confirm':
'Seçili {type} öğesini silmek istediğinizden emin misiniz?',
'common.delete.single.confirm':
@@ -254,6 +255,11 @@ export default {
'common.appearance.tips': 'Varsayılan olarak sistem tercihini takip eder.',
'common.button.forgotpassword': 'Şifrenizi mi unuttunuz?',
'common.appearance.theme': 'Tema',
'common.appearance.description':
'Arayüzün cihazınızdaki görünümünü özelleştirin.',
'common.security': 'Güvenlik',
'common.security.description':
'Hesabınıza giriş yapmak için kullanılan parolayı yönetin.',
'common.page.wentwrong': 'Bir şeyler ters gitti.',
'common.page.refresh.tips':
'Sayfanın güncellenmesi gerekebilir. Yenilemeyi deneyin!',
+2
View File
@@ -29,6 +29,8 @@ export default {
'users.password.modify.title': 'Şifreyi Değiştir',
'users.password.modify.description':
'Hesabınızın güvenliği için lütfen başlangıç şifrenizi değiştirin.',
'users.password.modify.tips':
'Parolanızı düzenli olarak güncellemek hesabınızın güvenliğini korumaya yardımcı olur.',
'users.password.confirm': 'Yeni Şifreyi Onayla',
'users.password.confirm.empty': 'Lütfen yeni şifreyi tekrar girin.',
'users.password.confirm.error': 'Girilen iki şifre eşleşmiyor.',
+4
View File
@@ -190,6 +190,7 @@ export default {
'common.table.user': '用户',
'common.settings.instructions': '操作指引',
'common.settings.language': '语言',
'common.settings.language.tips': '设置界面的显示语言。',
'common.delete.confirm': '确定删除选中的{type}吗?',
'common.delete.single.confirm':
'确定删除以下内容?\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
@@ -246,6 +247,9 @@ export default {
'common.appearance.tips': '默认跟随系统设置',
'common.button.forgotpassword': '忘记密码?',
'common.appearance.theme': '主题',
'common.appearance.description': '自定义界面在您设备上的视觉表现。',
'common.security': '安全设置',
'common.security.description': '管理用于登录账户的密码。',
'common.page.wentwrong': '哎呀,出了点问题',
'common.page.refresh.tips': '页面似乎需要更新,刷新一下试试吧!',
'common.tips.escape.disable': '请点击「取消」按钮或右上角 X 关闭窗口',
+1
View File
@@ -27,6 +27,7 @@ export default {
'users.password.length': '长度在6至64个字符之间',
'users.password.modify.title': '修改密码',
'users.password.modify.description': '为了确保您的账户安全,请修改初始密码',
'users.password.modify.tips': '定期更新密码有助于保护账户安全。',
'users.password.confirm': '确认新密码',
'users.password.confirm.empty': '请确认新密码',
'users.password.confirm.error': '两次输入的密码不一致',
+114 -56
View File
@@ -1,92 +1,150 @@
import useUserSettings from '@/hooks/use-user-settings';
import langConfigMap from '@/locales/lang-config-map';
import { MoonOutlined, SunOutlined } from '@ant-design/icons';
import { CheckCircleFilled } from '@ant-design/icons';
import { BaseSelect } from '@gpustack/core-ui';
import { getAllLocales, setLocale, useIntl } from '@umijs/max';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import _ from 'lodash';
import React from 'react';
import styled from 'styled-components';
const Wrapper = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px 0;
`;
import { SettingRow, SettingsGroup } from './settings-group';
import ThemePreview, { PreviewMode } from './theme-preview';
const SettingsItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
max-width: 300px;
.label {
const useStyles = createStyles(({ token, css }) => ({
cards: css`
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
`,
card: css`
padding: 8px;
border: 1px solid ${token.colorBorderSecondary};
border-radius: ${token.borderRadiusLG + 2}px;
background: ${token.colorBgContainer};
cursor: pointer;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:hover {
border-color: ${token.colorPrimaryBorderHover};
}
`,
cardActive: css`
border-color: ${token.colorPrimary};
box-shadow: 0 0 0 1px ${token.colorPrimary};
`,
meta: css`
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 8px 6px;
`,
label: css`
font-size: 14px;
font-weight: var(--font-weight-500);
}
`;
font-weight: var(--font-weight-medium);
color: ${token.colorText};
`,
check: css`
font-size: 18px;
color: ${token.colorPrimary};
`,
radio: css`
width: 16px;
height: 16px;
border-radius: 50%;
border: 1.5px solid ${token.colorBorder};
`
}));
const Appearance: React.FC = () => {
const { setTheme, userSettings } = useUserSettings();
const intl = useIntl();
const { styles } = useStyles();
const allLocals = getAllLocales();
const ThemeOptions = [
const themeOptions: { value: PreviewMode; label: string }[] = [
{
value: 'light',
label: intl.formatMessage({ id: 'common.appearance.light' }),
icon: <SunOutlined />
label: intl.formatMessage({ id: 'common.appearance.lightmode' })
},
{
value: 'realDark',
label: intl.formatMessage({ id: 'common.appearance.dark' }),
icon: <MoonOutlined />
label: intl.formatMessage({ id: 'common.appearance.darkmode' })
},
{
value: 'auto',
label: intl.formatMessage({ id: 'common.appearance.system' }),
icon: <SunOutlined />
label: intl.formatMessage({ id: 'common.appearance.system' })
}
];
const handleOnChange = (value: 'light' | 'realDark' | 'auto') => {
setTheme(value);
};
const languageOptions = allLocals.map((locale) => ({
value: locale,
label: _.get(langConfigMap, [locale, 'label'])
}));
const handleSelectTheme = (value: PreviewMode) => {
setTheme(value);
};
return (
<Wrapper>
<SettingsItem>
<span className="label">
<span>{intl.formatMessage({ id: 'common.appearance.theme' })}</span>
</span>
<BaseSelect
defaultValue={'light'}
value={userSettings.mode}
options={ThemeOptions}
onChange={handleOnChange}
style={{ width: 200 }}
></BaseSelect>
</SettingsItem>
<SettingsItem>
<span className="label">
<span>{intl.formatMessage({ id: 'common.settings.language' })}</span>
</span>
<BaseSelect
value={intl.locale}
options={languageOptions}
onChange={(value) => {
setLocale(value, false);
}}
style={{ width: 200 }}
></BaseSelect>
</SettingsItem>
</Wrapper>
<SettingsGroup>
<SettingRow
title={intl.formatMessage({ id: 'common.appearance.theme' })}
description={intl.formatMessage({ id: 'common.appearance.tips' })}
>
<div className={styles.cards}>
{themeOptions.map((option) => {
const active = userSettings.mode === option.value;
return (
<div
key={option.value}
role="radio"
aria-checked={active}
tabIndex={0}
className={classNames(styles.card, {
[styles.cardActive]: active
})}
onClick={() => handleSelectTheme(option.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleSelectTheme(option.value);
}
}}
>
<ThemePreview mode={option.value} />
<div className={styles.meta}>
<span className={styles.label}>{option.label}</span>
{active ? (
<CheckCircleFilled className={styles.check} />
) : (
<span className={styles.radio} />
)}
</div>
</div>
);
})}
</div>
</SettingRow>
<SettingRow
title={intl.formatMessage({ id: 'common.settings.language' })}
description={intl.formatMessage({
id: 'common.settings.language.tips'
})}
extra={
<BaseSelect
value={intl.locale}
options={languageOptions}
onChange={(value: string) => {
setLocale(value, false);
}}
style={{ width: 200 }}
/>
}
/>
</SettingsGroup>
);
};
@@ -0,0 +1,132 @@
import { PasswordReg } from '@/config';
import { INPUT_WIDTH } from '@/constants';
import useSubmitLock from '@/hooks/use-submit-lock';
import { updatePassword } from '@/pages/login/apis';
import { Input as CInput, FormButtons } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Form, message } from 'antd';
import React from 'react';
interface FormData {
new_password: string;
current_password: string;
confirm_password?: string;
}
interface ModifyPasswordFormProps {
onCancel: () => void;
onSuccess?: () => void;
}
const ModifyPasswordForm: React.FC<ModifyPasswordFormProps> = ({
onCancel,
onSuccess
}) => {
const [form] = Form.useForm();
const intl = useIntl();
const { guard, run, release } = useSubmitLock();
const handleSubmit = () => {
guard(() => form.submit());
};
const onFinish = async (values: FormData) => {
await run(async () => {
await updatePassword({
new_password: values.new_password,
current_password: values.current_password
});
message.success(intl.formatMessage({ id: 'common.message.success' }));
onSuccess?.();
});
};
return (
<Form
name="modifyPasswordForm"
form={form}
onFinish={onFinish}
onFinishFailed={release}
preserve={false}
>
<Form.Item<FormData>
name="current_password"
rules={[
{
required: true,
message: intl.formatMessage(
{ id: 'common.form.rule.input' },
{
name: intl.formatMessage({
id: 'users.form.currentpassword'
})
}
)
}
]}
>
<CInput.Password
autoComplete="current-password"
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
/>
</Form.Item>
<Form.Item<FormData>
name="new_password"
rules={[
{
required: true,
pattern: PasswordReg,
message: intl.formatMessage({
id: 'users.form.rule.password'
})
}
]}
>
<CInput.Password
autoComplete="new-password"
label={intl.formatMessage({ id: 'users.form.newpassword' })}
required
style={{ width: INPUT_WIDTH.default }}
/>
</Form.Item>
<Form.Item
name="confirm_password"
dependencies={['new_password']}
rules={[
{
required: true,
message: intl.formatMessage({
id: 'users.password.confirm.empty'
})
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('new_password') === value) {
return Promise.resolve();
}
return Promise.reject(
new Error(
intl.formatMessage({ id: 'users.password.confirm.error' })
)
);
}
})
]}
>
<CInput.Password
required
autoComplete="new-password"
style={{ width: INPUT_WIDTH.default }}
label={intl.formatMessage({ id: 'users.password.confirm' })}
/>
</Form.Item>
<FormButtons htmlType="submit" onCancel={onCancel} showCancel />
</Form>
);
};
ModifyPasswordForm.displayName = 'ModifyPasswordForm';
export default ModifyPasswordForm;
@@ -1,36 +0,0 @@
import { INPUT_WIDTH } from '@/constants';
import { Textarea } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Button, Form } from 'antd';
import React from 'react';
interface PublicKeyFormData {
public_key?: string;
}
const PublicKey: React.FC = () => {
const intl = useIntl();
const [form] = Form.useForm<PublicKeyFormData>();
return (
<Form style={{ width: '524px' }} name="publicKeyForm" form={form}>
<Form.Item<PublicKeyFormData> name="public_key">
<Textarea
label="SSH 公钥"
placeholder="将您的 SSH 公钥粘贴到此处"
trim={false}
alwaysFocus
autoSize={{ minRows: 4, maxRows: 8 }}
style={{ width: INPUT_WIDTH.default }}
></Textarea>
</Form.Item>
<Button type="primary" style={{ width: 120, marginTop: 100 }}>
{intl.formatMessage({ id: 'common.button.save' })}
</Button>
</Form>
);
};
PublicKey.displayName = 'PublicKey';
export default PublicKey;
+39
View File
@@ -0,0 +1,39 @@
import { useIntl } from '@umijs/max';
import { Button } from 'antd';
import React, { useState } from 'react';
import ModifyPasswordForm from './modify-password-form';
import { SettingRow, SettingsGroup } from './settings-group';
const Security: React.FC = () => {
const intl = useIntl();
const [open, setOpen] = useState(false);
return (
<SettingsGroup>
<SettingRow
title={intl.formatMessage({ id: 'users.form.updatepassword' })}
description={intl.formatMessage({
id: 'users.password.modify.tips'
})}
extra={
!open && (
<Button onClick={() => setOpen(true)}>
{intl.formatMessage({ id: 'users.form.updatepassword' })}
</Button>
)
}
>
{open && (
<ModifyPasswordForm
onCancel={() => setOpen(false)}
onSuccess={() => setOpen(false)}
/>
)}
</SettingRow>
</SettingsGroup>
);
};
Security.displayName = 'Security';
export default Security;
@@ -0,0 +1,84 @@
import { createStyles } from 'antd-style';
import React from 'react';
const useStyles = createStyles(({ token, css }) => ({
group: css`
/* Match the page panel surface (page-box.less): 8px radius + the
lighter container border, rather than the heavier component token. */
border: 1px solid ${token.colorBorder};
border-radius: 8px;
background: ${token.colorBgContainer};
overflow: hidden;
`,
row: css`
padding: 16px 20px;
& + & {
border-top: 1px solid ${token.colorBorderSecondary};
}
`,
head: css`
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
`,
info: css`
min-width: 0;
`,
title: css`
font-size: 14px;
font-weight: var(--font-weight-medium);
color: ${token.colorText};
line-height: 22px;
`,
description: css`
margin-top: 2px;
font-size: 13px;
color: ${token.colorTextTertiary};
line-height: 20px;
`,
extra: css`
flex-shrink: 0;
`,
body: css`
margin-top: 16px;
`
}));
export const SettingsGroup: React.FC<{ children: React.ReactNode }> = ({
children
}) => {
const { styles } = useStyles();
return <div className={styles.group}>{children}</div>;
};
interface SettingRowProps {
title: React.ReactNode;
description?: React.ReactNode;
extra?: React.ReactNode;
children?: React.ReactNode;
}
export const SettingRow: React.FC<SettingRowProps> = ({
title,
description,
extra,
children
}) => {
const { styles } = useStyles();
return (
<div className={styles.row}>
<div className={styles.head}>
<div className={styles.info}>
<div className={styles.title}>{title}</div>
{description && (
<div className={styles.description}>{description}</div>
)}
</div>
{extra && <div className={styles.extra}>{extra}</div>}
</div>
{children && <div className={styles.body}>{children}</div>}
</div>
);
};
@@ -0,0 +1,53 @@
import { createStyles } from 'antd-style';
import React from 'react';
const useStyles = createStyles(({ token, css }) => ({
section: css`
& + & {
margin-top: 40px;
}
`,
header: css`
margin-bottom: 16px;
`,
title: css`
margin: 0;
font-size: 16px;
font-weight: var(--font-weight-medium);
color: ${token.colorTextHeading};
line-height: 24px;
`,
description: css`
margin: 4px 0 0;
font-size: 13px;
color: ${token.colorTextTertiary};
line-height: 20px;
`
}));
interface SettingsSectionProps {
title: React.ReactNode;
description?: React.ReactNode;
children: React.ReactNode;
}
const SettingsSection: React.FC<SettingsSectionProps> = ({
title,
description,
children
}) => {
const { styles } = useStyles();
return (
<section className={styles.section}>
<div className={styles.header}>
<h3 className={styles.title}>{title}</h3>
{description && <p className={styles.description}>{description}</p>}
</div>
{children}
</section>
);
};
SettingsSection.displayName = 'SettingsSection';
export default SettingsSection;
@@ -0,0 +1,119 @@
import { createStyles } from 'antd-style';
import React from 'react';
export type PreviewMode = 'light' | 'realDark' | 'auto';
const PALETTE = {
light: {
surface: '#ffffff',
bar: '#e7ebf2',
block: '#f1f4f9',
accent: '#cdd7e8'
},
dark: {
surface: '#0f1729',
bar: '#1d2740',
block: '#27324d',
accent: '#39445f'
}
};
const useStyles = createStyles(({ css }) => ({
preview: css`
width: 100%;
aspect-ratio: 16 / 10;
border-radius: 8px;
overflow: hidden;
display: flex;
pointer-events: none;
user-select: none;
`,
half: css`
height: 100%;
display: flex;
flex-direction: column;
padding: 10px;
gap: 8px;
overflow: hidden;
`,
topbar: css`
height: 8px;
border-radius: 3px;
flex-shrink: 0;
`,
body: css`
flex: 1;
display: flex;
gap: 8px;
`,
sidebar: css`
width: 22%;
border-radius: 4px;
`,
main: css`
flex: 1;
border-radius: 4px;
`,
footer: css`
height: 6px;
border-radius: 3px;
flex-shrink: 0;
`,
systemIcon: css`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
color: rgba(255, 255, 255, 0.55);
mix-blend-mode: difference;
z-index: 2;
`,
systemWrap: css`
position: relative;
`
}));
const Mockup: React.FC<{ tone: 'light' | 'dark'; flex?: number }> = ({
tone,
flex = 1
}) => {
const { styles } = useStyles();
const c = PALETTE[tone];
return (
<div
className={styles.half}
style={{ background: c.surface, flex, minWidth: 0 }}
>
<div className={styles.topbar} style={{ background: c.accent }} />
<div className={styles.body}>
<div className={styles.sidebar} style={{ background: c.block }} />
<div className={styles.main} style={{ background: c.block }} />
</div>
<div className={styles.footer} style={{ background: c.bar }} />
</div>
);
};
const ThemePreview: React.FC<{ mode: PreviewMode }> = ({ mode }) => {
const { styles } = useStyles();
if (mode === 'auto') {
return (
<div className={`${styles.preview} ${styles.systemWrap}`}>
<Mockup tone="light" />
<Mockup tone="dark" />
</div>
);
}
return (
<div className={styles.preview}>
<Mockup tone={mode === 'realDark' ? 'dark' : 'light'} />
</div>
);
};
ThemePreview.displayName = 'ThemePreview';
export default ThemePreview;
+69
View File
@@ -0,0 +1,69 @@
import useTabActive from '@/hooks/use-tab-active';
import { useIntl, useModel } from '@umijs/max';
import { Tabs, TabsProps } from 'antd';
import React, { useCallback, useMemo, useState } from 'react';
import styled from 'styled-components';
import PageBox from '../_components/page-box';
import Appearance from './components/appearance';
import ModifyPasswordn from './components/modify-password';
const Wrapper = styled.div`
.ant-page-header-heading {
padding-inline: 8px;
}
`;
const Profile: React.FC = () => {
const intl = useIntl();
const { initialState, setInitialState } = useModel('@@initialState') || {};
const { setTabActive, getTabActive, tabsMap } = useTabActive();
const [activeKey, setActiveKey] = useState(
initialState?.currentUser?.source === 'Local'
? 'modify-password'
: 'appearance'
);
const items: TabsProps['items'] = useMemo(() => {
if (initialState?.currentUser?.source !== 'Local') {
return [
{
key: 'appearance',
label: intl.formatMessage({ id: 'common.appearance' }),
children: <Appearance />
}
];
}
return [
{
key: 'modify-password',
label: intl.formatMessage({ id: 'users.form.updatepassword' }),
children: <ModifyPasswordn />
},
{
key: 'appearance',
label: intl.formatMessage({ id: 'common.appearance' }),
children: <Appearance />
}
];
}, [intl, initialState?.currentUser?.source]);
const handleChangeTab = useCallback((key: string) => {
setActiveKey(key);
setTabActive(tabsMap.userSettings, key);
}, []);
return (
<PageBox>
<Tabs
activeKey={activeKey}
onChange={handleChangeTab}
items={items}
type="card"
/>
</PageBox>
);
};
Profile.displayName = 'Profile';
export default Profile;
+36 -52
View File
@@ -1,65 +1,49 @@
import useTabActive from '@/hooks/use-tab-active';
import { useIntl, useModel } from '@umijs/max';
import { Tabs, TabsProps } from 'antd';
import React, { useCallback, useMemo, useState } from 'react';
import styled from 'styled-components';
import { createStyles } from 'antd-style';
import React from 'react';
import PageBox from '../_components/page-box';
import Appearance from './components/appearance';
import ModifyPasswordn from './components/modify-password';
import Security from './components/security';
import SettingsSection from './components/settings-section';
const Wrapper = styled.div`
.ant-page-header-heading {
padding-inline: 8px;
}
`;
const useStyles = createStyles(({ css }) => ({
wrapper: css`
width: 100%;
max-width: 720px;
margin: 0 auto;
padding: 8px 0 40px;
`
}));
const Profile: React.FC = () => {
const intl = useIntl();
const { initialState, setInitialState } = useModel('@@initialState') || {};
const { setTabActive, getTabActive, tabsMap } = useTabActive();
const [activeKey, setActiveKey] = useState(
initialState?.currentUser?.source === 'Local'
? 'modify-password'
: 'appearance'
);
const items: TabsProps['items'] = useMemo(() => {
if (initialState?.currentUser?.source !== 'Local') {
return [
{
key: 'appearance',
label: intl.formatMessage({ id: 'common.appearance' }),
children: <Appearance />
}
];
}
return [
{
key: 'modify-password',
label: intl.formatMessage({ id: 'users.form.updatepassword' }),
children: <ModifyPasswordn />
},
{
key: 'appearance',
label: intl.formatMessage({ id: 'common.appearance' }),
children: <Appearance />
}
];
}, [intl, initialState?.currentUser?.source]);
const handleChangeTab = useCallback((key: string) => {
setActiveKey(key);
setTabActive(tabsMap.userSettings, key);
}, []);
const { styles } = useStyles();
const { initialState } = useModel('@@initialState') || {};
const isLocalUser = initialState?.currentUser?.source === 'Local';
return (
<PageBox>
<Tabs
activeKey={activeKey}
onChange={handleChangeTab}
items={items}
type="card"
/>
<div className={styles.wrapper}>
<SettingsSection
title={intl.formatMessage({ id: 'common.appearance' })}
description={intl.formatMessage({
id: 'common.appearance.description'
})}
>
<Appearance />
</SettingsSection>
{isLocalUser && (
<SettingsSection
title={intl.formatMessage({ id: 'common.security' })}
description={intl.formatMessage({
id: 'common.security.description'
})}
>
<Security />
</SettingsSection>
)}
</div>
</PageBox>
);
};