From 8f47b53879adaf87fb833ddec48df70062c0f992 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 27 Sep 2026 16:06:42 +0200 Subject: [PATCH] style(settings): fold the Happ settings into four tabs Seven tabs, several holding one or two settings, made the page hard to scan. The encrypted-links switch moves above the tabs under auto-detection, the colour profile joins the banners under Appearance & Theme, and Android per-app proxy joins Network & TUN Engine. The QR modal's settings link no longer needs a happTab selector, and the three orphaned tab-label keys are dropped from every locale. --- frontend/src/pages/clients/ClientQrModal.tsx | 2 +- .../pages/settings/HappSettingsContent.tsx | 229 ++++++++---------- .../pages/settings/SubscriptionGeneralTab.tsx | 2 - frontend/src/test/client-qr-modal.test.tsx | 2 +- .../test/subscription-general-tab.test.tsx | 38 +-- internal/web/translation/ar-EG.json | 3 - internal/web/translation/en-US.json | 3 - internal/web/translation/es-ES.json | 3 - internal/web/translation/fa-IR.json | 3 - internal/web/translation/id-ID.json | 3 - internal/web/translation/ja-JP.json | 3 - internal/web/translation/pt-BR.json | 3 - internal/web/translation/ru-RU.json | 3 - internal/web/translation/tr-TR.json | 3 - internal/web/translation/uk-UA.json | 3 - internal/web/translation/vi-VN.json | 3 - internal/web/translation/zh-CN.json | 3 - internal/web/translation/zh-TW.json | 3 - 18 files changed, 103 insertions(+), 209 deletions(-) diff --git a/frontend/src/pages/clients/ClientQrModal.tsx b/frontend/src/pages/clients/ClientQrModal.tsx index 8b5027c9c..da566c5d6 100644 --- a/frontend/src/pages/clients/ClientQrModal.tsx +++ b/frontend/src/pages/clients/ClientQrModal.tsx @@ -50,7 +50,7 @@ type QrVariant = 'standard' | 'happ'; type HappError = 'too_long' | 'unavailable' | null; const HAPP_CRYPT5_PREFIX = 'happ://crypt5/'; -const HAPP_SETTINGS_PATH = '/settings?subscriptionTab=happ&happTab=links#subscription'; +const HAPP_SETTINGS_PATH = '/settings?subscriptionTab=happ#subscription'; // QrPanel encodes at error level L; QR version 40 holds 2953 UTF-8 bytes at that level. const HAPP_QR_MAX_BYTES = 2953; const UTF8_ENCODER = new TextEncoder(); diff --git a/frontend/src/pages/settings/HappSettingsContent.tsx b/frontend/src/pages/settings/HappSettingsContent.tsx index 459cc2fa6..cb1e2a035 100644 --- a/frontend/src/pages/settings/HappSettingsContent.tsx +++ b/frontend/src/pages/settings/HappSettingsContent.tsx @@ -6,9 +6,6 @@ import { BuildOutlined, CloudSyncOutlined, DesktopOutlined, - LinkOutlined, - MobileOutlined, - NotificationOutlined, ThunderboltOutlined, } from '@ant-design/icons'; import type { AllSetting } from '@/models/setting'; @@ -22,7 +19,6 @@ interface HappSettingsContentProps { updateSetting: (patch: Partial) => void; isMobile: boolean; remoteSourceBadge: (val: string) => React.ReactNode; - defaultActiveTab?: 'routing' | 'links'; } export default function HappSettingsContent({ @@ -30,7 +26,6 @@ export default function HappSettingsContent({ updateSetting, isMobile, remoteSourceBadge, - defaultActiveTab = 'routing', }: HappSettingsContentProps) { const { t } = useTranslation(); // Generator choices stay local until Apply updates the draft; page Save persists it. @@ -65,17 +60,27 @@ export default function HappSettingsContent({ /> + + updateSetting({ happLinkEnable: v })} + /> + + - {!isMobile && t('pages.settings.subHappGroupRouting')} - + label: catTabLabel( + , + t('pages.settings.subHappGroupRouting'), + isMobile, ), children: ( <> @@ -173,27 +178,11 @@ export default function HappSettingsContent({ ), }, { - key: 'links', - label: catTabLabel(, t('pages.settings.subHappGroupLinks'), isMobile), - children: ( - - updateSetting({ happLinkEnable: v })} - /> - - ), - }, - { - key: 'banners', - label: ( - - {!isMobile && t('pages.settings.subHappGroupBanners')} - + key: 'appearance', + label: catTabLabel( + , + t('pages.settings.subHappGroupThemes'), + isMobile, ), children: ( <> @@ -285,15 +274,59 @@ export default function HappSettingsContent({ onChange={(v) => updateSetting({ subHappNotificationExpire: v })} /> + + + + updateSetting({ subHappColorProfile: e.target.value })} + /> + + + + + + + ), }, { key: 'network', - label: ( - - {!isMobile && t('pages.settings.subHappGroupNetwork')} - + label: catTabLabel( + , + t('pages.settings.subHappGroupNetwork'), + isMobile, ), children: ( <> @@ -422,70 +455,45 @@ export default function HappSettingsContent({ ]} /> - - ), - }, - { - key: 'themes', - label: ( - - {!isMobile && t('pages.settings.subHappGroupThemes')} - - ), - children: ( - <> + - - updateSetting({ subHappColorProfile: e.target.value })} - /> - - - - - - + updateSetting({ subHappPerAppMode: v })} - options={[ - { value: 'off', label: t('pages.settings.subHappPerAppOff') }, - { value: 'on', label: t('pages.settings.subHappPerAppOn') }, - { value: 'bypass', label: t('pages.settings.subHappPerAppBypass') }, - ]} - /> - - - - updateSetting({ subHappPerAppList: e.target.value })} - /> - - - ), - }, ]} /> diff --git a/frontend/src/pages/settings/SubscriptionGeneralTab.tsx b/frontend/src/pages/settings/SubscriptionGeneralTab.tsx index 970e49ce9..61cb72c96 100644 --- a/frontend/src/pages/settings/SubscriptionGeneralTab.tsx +++ b/frontend/src/pages/settings/SubscriptionGeneralTab.tsx @@ -414,8 +414,6 @@ export default function SubscriptionGeneralTab({ updateSetting={updateSetting} isMobile={isMobile} remoteSourceBadge={remoteSourceBadge} - // QR settings links select the link control; ordinary Happ visits still start on routing. - defaultActiveTab={searchParams.get('happTab') === 'links' ? 'links' : 'routing'} /> ), }, diff --git a/frontend/src/test/client-qr-modal.test.tsx b/frontend/src/test/client-qr-modal.test.tsx index 62c9b7ca6..141de33bf 100644 --- a/frontend/src/test/client-qr-modal.test.tsx +++ b/frontend/src/test/client-qr-modal.test.tsx @@ -241,7 +241,7 @@ describe('ClientQrModal Happ presentation', () => { expect(view.onOpenChange).toHaveBeenCalledOnce(); expect(view.onOpenChange).toHaveBeenCalledWith(false); expect(screen.getByTestId('location').textContent).toBe( - '/settings?subscriptionTab=happ&happTab=links#subscription', + '/settings?subscriptionTab=happ#subscription', ); expect(HttpUtil.post).not.toHaveBeenCalled(); }); diff --git a/frontend/src/test/subscription-general-tab.test.tsx b/frontend/src/test/subscription-general-tab.test.tsx index 839b34cfe..a712bb677 100644 --- a/frontend/src/test/subscription-general-tab.test.tsx +++ b/frontend/src/test/subscription-general-tab.test.tsx @@ -144,7 +144,7 @@ describe('SubscriptionGeneralTab', () => { }); it.each([false, true])( - 'updates the Happ link gate from its own tab when stored as %s', + 'updates the Happ link gate beside auto-detection when stored as %s', (enabled) => { const updateSetting = vi.fn(); @@ -158,11 +158,6 @@ describe('SubscriptionGeneralTab', () => { ); fireEvent.click(screen.getByRole('tab', { name: /Happ/ })); - expect( - screen.getByRole('tab', { name: /Routing & Rules/ }).getAttribute('aria-selected'), - ).toBe('true'); - expect(screen.queryByRole('switch', { name: 'Encrypted subscription links' })).toBeNull(); - fireEvent.click(screen.getByRole('tab', { name: /Subscription Links/ })); const linkSwitch = screen.getByRole('switch', { name: 'Encrypted subscription links' }); expect(linkSwitch.getAttribute('aria-checked')).toBe(String(enabled)); expect(updateSetting).not.toHaveBeenCalled(); @@ -172,51 +167,24 @@ describe('SubscriptionGeneralTab', () => { }, ); - it('opens the Happ link tab from the QR settings deep link without enabling generation', () => { + it('shows the Happ link gate from the QR settings deep link without enabling generation', () => { const updateSetting = vi.fn(); renderWithProviders( - + - , ); expect(screen.getByRole('tab', { name: /Happ/ }).getAttribute('aria-selected')).toBe('true'); - expect( - screen.getByRole('tab', { name: /Subscription Links/ }).getAttribute('aria-selected'), - ).toBe('true'); expect( screen .getByRole('switch', { name: 'Encrypted subscription links' }) .getAttribute('aria-checked'), ).toBe('false'); - expect(screen.getByTestId('location').textContent).toBe( - '/settings?subscriptionTab=happ&happTab=links#subscription', - ); expect(updateSetting).not.toHaveBeenCalled(); }); - - it.each(['', '&happTab=unknown'])( - 'keeps the routing default for a general Happ deep link %s', - (query) => { - const updateSetting = vi.fn(); - - renderWithProviders( - - - , - ); - - expect(screen.getByRole('tab', { name: /Happ/ }).getAttribute('aria-selected')).toBe('true'); - expect( - screen.getByRole('tab', { name: /Routing & Rules/ }).getAttribute('aria-selected'), - ).toBe('true'); - expect(screen.queryByRole('switch', { name: 'Encrypted subscription links' })).toBeNull(); - expect(updateSetting).not.toHaveBeenCalled(); - }, - ); }); diff --git a/internal/web/translation/ar-EG.json b/internal/web/translation/ar-EG.json index de8231aca..fa3ed2736 100644 --- a/internal/web/translation/ar-EG.json +++ b/internal/web/translation/ar-EG.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "IPs / CIDRs عبر البروكسي", "subHappBlockIPs": "IPs / CIDRs محظورة", "subHappDeeplinkGenerated": "تم إنشاء الرابط العميق وتطبيقه على قواعد التوجيه", - "subHappGroupLinks": "روابط الاشتراك", "subHappGroupRouting": "التوجيه والقواعد", - "subHappGroupBanners": "اللافتات والإعلانات", "subHappGroupNetwork": "الشبكة ومحرك TUN", "subHappGroupThemes": "المظهر والسمة", "subHappGroupFailover": "الترحيل وإدارة التطبيق", - "subHappGroupAndroid": "البروكسي لكل تطبيق على Android", "discordSettings": "بوت ديسكورد", "discordBotEnable": "تفعيل إشعارات ديسكورد", "discordBotEnableDesc": "إرسال تنبيهات النظام والأحداث إلى قناة ديسكورد عبر البوت", diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json index 53defe043..8e3abdd74 100644 --- a/internal/web/translation/en-US.json +++ b/internal/web/translation/en-US.json @@ -1761,13 +1761,10 @@ "subHappProxyIPs": "Proxy IPs / CIDRs", "subHappBlockIPs": "Blocked IPs / CIDRs", "subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules", - "subHappGroupLinks": "Subscription Links", "subHappGroupRouting": "Routing & Rules", - "subHappGroupBanners": "Banners & Announcements", "subHappGroupNetwork": "Network & TUN Engine", "subHappGroupThemes": "Appearance & Theme", "subHappGroupFailover": "Migration & App Management", - "subHappGroupAndroid": "Android Per-App Proxy", "discordSettings": "Discord Bot", "discordBotEnable": "Enable Discord Notifications", "discordBotEnableDesc": "Send system and event alerts to a Discord channel via bot", diff --git a/internal/web/translation/es-ES.json b/internal/web/translation/es-ES.json index 0156a7648..b26379c40 100644 --- a/internal/web/translation/es-ES.json +++ b/internal/web/translation/es-ES.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "IPs / CIDR por proxy", "subHappBlockIPs": "IPs / CIDR bloqueados", "subHappDeeplinkGenerated": "Deeplink generado y aplicado a las reglas de enrutamiento", - "subHappGroupLinks": "Enlaces de suscripción", "subHappGroupRouting": "Enrutamiento y reglas", - "subHappGroupBanners": "Banners y anuncios", "subHappGroupNetwork": "Red y motor TUN", "subHappGroupThemes": "Apariencia y tema", "subHappGroupFailover": "Migración y gestión de la app", - "subHappGroupAndroid": "Proxy por aplicación en Android", "discordSettings": "Bot de Discord", "discordBotEnable": "Habilitar notificaciones de Discord", "discordBotEnableDesc": "Enviar alertas del sistema y eventos a un canal de Discord a través del bot", diff --git a/internal/web/translation/fa-IR.json b/internal/web/translation/fa-IR.json index d95bcc7d7..1f7c89260 100644 --- a/internal/web/translation/fa-IR.json +++ b/internal/web/translation/fa-IR.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "آی‌پی‌های پراکسی (Proxy CIDRs)", "subHappBlockIPs": "آی‌پی‌های مسدود (Block CIDRs)", "subHappDeeplinkGenerated": "دیپ‌لینک تولید و در قوانین روتینگ اعمال شد", - "subHappGroupLinks": "لینک‌های اشتراک", "subHappGroupRouting": "قوانین و روتینگ", - "subHappGroupBanners": "بنرها و اعلانات", "subHappGroupNetwork": "تنظیمات شبکه و TUN", "subHappGroupThemes": "ظاهر و پوسته برنامه", "subHappGroupFailover": "مهاجرت و مدیریت برنامه", - "subHappGroupAndroid": "پراکسی برنامه‌های اندروید", "discordSettings": "ربات دیسکورد", "discordBotEnable": "فعال‌سازی اعلان‌های دیسکورد", "discordBotEnableDesc": "ارسال هشدارهای سیستم و رویدادها به کانال دیسکورد از طریق ربات", diff --git a/internal/web/translation/id-ID.json b/internal/web/translation/id-ID.json index f93fee3f2..c83463921 100644 --- a/internal/web/translation/id-ID.json +++ b/internal/web/translation/id-ID.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "IP / CIDR Proxy", "subHappBlockIPs": "IP / CIDR Diblokir", "subHappDeeplinkGenerated": "Deeplink dibuat dan diterapkan ke aturan routing", - "subHappGroupLinks": "Tautan Langganan", "subHappGroupRouting": "Routing & Aturan", - "subHappGroupBanners": "Banner & Pengumuman", "subHappGroupNetwork": "Jaringan & Mesin TUN", "subHappGroupThemes": "Tampilan & Tema", "subHappGroupFailover": "Migrasi & Manajemen Aplikasi", - "subHappGroupAndroid": "Proxy Per-Aplikasi Android", "discordSettings": "Bot Discord", "discordBotEnable": "Aktifkan Notifikasi Discord", "discordBotEnableDesc": "Kirim peringatan sistem dan acara ke saluran Discord melalui bot", diff --git a/internal/web/translation/ja-JP.json b/internal/web/translation/ja-JP.json index 58be2e24f..847292a5c 100644 --- a/internal/web/translation/ja-JP.json +++ b/internal/web/translation/ja-JP.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "プロキシする IP / CIDR", "subHappBlockIPs": "ブロックする IP / CIDR", "subHappDeeplinkGenerated": "ディープリンクを生成し、ルーティングルールに適用しました", - "subHappGroupLinks": "サブスクリプションリンク", "subHappGroupRouting": "ルーティングとルール", - "subHappGroupBanners": "バナーとお知らせ", "subHappGroupNetwork": "ネットワークと TUN エンジン", "subHappGroupThemes": "外観とテーマ", "subHappGroupFailover": "移行とアプリ管理", - "subHappGroupAndroid": "Android アプリ別プロキシ", "discordSettings": "Discord Bot", "discordBotEnable": "Discord通知を有効化", "discordBotEnableDesc": "Botを介してDiscordチャンネルにシステムおよびイベント通知を送信します", diff --git a/internal/web/translation/pt-BR.json b/internal/web/translation/pt-BR.json index b189680ef..04a0280c6 100644 --- a/internal/web/translation/pt-BR.json +++ b/internal/web/translation/pt-BR.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "IPs / CIDRs via proxy", "subHappBlockIPs": "IPs / CIDRs bloqueados", "subHappDeeplinkGenerated": "Deeplink gerado e aplicado às regras de roteamento", - "subHappGroupLinks": "Links de assinatura", "subHappGroupRouting": "Roteamento e regras", - "subHappGroupBanners": "Banners e anúncios", "subHappGroupNetwork": "Rede e mecanismo TUN", "subHappGroupThemes": "Aparência e tema", "subHappGroupFailover": "Migração e gerenciamento do app", - "subHappGroupAndroid": "Proxy por aplicativo no Android", "discordSettings": "Bot do Discord", "discordBotEnable": "Ativar notificações do Discord", "discordBotEnableDesc": "Enviar alertas de sistema e eventos para um canal do Discord via bot", diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json index 7159df78f..71e2b3311 100644 --- a/internal/web/translation/ru-RU.json +++ b/internal/web/translation/ru-RU.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "Проксируемые IP / CIDR", "subHappBlockIPs": "Заблокированные IP / CIDR", "subHappDeeplinkGenerated": "Диплинк сгенерирован и применен к правилам маршрутизации", - "subHappGroupLinks": "Ссылки подписки", "subHappGroupRouting": "Маршрутизация и правила", - "subHappGroupBanners": "Баннеры и уведомления", "subHappGroupNetwork": "Сетевые настройки и TUN", "subHappGroupThemes": "Внешний вид и темы", "subHappGroupFailover": "Миграция и управление", - "subHappGroupAndroid": "Прокси приложений Android", "discordSettings": "Discord бот", "discordBotEnable": "Включить уведомления в Discord", "discordBotEnableDesc": "Отправлять оповещения о событиях в канал Discord через бота", diff --git a/internal/web/translation/tr-TR.json b/internal/web/translation/tr-TR.json index a3d6d66f2..2bafb9017 100644 --- a/internal/web/translation/tr-TR.json +++ b/internal/web/translation/tr-TR.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "Proxy IP'ler / CIDR'ler", "subHappBlockIPs": "Engellenen IP'ler / CIDR'ler", "subHappDeeplinkGenerated": "Derin bağlantı oluşturuldu ve yönlendirme kurallarına uygulandı", - "subHappGroupLinks": "Abonelik Bağlantıları", "subHappGroupRouting": "Yönlendirme ve Kurallar", - "subHappGroupBanners": "Afişler ve Duyurular", "subHappGroupNetwork": "Ağ ve TUN Motoru", "subHappGroupThemes": "Görünüm ve Tema", "subHappGroupFailover": "Geçiş ve Uygulama Yönetimi", - "subHappGroupAndroid": "Android Uygulama Başına Proxy", "discordSettings": "Discord Botu", "discordBotEnable": "Discord Bildirimlerini Etkinleştir", "discordBotEnableDesc": "Sistem ve olay uyarılarını bot aracılığıyla bir Discord kanalına gönderin", diff --git a/internal/web/translation/uk-UA.json b/internal/web/translation/uk-UA.json index 8e9d70bc6..be31441a1 100644 --- a/internal/web/translation/uk-UA.json +++ b/internal/web/translation/uk-UA.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "IP / CIDR через проксі", "subHappBlockIPs": "Заблоковані IP / CIDR", "subHappDeeplinkGenerated": "Діплінк згенеровано та застосовано до правил маршрутизації", - "subHappGroupLinks": "Посилання на підписку", "subHappGroupRouting": "Маршрутизація та правила", - "subHappGroupBanners": "Банери та оголошення", "subHappGroupNetwork": "Мережа та рушій TUN", "subHappGroupThemes": "Зовнішній вигляд і тема", "subHappGroupFailover": "Міграція та керування програмою", - "subHappGroupAndroid": "Проксі для окремих програм Android", "discordSettings": "Discord бот", "discordBotEnable": "Увімкнути сповіщення Discord", "discordBotEnableDesc": "Надсилати системні сповіщення та події в канал Discord через бота", diff --git a/internal/web/translation/vi-VN.json b/internal/web/translation/vi-VN.json index 0747ddafb..ec4e6b97b 100644 --- a/internal/web/translation/vi-VN.json +++ b/internal/web/translation/vi-VN.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "IP / CIDR proxy", "subHappBlockIPs": "IP / CIDR bị chặn", "subHappDeeplinkGenerated": "Đã tạo deeplink và áp dụng vào quy tắc định tuyến", - "subHappGroupLinks": "Liên kết đăng ký", "subHappGroupRouting": "Định tuyến & quy tắc", - "subHappGroupBanners": "Biểu ngữ & thông báo", "subHappGroupNetwork": "Mạng & lõi TUN", "subHappGroupThemes": "Giao diện & chủ đề", "subHappGroupFailover": "Di chuyển & quản lý ứng dụng", - "subHappGroupAndroid": "Proxy theo ứng dụng Android", "discordSettings": "Bot Discord", "discordBotEnable": "Bật thông báo Discord", "discordBotEnableDesc": "Gửi cảnh báo hệ thống và sự kiện đến kênh Discord qua bot", diff --git a/internal/web/translation/zh-CN.json b/internal/web/translation/zh-CN.json index 7477dffa7..26c831e2e 100644 --- a/internal/web/translation/zh-CN.json +++ b/internal/web/translation/zh-CN.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "代理 IP / CIDR", "subHappBlockIPs": "阻止 IP / CIDR", "subHappDeeplinkGenerated": "DeepLink 已生成并填入路由规则", - "subHappGroupLinks": "订阅链接", "subHappGroupRouting": "路由分流与规则", - "subHappGroupBanners": "横幅公告与通知", "subHappGroupNetwork": "网络与 TUN 引擎", "subHappGroupThemes": "界面与主题外观", "subHappGroupFailover": "迁移与客户端管理", - "subHappGroupAndroid": "Android 分应用代理", "discordSettings": "Discord 机器人", "discordBotEnable": "启用 Discord 通知", "discordBotEnableDesc": "通过机器人向 Discord 频道发送系统和事件警报", diff --git a/internal/web/translation/zh-TW.json b/internal/web/translation/zh-TW.json index a98aa03fe..ea8bc5662 100644 --- a/internal/web/translation/zh-TW.json +++ b/internal/web/translation/zh-TW.json @@ -1643,13 +1643,10 @@ "subHappProxyIPs": "代理 IP / CIDR", "subHappBlockIPs": "封鎖 IP / CIDR", "subHappDeeplinkGenerated": "已產生深層連結並套用至路由規則", - "subHappGroupLinks": "訂閱連結", "subHappGroupRouting": "路由與規則", - "subHappGroupBanners": "橫幅與公告", "subHappGroupNetwork": "網路與 TUN 引擎", "subHappGroupThemes": "外觀與主題", "subHappGroupFailover": "遷移與應用程式管理", - "subHappGroupAndroid": "Android 個別應用程式代理", "discordSettings": "Discord 機器人", "discordBotEnable": "啟用 Discord 通知", "discordBotEnableDesc": "透過機器人向 Discord 頻道發送系統與事件警報",