refactor(settings): reorganize subscription settings into clearer tabs

- Split the Happ and Clash/Mihomo routing sections out of Information into
  their own dedicated tabs.
- Extract the profile/branding fields (title, support URL, profile page,
  announcement, theme dir) out of the mislabeled "Subscription Title"
  divider into a new Profile tab.
- Move the Update Interval setting into Information and drop the
  single-field Intervals tab.
- Add the "profile" tab label across all locales.
This commit is contained in:
MHSanaei
2026-06-12 19:41:02 +02:00
parent 0f7da02a07
commit 08bc481ae3
14 changed files with 57 additions and 37 deletions
@@ -1,6 +1,6 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Divider, Input, InputNumber, Select, Space, Switch, Tabs } from 'antd'; import { Input, InputNumber, Select, Space, Switch, Tabs } from 'antd';
import { ClockCircleOutlined, InfoCircleOutlined, SafetyCertificateOutlined, SettingOutlined } from '@ant-design/icons'; import { BranchesOutlined, IdcardOutlined, InfoCircleOutlined, NodeIndexOutlined, SafetyCertificateOutlined, SettingOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { AllSetting } from '@/models/setting'; import type { AllSetting } from '@/models/setting';
import { SettingListItem } from '@/components/ui'; import { SettingListItem } from '@/components/ui';
@@ -139,8 +139,18 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
</Space.Compact> </Space.Compact>
</SettingListItem> </SettingListItem>
<Divider>{t('pages.settings.subTitle')}</Divider> <SettingListItem paddings="small" title={t('pages.settings.subUpdates')} description={t('pages.settings.subUpdatesDesc')}>
<InputNumber value={allSetting.subUpdates} min={1} style={{ width: '100%' }}
onChange={(v) => updateSetting({ subUpdates: Number(v) || 0 })} />
</SettingListItem>
</>
),
},
{
key: '3',
label: catTabLabel(<IdcardOutlined />, t('pages.settings.profile'), isMobile),
children: (
<>
<SettingListItem paddings="small" title={t('pages.settings.subTitle')} description={t('pages.settings.subTitleDesc')}> <SettingListItem paddings="small" title={t('pages.settings.subTitle')} description={t('pages.settings.subTitleDesc')}>
<Input value={allSetting.subTitle} onChange={(e) => updateSetting({ subTitle: e.target.value })} /> <Input value={allSetting.subTitle} onChange={(e) => updateSetting({ subTitle: e.target.value })} />
</SettingListItem> </SettingListItem>
@@ -156,7 +166,6 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
<Input.TextArea value={allSetting.subAnnounce} <Input.TextArea value={allSetting.subAnnounce}
onChange={(e) => updateSetting({ subAnnounce: e.target.value })} /> onChange={(e) => updateSetting({ subAnnounce: e.target.value })} />
</SettingListItem> </SettingListItem>
<SettingListItem <SettingListItem
paddings="small" paddings="small"
title={t('pages.settings.subThemeDir')} title={t('pages.settings.subThemeDir')}
@@ -176,9 +185,28 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
<Input value={allSetting.subThemeDir} placeholder="/etc/3x-ui/sub_templates/my-theme/" <Input value={allSetting.subThemeDir} placeholder="/etc/3x-ui/sub_templates/my-theme/"
onChange={(e) => updateSetting({ subThemeDir: e.target.value })} /> onChange={(e) => updateSetting({ subThemeDir: e.target.value })} />
</SettingListItem> </SettingListItem>
</>
<Divider>Happ</Divider> ),
},
{
key: '4',
label: catTabLabel(<SafetyCertificateOutlined />, t('pages.settings.certs'), isMobile),
children: (
<>
<SettingListItem paddings="small" title={t('pages.settings.subCertPath')} description={t('pages.settings.subCertPathDesc')}>
<Input value={allSetting.subCertFile} onChange={(e) => updateSetting({ subCertFile: e.target.value })} />
</SettingListItem>
<SettingListItem paddings="small" title={t('pages.settings.subKeyPath')} description={t('pages.settings.subKeyPathDesc')}>
<Input value={allSetting.subKeyFile} onChange={(e) => updateSetting({ subKeyFile: e.target.value })} />
</SettingListItem>
</>
),
},
{
key: '5',
label: catTabLabel(<BranchesOutlined />, 'Happ', isMobile),
children: (
<>
<SettingListItem paddings="small" title={t('pages.settings.subEnableRouting')} description={t('pages.settings.subEnableRoutingDesc')}> <SettingListItem paddings="small" title={t('pages.settings.subEnableRouting')} description={t('pages.settings.subEnableRoutingDesc')}>
<Switch checked={allSetting.subEnableRouting} onChange={(v) => updateSetting({ subEnableRouting: v })} /> <Switch checked={allSetting.subEnableRouting} onChange={(v) => updateSetting({ subEnableRouting: v })} />
</SettingListItem> </SettingListItem>
@@ -186,9 +214,14 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
<Input.TextArea value={allSetting.subRoutingRules} placeholder="happ://routing/add/..." <Input.TextArea value={allSetting.subRoutingRules} placeholder="happ://routing/add/..."
onChange={(e) => updateSetting({ subRoutingRules: e.target.value })} /> onChange={(e) => updateSetting({ subRoutingRules: e.target.value })} />
</SettingListItem> </SettingListItem>
</>
<Divider>Clash / Mihomo</Divider> ),
},
{
key: '6',
label: catTabLabel(<NodeIndexOutlined />, 'Clash / Mihomo', isMobile),
children: (
<>
<SettingListItem paddings="small" title={t('pages.settings.subClashEnableRouting')} description={t('pages.settings.subClashEnableRoutingDesc')}> <SettingListItem paddings="small" title={t('pages.settings.subClashEnableRouting')} description={t('pages.settings.subClashEnableRoutingDesc')}>
<Switch checked={allSetting.subClashEnableRouting} onChange={(v) => updateSetting({ subClashEnableRouting: v })} /> <Switch checked={allSetting.subClashEnableRouting} onChange={(v) => updateSetting({ subClashEnableRouting: v })} />
</SettingListItem> </SettingListItem>
@@ -203,32 +236,6 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
</> </>
), ),
}, },
{
key: '3',
label: catTabLabel(<SafetyCertificateOutlined />, t('pages.settings.certs'), isMobile),
children: (
<>
<SettingListItem paddings="small" title={t('pages.settings.subCertPath')} description={t('pages.settings.subCertPathDesc')}>
<Input value={allSetting.subCertFile} onChange={(e) => updateSetting({ subCertFile: e.target.value })} />
</SettingListItem>
<SettingListItem paddings="small" title={t('pages.settings.subKeyPath')} description={t('pages.settings.subKeyPathDesc')}>
<Input value={allSetting.subKeyFile} onChange={(e) => updateSetting({ subKeyFile: e.target.value })} />
</SettingListItem>
</>
),
},
{
key: '4',
label: catTabLabel(<ClockCircleOutlined />, t('pages.settings.intervals'), isMobile),
children: (
<>
<SettingListItem paddings="small" title={t('pages.settings.subUpdates')} description={t('pages.settings.subUpdatesDesc')}>
<InputNumber value={allSetting.subUpdates} min={1} style={{ width: '100%' }}
onChange={(v) => updateSetting({ subUpdates: Number(v) || 0 })} />
</SettingListItem>
</>
),
},
]} /> ]} />
); );
} }
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "البروكسي والسيرفر", "proxyAndServer": "البروكسي والسيرفر",
"intervals": "الفترات", "intervals": "الفترات",
"information": "المعلومات", "information": "المعلومات",
"profile": "الملف الشخصي",
"language": "اللغة", "language": "اللغة",
"telegramBotLanguage": "لغة بوت Telegram", "telegramBotLanguage": "لغة بوت Telegram",
"security": { "security": {
+1
View File
@@ -1152,6 +1152,7 @@
"proxyAndServer": "Proxy and Server", "proxyAndServer": "Proxy and Server",
"intervals": "Intervals", "intervals": "Intervals",
"information": "Information", "information": "Information",
"profile": "Profile",
"language": "Language", "language": "Language",
"telegramBotLanguage": "Telegram Bot Language", "telegramBotLanguage": "Telegram Bot Language",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "Proxy y Servidor", "proxyAndServer": "Proxy y Servidor",
"intervals": "Intervalos", "intervals": "Intervalos",
"information": "Información", "information": "Información",
"profile": "Perfil",
"language": "Idioma", "language": "Idioma",
"telegramBotLanguage": "Idioma del Bot de Telegram", "telegramBotLanguage": "Idioma del Bot de Telegram",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "پراکسی و سرور", "proxyAndServer": "پراکسی و سرور",
"intervals": "فواصل", "intervals": "فواصل",
"information": "اطلاعات", "information": "اطلاعات",
"profile": "پروفایل",
"language": "زبان", "language": "زبان",
"telegramBotLanguage": "زبان ربات تلگرام", "telegramBotLanguage": "زبان ربات تلگرام",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "Proxy dan Server", "proxyAndServer": "Proxy dan Server",
"intervals": "Interval", "intervals": "Interval",
"information": "Informasi", "information": "Informasi",
"profile": "Profil",
"language": "Bahasa", "language": "Bahasa",
"telegramBotLanguage": "Bahasa Bot Telegram", "telegramBotLanguage": "Bahasa Bot Telegram",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "プロキシとサーバー", "proxyAndServer": "プロキシとサーバー",
"intervals": "間隔", "intervals": "間隔",
"information": "情報", "information": "情報",
"profile": "プロフィール",
"language": "言語", "language": "言語",
"telegramBotLanguage": "Telegram Botの言語", "telegramBotLanguage": "Telegram Botの言語",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "Proxy e Servidor", "proxyAndServer": "Proxy e Servidor",
"intervals": "Intervalos", "intervals": "Intervalos",
"information": "Informação", "information": "Informação",
"profile": "Perfil",
"language": "Idioma", "language": "Idioma",
"telegramBotLanguage": "Idioma do Bot do Telegram", "telegramBotLanguage": "Idioma do Bot do Telegram",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "Прокси и сервер", "proxyAndServer": "Прокси и сервер",
"intervals": "Интервалы", "intervals": "Интервалы",
"information": "Информация", "information": "Информация",
"profile": "Профиль",
"language": "Язык интерфейса", "language": "Язык интерфейса",
"telegramBotLanguage": "Язык Telegram-бота", "telegramBotLanguage": "Язык Telegram-бота",
"security": { "security": {
+1
View File
@@ -1150,6 +1150,7 @@
"proxyAndServer": "Proxy ve Sunucu", "proxyAndServer": "Proxy ve Sunucu",
"intervals": "Aralıklar", "intervals": "Aralıklar",
"information": "Bilgi", "information": "Bilgi",
"profile": "Profil",
"language": "Dil", "language": "Dil",
"telegramBotLanguage": "Telegram Bot Dili", "telegramBotLanguage": "Telegram Bot Dili",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "Проксі та сервер", "proxyAndServer": "Проксі та сервер",
"intervals": "Інтервали", "intervals": "Інтервали",
"information": "Інформація", "information": "Інформація",
"profile": "Профіль",
"language": "Мова", "language": "Мова",
"telegramBotLanguage": "Мова Telegram-бота", "telegramBotLanguage": "Мова Telegram-бота",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "Proxy và máy chủ", "proxyAndServer": "Proxy và máy chủ",
"intervals": "Khoảng thời gian", "intervals": "Khoảng thời gian",
"information": "Thông tin", "information": "Thông tin",
"profile": "Hồ sơ",
"language": "Ngôn ngữ", "language": "Ngôn ngữ",
"telegramBotLanguage": "Ngôn ngữ của Bot Telegram", "telegramBotLanguage": "Ngôn ngữ của Bot Telegram",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "代理和服务器", "proxyAndServer": "代理和服务器",
"intervals": "间隔", "intervals": "间隔",
"information": "信息", "information": "信息",
"profile": "资料",
"language": "语言", "language": "语言",
"telegramBotLanguage": "Telegram 机器人语言", "telegramBotLanguage": "Telegram 机器人语言",
"security": { "security": {
+1
View File
@@ -1151,6 +1151,7 @@
"proxyAndServer": "代理和伺服器", "proxyAndServer": "代理和伺服器",
"intervals": "間隔", "intervals": "間隔",
"information": "資訊", "information": "資訊",
"profile": "資料",
"language": "語言", "language": "語言",
"telegramBotLanguage": "Telegram 機器人語言", "telegramBotLanguage": "Telegram 機器人語言",
"security": { "security": {