From 04550ab642ce97473856180fcf3180dfef9bab8d Mon Sep 17 00:00:00 2001 From: sl Date: Sun, 17 May 2026 18:47:12 +0200 Subject: [PATCH] frontend: settings search --- CHANGELOG.md | 1 + .../components/forms/search-input.module.css | 19 ++ .../web/src/components/forms/search-input.tsx | 60 +++- frontends/web/src/locales/en/app.json | 3 + .../src/routes/device/no-device-connected.tsx | 8 +- frontends/web/src/routes/router.tsx | 13 +- frontends/web/src/routes/settings/about.tsx | 28 +- .../src/routes/settings/advanced-settings.tsx | 70 +++-- .../routes/settings/bb02-settings.module.css | 14 +- .../web/src/routes/settings/bb02-settings.tsx | 169 ++++++----- .../components/settings-content.test.tsx | 58 ++++ .../settings/components/settings-content.tsx | 53 ++++ .../settings-search-content.module.css | 18 ++ .../components/settings-search-content.tsx | 88 ++++++ .../settings/components/tabs.module.css | 4 + .../src/routes/settings/components/tabs.tsx | 39 ++- .../src/routes/settings/general.module.css | 5 - frontends/web/src/routes/settings/general.tsx | 62 ++-- .../src/routes/settings/manage-accounts.tsx | 2 +- .../src/routes/settings/mobile-settings.tsx | 6 +- .../routes/settings/settings-search.test.ts | 142 +++++++++ .../src/routes/settings/settings-search.ts | 283 ++++++++++++++++++ .../settings/use-settings-search.test.tsx | 184 ++++++++++++ .../routes/settings/use-settings-search.ts | 74 +++++ 24 files changed, 1263 insertions(+), 140 deletions(-) create mode 100644 frontends/web/src/routes/settings/components/settings-content.test.tsx create mode 100644 frontends/web/src/routes/settings/components/settings-content.tsx create mode 100644 frontends/web/src/routes/settings/components/settings-search-content.module.css create mode 100644 frontends/web/src/routes/settings/components/settings-search-content.tsx delete mode 100644 frontends/web/src/routes/settings/general.module.css create mode 100644 frontends/web/src/routes/settings/settings-search.test.ts create mode 100644 frontends/web/src/routes/settings/settings-search.ts create mode 100644 frontends/web/src/routes/settings/use-settings-search.test.tsx create mode 100644 frontends/web/src/routes/settings/use-settings-search.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e33be32c48..07c6e6e48a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- Settings search ## v4.51.1 - iOS: fix App Store submission by packaging Inter as TTF fonts diff --git a/frontends/web/src/components/forms/search-input.module.css b/frontends/web/src/components/forms/search-input.module.css index 6af4680cf0..4be6da016a 100644 --- a/frontends/web/src/components/forms/search-input.module.css +++ b/frontends/web/src/components/forms/search-input.module.css @@ -6,3 +6,22 @@ pointer-events: none; width: 20px; } + +.clearButton { + align-items: center; + background: transparent; + border: none; + cursor: pointer; + display: flex; + flex: none; + height: 24px; + justify-content: center; + margin: auto 0; + padding: 0; + width: 24px; +} + +.clearButton img { + height: 20px; + width: 20px; +} diff --git a/frontends/web/src/components/forms/search-input.tsx b/frontends/web/src/components/forms/search-input.tsx index 5da2609848..8f906775df 100644 --- a/frontends/web/src/components/forms/search-input.tsx +++ b/frontends/web/src/components/forms/search-input.tsx @@ -1,13 +1,61 @@ // SPDX-License-Identifier: Apache-2.0 import { forwardRef } from 'react'; -import { Loupe } from '@/components/icon'; +import { CloseXDark, CloseXWhite, Loupe } from '@/components/icon'; import { Input } from './input'; import type { TInputProps } from './input'; import styles from './search-input.module.css'; -export const SearchInput = forwardRef((props, ref) => ( - - - -)); +type TBaseProps = Omit; + +type TClearProps = TBaseProps & { + clearButtonLabel?: string; + onClear: () => void; + variant: 'clear'; +}; + +type TSearchProps = TBaseProps & { + clearButtonLabel?: never; + onClear?: never; + variant?: 'search'; +}; + +type TProps = TClearProps | TSearchProps; + +export const SearchInput = forwardRef((props, ref) => { + if (props.variant === 'clear') { + const { + clearButtonLabel, + onClear, + variant: _variant, + ...inputProps + } = props; + + return ( + + {String(inputProps.value ?? '').trim().length > 0 ? ( + + ) : null} + + ); + } + + const { + variant: _variant, + ...inputProps + } = props; + + return ( + + + + ); +}); diff --git a/frontends/web/src/locales/en/app.json b/frontends/web/src/locales/en/app.json index 30b4c4e2df..1e264f53c3 100644 --- a/frontends/web/src/locales/en/app.json +++ b/frontends/web/src/locales/en/app.json @@ -1987,6 +1987,9 @@ "title": "Manage notes" }, "restart": "Please re-start the BitBoxApp for the changes to take effect.", + "search": { + "noResults": "No settings found" + }, "services": { "title": "Services" }, diff --git a/frontends/web/src/routes/device/no-device-connected.tsx b/frontends/web/src/routes/device/no-device-connected.tsx index 99cc5d17c4..b71f511149 100644 --- a/frontends/web/src/routes/device/no-device-connected.tsx +++ b/frontends/web/src/routes/device/no-device-connected.tsx @@ -12,10 +12,15 @@ import { WithSettingsTabs } from '@/routes/settings/components/tabs'; import { ManageDeviceGuide } from './bitbox02/settings-guide'; import styles from './no-device-connected.module.css'; +type TProps = TPagePropsWithSettingsTabs & { + hideSearch?: boolean; +}; + export const NoDeviceConnected = ({ devices, + hideSearch, hasAccounts, -}: TPagePropsWithSettingsTabs) => { +}: TProps) => { const { t } = useTranslation(); return ( @@ -38,6 +43,7 @@ export const NoDeviceConnected = ({
diff --git a/frontends/web/src/routes/router.tsx b/frontends/web/src/routes/router.tsx index db58213964..b62f8de816 100644 --- a/frontends/web/src/routes/router.tsx +++ b/frontends/web/src/routes/router.tsx @@ -82,6 +82,17 @@ export const AppRouter = ({ devices, devicesKey, accounts, activeAccounts }: TAp + + ); + + const NoAccounts = ( + + @@ -324,7 +335,7 @@ export const AppRouter = ({ devices, devicesKey, accounts, activeAccounts }: TAp - + diff --git a/frontends/web/src/routes/settings/about.tsx b/frontends/web/src/routes/settings/about.tsx index 68d98d4761..f6699ef505 100644 --- a/frontends/web/src/routes/settings/about.tsx +++ b/frontends/web/src/routes/settings/about.tsx @@ -13,6 +13,11 @@ import { ContentWrapper } from '@/components/contentwrapper/contentwrapper'; import { GlobalBanners } from '@/components/banners'; import { FeedbackLink } from './components/about/feedback-link-setting'; import { SupportLink } from './components/about/support-link-setting'; +import { SettingsContent, type TSettingsContentSection } from './components/settings-content'; + +type TProps = { + visibleItemIDs?: string[]; +}; export const About = ({ devices, hasAccounts }: TPagePropsWithSettingsTabs) => { const { t } = useTranslation(); @@ -34,9 +39,7 @@ export const About = ({ devices, hasAccounts }: TPagePropsWithSettingsTabs) => { - - - + @@ -47,6 +50,25 @@ export const About = ({ devices, hasAccounts }: TPagePropsWithSettingsTabs) => { ); }; +export const AboutSettingsContent = ({ visibleItemIDs }: TProps) => { + const sections: TSettingsContentSection[] = [ + { + id: 'about', + items: [ + { id: 'app-version', content: }, + { id: 'feedback', content: }, + { id: 'support', content: }, + ], + }, + ]; + + return ( + + ); +}; const AboutGuide = () => { const { t } = useTranslation(); diff --git a/frontends/web/src/routes/settings/advanced-settings.tsx b/frontends/web/src/routes/settings/advanced-settings.tsx index 2482cf13bd..1c59a0b852 100644 --- a/frontends/web/src/routes/settings/advanced-settings.tsx +++ b/frontends/web/src/routes/settings/advanced-settings.tsx @@ -22,6 +22,7 @@ import { Entry } from '@/components/guide/entry'; import { EnableAuthSetting } from './components/advanced-settings/enable-auth-setting'; import { ContentWrapper } from '@/components/contentwrapper/contentwrapper'; import { GlobalBanners } from '@/components/banners'; +import { SettingsContent, type TSettingsContentSection } from './components/settings-content'; export type TProxyConfig = { proxyAddress: string; @@ -46,21 +47,13 @@ export type TConfig = { frontend?: TFrontendConfig; }; +type TProps = { + devices: TPagePropsWithSettingsTabs['devices']; + visibleItemIDs?: string[]; +}; + export const AdvancedSettings = ({ devices, hasAccounts }: TPagePropsWithSettingsTabs) => { const { t } = useTranslation(); - const fetchedConfig = useLoad(getConfig) as TConfig; - const [config, setConfig] = useState(); - - const frontendConfig = config?.frontend; - const backendConfig = config?.backend; - const proxyConfig = config?.backend?.proxy; - - useEffect(() => { - setConfig(fetchedConfig); - }, [fetchedConfig]); - - const deviceIDs = Object.keys(devices); - return ( @@ -83,15 +76,7 @@ export const AdvancedSettings = ({ devices, hasAccounts }: TPagePropsWithSetting hideMobileMenu hasAccounts={hasAccounts} > - - - - - - - - - + @@ -103,6 +88,47 @@ export const AdvancedSettings = ({ devices, hasAccounts }: TPagePropsWithSetting ); }; +export const AdvancedSettingsContent = ({ + devices, + visibleItemIDs, +}: TProps) => { + const fetchedConfig = useLoad(getConfig) as TConfig; + const [config, setConfig] = useState(); + + const frontendConfig = config?.frontend; + const backendConfig = config?.backend; + const proxyConfig = config?.backend?.proxy; + + useEffect(() => { + setConfig(fetchedConfig); + }, [fetchedConfig]); + + const deviceIDs = Object.keys(devices); + const sections: TSettingsContentSection[] = [ + { + id: 'advanced-settings', + items: [ + { id: 'custom-fees', content: }, + { id: 'coin-control', content: }, + { id: 'screen-lock', content: }, + { id: 'tor-proxy', content: }, + { id: 'testnet-mode', content: }, + { id: 'gap-limit', content: }, + { id: 'test-wallet', content: }, + { id: 'full-node', content: }, + { id: 'export-logs', content: }, + ], + }, + ]; + + return ( + + ); +}; + const AdvancedSettingsGuide = () => { const { t } = useTranslation(); diff --git a/frontends/web/src/routes/settings/bb02-settings.module.css b/frontends/web/src/routes/settings/bb02-settings.module.css index e4de1ab837..de198f7393 100644 --- a/frontends/web/src/routes/settings/bb02-settings.module.css +++ b/frontends/web/src/routes/settings/bb02-settings.module.css @@ -1,11 +1,3 @@ -.section { - margin-bottom: var(--space-default) -} - -.section h3 { - margin-bottom: var(--space-half) -} - .skeletonWrapper { margin-bottom: var(--space-half); } @@ -15,8 +7,4 @@ .skeletonWrapper { margin-bottom: 2px; } - - .withMobilePadding { - padding: 0 var(--space-half); - } -} \ No newline at end of file +} diff --git a/frontends/web/src/routes/settings/bb02-settings.tsx b/frontends/web/src/routes/settings/bb02-settings.tsx index 81e1c9d0a5..fe9c1755af 100644 --- a/frontends/web/src/routes/settings/bb02-settings.tsx +++ b/frontends/web/src/routes/settings/bb02-settings.tsx @@ -29,14 +29,20 @@ import { ManageDeviceGuide } from '@/routes/device/bitbox02/settings-guide'; import { MobileHeader } from './components/mobile-header'; import { ContentWrapper } from '@/components/contentwrapper/contentwrapper'; import { GlobalBanners } from '@/components/banners'; +import { SettingsContent, type TSettingsContentSection } from './components/settings-content'; import { SubTitle } from '@/components/title'; import styles from './bb02-settings.module.css'; -type TProps = { +type TCommonProps = { deviceID: string; }; -type TWrapperProps = TProps & TPagePropsWithSettingsTabs; +type TWrapperProps = TCommonProps & TPagePropsWithSettingsTabs; + +type TProps = TCommonProps & { + showSectionTitles?: boolean; + visibleItemIDs?: string[]; +}; export const StyledSkeleton = () => { return ( @@ -70,7 +76,7 @@ const BB02Settings = ({ deviceID, devices, hasAccounts }: TWrapperProps) => { hideMobileMenu hasAccounts={hasAccounts} > - + @@ -81,7 +87,11 @@ const BB02Settings = ({ deviceID, devices, hasAccounts }: TWrapperProps) => { ); }; -const Content = ({ deviceID }: TProps) => { +export const ManageDeviceSettingsContent = ({ + deviceID, + showSectionTitles = true, + visibleItemIDs, +}: TProps) => { const { t } = useTranslation(); const [deviceInfo, setDeviceInfo] = useState(); @@ -100,103 +110,126 @@ const Content = ({ deviceID }: TProps) => { .catch(console.error); }, [deviceID, t]); - return ( - <> - {/*"Backups" section*/} -
- {t('deviceSettings.backups.title')} - - -
+ const hasBluetooth = !!deviceInfo?.bluetooth; + const canToggleBluetooth = hasBluetooth && !runningInIOS(); - {/*"Device settings" section*/} -
- {t('deviceSettings.deviceSettings.title')} - {deviceInfo ? ( - - ) : - - } - { deviceInfo && deviceInfo.bluetooth && !runningInIOS() - ? - : null - } + const sections: TSettingsContentSection[] = [ + { + id: 'backups', + items: [ + { id: 'manage-backups', content: }, + { id: 'show-recovery-words', content: }, + ], + title: {t('deviceSettings.backups.title')}, + }, + { + id: 'device-settings', + items: [ { - versionInfo ? ( + id: 'device-name', + content: deviceInfo ? ( + + ) : ( + + ), + }, + ...(canToggleBluetooth ? [{ + id: 'bluetooth', + content: , + }] : []), + { + id: 'device-password', + content: versionInfo ? ( ) : ( - ) - } -
- - {/*"Device information" section*/} -
- {t('deviceSettings.deviceInformation.title')} + ), + }, + ], + title: {t('deviceSettings.deviceSettings.title')}, + }, + { + id: 'device-information', + items: [ { - versionInfo ? ( + id: 'firmware', + content: versionInfo ? ( - ) : + ) : ( - } - { - deviceInfo && deviceInfo.bluetooth ? ( + ), + }, + ...(hasBluetooth && deviceInfo?.bluetooth ? [{ + id: 'bluetooth-firmware', + content: ( - ) : null - } - + ), + }] : []), + { id: 'authenticity-check', content: }, { - rootFingerprintResult && rootFingerprintResult.success ? - - : - - } + id: 'root-fingerprint', + content: rootFingerprintResult && rootFingerprintResult.success + ? + : , + }, { - deviceInfo && deviceInfo.securechipModel !== '' ? - - : - - } -
- - {/*"Expert settings" section*/} -
- {t('settings.expert.title')} + id: 'secure-chip', + content: deviceInfo && deviceInfo.securechipModel !== '' + ? + : , + }, + ], + title: {t('deviceSettings.deviceInformation.title')}, + }, + { + id: 'expert-settings', + items: [ { - deviceInfo ? ( + id: 'passphrase', + content: deviceInfo ? ( ) : ( - ) - } + ), + }, { - versionInfo ? ( + id: 'bip85', + content: versionInfo ? ( ) : ( - ) - } - - -
- + ), + }, + { id: 'startup-settings', content: }, + { id: 'factory-reset', content: }, + ], + title: {t('settings.expert.title')}, + }, + ]; + + return ( + ); }; diff --git a/frontends/web/src/routes/settings/components/settings-content.test.tsx b/frontends/web/src/routes/settings/components/settings-content.test.tsx new file mode 100644 index 0000000000..eb981e961e --- /dev/null +++ b/frontends/web/src/routes/settings/components/settings-content.test.tsx @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { SettingsContent, type TSettingsContentSection } from './settings-content'; + +const sections: TSettingsContentSection[] = [ + { + id: 'general', + items: [ + { id: 'language', content:
Language setting
}, + { id: 'currency', content:
Currency setting
}, + ], + title: 'General', + }, + { + id: 'device', + items: [ + { id: 'firmware', content:
Firmware setting
}, + ], + title: 'Device', + }, +]; + +describe('SettingsContent', () => { + it('renders all items when visibleItemIDs is omitted', () => { + render(); + + expect(screen.getByText('Language setting')).toBeInTheDocument(); + expect(screen.getByText('Currency setting')).toBeInTheDocument(); + expect(screen.getByText('Firmware setting')).toBeInTheDocument(); + }); + + it('renders only matching items when visibleItemIDs is provided', () => { + render(); + + expect(screen.queryByText('Language setting')).not.toBeInTheDocument(); + expect(screen.getByText('Currency setting')).toBeInTheDocument(); + expect(screen.queryByText('Firmware setting')).not.toBeInTheDocument(); + }); + + it('hides sections without visible items', () => { + render(); + + expect(screen.queryByText('General')).not.toBeInTheDocument(); + expect(screen.getByText('Device')).toBeInTheDocument(); + expect(screen.getByText('Firmware setting')).toBeInTheDocument(); + }); + + it('hides section titles when showSectionTitles is false', () => { + render(); + + expect(screen.queryByText('General')).not.toBeInTheDocument(); + expect(screen.queryByText('Device')).not.toBeInTheDocument(); + expect(screen.getByText('Language setting')).toBeInTheDocument(); + expect(screen.getByText('Firmware setting')).toBeInTheDocument(); + }); +}); diff --git a/frontends/web/src/routes/settings/components/settings-content.tsx b/frontends/web/src/routes/settings/components/settings-content.tsx new file mode 100644 index 0000000000..f70aee9e81 --- /dev/null +++ b/frontends/web/src/routes/settings/components/settings-content.tsx @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { Fragment, ReactNode } from 'react'; + +export type TSettingsContentItem = { + content: ReactNode; + id: string; +}; + +export type TSettingsContentSection = { + id: string; + items: TSettingsContentItem[]; + title?: ReactNode; +}; + +type TProps = { + sections: TSettingsContentSection[]; + showSectionTitles?: boolean; + visibleItemIDs?: string[]; +}; + +export const SettingsContent = ({ + sections, + showSectionTitles = true, + visibleItemIDs, +}: TProps) => { + const visibleItemIDsSet = visibleItemIDs ? new Set(visibleItemIDs) : undefined; + + return ( + <> + {sections.map(section => { + const visibleSettingItems = section.items.filter(settingItem => ( + !visibleItemIDsSet || visibleItemIDsSet.has(settingItem.id) + )); + + if (visibleSettingItems.length === 0) { + return null; + } + + return ( + + {showSectionTitles && section.title ? section.title : null} + {visibleSettingItems.map(settingItem => ( + + {settingItem.content} + + ))} + + ); + })} + + ); +}; diff --git a/frontends/web/src/routes/settings/components/settings-search-content.module.css b/frontends/web/src/routes/settings/components/settings-search-content.module.css new file mode 100644 index 0000000000..8c30e540c2 --- /dev/null +++ b/frontends/web/src/routes/settings/components/settings-search-content.module.css @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +.container { + margin-bottom: var(--space-default); +} + +.empty { + color: var(--color-secondary); + margin: 0 0 var(--space-default); +} + +.pageGroup { + margin-bottom: var(--space-default); +} + +.pageTitle { + margin-top: var(--space-default); +} diff --git a/frontends/web/src/routes/settings/components/settings-search-content.tsx b/frontends/web/src/routes/settings/components/settings-search-content.tsx new file mode 100644 index 0000000000..f8a0709d60 --- /dev/null +++ b/frontends/web/src/routes/settings/components/settings-search-content.tsx @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { useTranslation } from 'react-i18next'; +import type { TDevices } from '@/api/devices'; +import { SubTitle } from '@/components/title'; +import { AboutSettingsContent } from '../about'; +import { AdvancedSettingsContent } from '../advanced-settings'; +import { ManageDeviceSettingsContent } from '../bb02-settings'; +import { GeneralSettingsContent } from '../general'; +import { + SETTINGS_SEARCH_PAGE_ORDER, + groupSearchResultsByPage, + type TSettingsSearchItem, + type TSettingsSearchPage, +} from '../settings-search'; +import styles from './settings-search-content.module.css'; + +type TProps = { + devices: TDevices; + hasAccounts: boolean; + searchResults: TSettingsSearchItem[]; +}; + +export const SettingsSearchContent = ({ + devices, + hasAccounts, + searchResults, +}: TProps) => { + const { t } = useTranslation(); + const firstDeviceID = Object.keys(devices)[0]; + + const pageTitles: Record = { + about: t('settings.about'), + advanced: t('settings.advancedSettings'), + device: t('sidebar.device'), + general: t('settings.general'), + }; + + const searchResultsByPage = groupSearchResultsByPage(searchResults); + + return ( +
+ {searchResults.length === 0 ? ( +

+ {t('settings.search.noResults')} +

+ ) : ( + SETTINGS_SEARCH_PAGE_ORDER.map(page => { + const pageSearchResults = searchResultsByPage[page]; + if (pageSearchResults.length === 0) { + return null; + } + + const visibleItemIDs = pageSearchResults.map(searchResult => searchResult.id); + + return ( +
+ {pageTitles[page]} + {page === 'general' ? ( + + ) : null} + {page === 'device' && firstDeviceID ? ( + + ) : null} + {page === 'advanced' ? ( + + ) : null} + {page === 'about' ? ( + + ) : null} +
+ ); + }) + )} +
+ ); +}; diff --git a/frontends/web/src/routes/settings/components/tabs.module.css b/frontends/web/src/routes/settings/components/tabs.module.css index e927b535e8..d741325440 100644 --- a/frontends/web/src/routes/settings/components/tabs.module.css +++ b/frontends/web/src/routes/settings/components/tabs.module.css @@ -4,6 +4,10 @@ word-break: keep-all; } +.searchContainer { + margin-bottom: var(--space-default); +} + .container a { margin-right: var(--space-default); font-size: var(--size-subheader); diff --git a/frontends/web/src/routes/settings/components/tabs.tsx b/frontends/web/src/routes/settings/components/tabs.tsx index 0daad0926a..4a3d8632bc 100644 --- a/frontends/web/src/routes/settings/components/tabs.tsx +++ b/frontends/web/src/routes/settings/components/tabs.tsx @@ -8,6 +8,9 @@ import { useLoad } from '@/hooks/api'; import { getVersion } from '@/api/bitbox02'; import { useDarkmode } from '@/hooks/darkmode'; import { SettingsItem } from './settingsItem/settingsItem'; +import { SettingsSearchContent } from './settings-search-content'; +import { SearchInput } from '@/components/forms'; +import { useSettingsSearch } from '../use-settings-search'; import { AdvancedSettingsIcon, AdvancedSettingsIconDark, @@ -29,6 +32,7 @@ type TWithSettingsTabsProps = { devices: TDevices; hasAccounts: boolean; hideMobileMenu?: boolean; + hideSearch?: boolean; }; type TTab = { @@ -49,8 +53,21 @@ export const WithSettingsTabs = ({ children, devices, hideMobileMenu, + hideSearch, hasAccounts, }: TWithSettingsTabsProps) => { + const { t } = useTranslation(); + const { + searchResults, + searchTerm, + showSearchResults, + updateSearchTerm, + } = useSettingsSearch({ + devices, + hasAccounts, + hideSearch, + }); + return ( <>
@@ -60,7 +77,27 @@ export const WithSettingsTabs = ({ hasAccounts={hasAccounts} />
- {children} + {!hideSearch && ( + <> +
+ updateSearchTerm(event.currentTarget.value)} + onClear={() => updateSearchTerm('')} + placeholder={t('generic.search')} + value={searchTerm} + variant="clear" + /> +
+ + )} + {showSearchResults ? ( + + ) : children} ); }; diff --git a/frontends/web/src/routes/settings/general.module.css b/frontends/web/src/routes/settings/general.module.css deleted file mode 100644 index c13dc77588..0000000000 --- a/frontends/web/src/routes/settings/general.module.css +++ /dev/null @@ -1,5 +0,0 @@ -@media (max-width: 768px) { - .subtitleWithMobilePadding { - padding: 0 var(--space-half); - } -} \ No newline at end of file diff --git a/frontends/web/src/routes/settings/general.tsx b/frontends/web/src/routes/settings/general.tsx index 0d547b2952..65a3b26775 100644 --- a/frontends/web/src/routes/settings/general.tsx +++ b/frontends/web/src/routes/settings/general.tsx @@ -13,11 +13,17 @@ import { WithSettingsTabs } from './components/tabs'; import { MobileHeader } from './components/mobile-header'; import { Guide } from '@/components/guide/guide'; import { Entry } from '@/components/guide/entry'; +import { SettingsContent, type TSettingsContentSection } from './components/settings-content'; import { SubTitle } from '@/components/title'; import { TPagePropsWithSettingsTabs } from './types'; import { GlobalBanners } from '@/components/banners'; import { ContentWrapper } from '@/components/contentwrapper/contentwrapper'; -import style from './general.module.css'; + +type TProps = { + hasAccounts: boolean; + showSectionTitles?: boolean; + visibleItemIDs?: string[]; +}; export const General = ({ devices, hasAccounts }: TPagePropsWithSettingsTabs) => { const { t } = useTranslation(); @@ -39,22 +45,7 @@ export const General = ({ devices, hasAccounts }: TPagePropsWithSettingsTabs) => - - {t('settings.appearance')} - - - - - - { hasAccounts ? ( - <> - - {t('settings.notes.title')} - - - - - ) : null } + @@ -66,6 +57,43 @@ export const General = ({ devices, hasAccounts }: TPagePropsWithSettingsTabs) => ); }; +export const GeneralSettingsContent = ({ + hasAccounts, + showSectionTitles = true, + visibleItemIDs, +}: TProps) => { + const { t } = useTranslation(); + + const sections: TSettingsContentSection[] = [ + { + id: 'appearance', + items: [ + { id: 'language', content: }, + { id: 'default-currency', content: }, + { id: 'active-currencies', content: }, + { id: 'dark-mode', content: }, + ], + title: {t('settings.appearance')}, + }, + ...(hasAccounts ? [{ + id: 'notes', + items: [ + { id: 'export-notes', content: }, + { id: 'import-notes', content: }, + ], + title: {t('settings.notes.title')}, + }] : []), + ]; + + return ( + + ); +}; + const GeneralGuide = () => { const { t } = useTranslation(); diff --git a/frontends/web/src/routes/settings/manage-accounts.tsx b/frontends/web/src/routes/settings/manage-accounts.tsx index 9c16f8b3ee..5aec485163 100644 --- a/frontends/web/src/routes/settings/manage-accounts.tsx +++ b/frontends/web/src/routes/settings/manage-accounts.tsx @@ -208,7 +208,7 @@ export const ManageAccounts = ({ accounts, devices, hasAccounts }: Props) => { } /> - +