perf(settings): speed up board settings navigation (#1191)

* perf(settings): speed up board settings navigation

* perf(settings): parallelize account section loading
This commit is contained in:
Tommaso Casaburi
2026-08-01 19:12:20 +02:00
committed by GitHub
parent 660cdf8a2d
commit a9040a5425
4 changed files with 63 additions and 32 deletions
+1 -1
View File
@@ -56,6 +56,7 @@ import BoardBlotter from './components/board-blotter/board-blotter';
import BoardsBar from './components/boards-bar/boards-bar';
import ExternalQuoteStatus from './components/external-quote-status/external-quote-status';
import ModEmptyState from './components/mod-empty-state/mod-empty-state';
import SettingsModal from './components/settings-modal';
const AccountDataEditor = lazy(() => import('./views/account-data-editor'));
const BoardsBarEditModal = lazy(() => import('./components/boards-bar-edit-modal'));
@@ -64,7 +65,6 @@ const ChallengeModal = lazy(() => import('./components/challenge-modal'));
const DirectoryModal = lazy(() => import('./components/directory-modal'));
const DisclaimerModal = lazy(() => import('./components/disclaimer-modal'));
const ReplyModal = lazy(() => import('./components/reply-modal'));
const SettingsModal = lazy(() => import('./components/settings-modal'));
const SettingsUpgradeModal = lazy(() => import('./components/settings-upgrade-modal'));
// Preload all theme assets (buttons, backgrounds) immediately on app load
@@ -76,8 +76,8 @@ const LocationProbe = () => {
let root: Root;
let container: HTMLDivElement;
const render = (initialEntry = '/all/settings') => {
act(() => {
const render = async (initialEntry = '/all/settings') => {
await act(async () => {
root.render(
createElement(MemoryRouter, { initialEntries: [initialEntry] }, createElement(React.Fragment, {}, createElement(SettingsModal), createElement(LocationProbe))),
);
@@ -123,8 +123,8 @@ describe('SettingsModal', () => {
container.remove();
});
it('opens the account section for crypto subsection hashes', () => {
render('/all/settings#crypto-wallet-settings');
it('opens the account section for crypto subsection hashes', async () => {
await render('/all/settings#crypto-wallet-settings');
expect(container.querySelector('[data-testid="account-settings"]')).not.toBeNull();
expect(container.querySelector('[data-testid="crypto-address-setting"]')).not.toBeNull();
@@ -132,7 +132,7 @@ describe('SettingsModal', () => {
});
it('updates the section query when sections open and close', async () => {
render('/all/settings?section=account-settings');
await render('/all/settings?section=account-settings');
expect(getLocationText()).toBe('/all/settings?section=account-settings');
@@ -160,7 +160,7 @@ describe('SettingsModal', () => {
});
it('expands and collapses all settings sections', async () => {
render('/all/settings');
await render('/all/settings');
const expandAllControl = Array.from(container.querySelectorAll('button')).find((candidate) => (candidate.textContent ?? '').includes('expand_all_settings'));
if (!expandAllControl) {
@@ -208,7 +208,7 @@ describe('SettingsModal', () => {
};
useSettingsUpgradeReviewStore.getState().dismissUpgradeKeys([upgradeKey]);
render('/all/settings');
await render('/all/settings');
expect(container.textContent).toContain('settings_upgrade_review_notice');
const reviewButton = Array.from(container.querySelectorAll('button')).find((candidate) => candidate.textContent === 'settings_upgrade_review_button');
@@ -233,14 +233,14 @@ describe('SettingsModal', () => {
expect(useSettingsUpgradeReviewStore.getState().reviewRequestId).toBe(1);
});
it.each(['/all/settings?section=p2p-stats-settings', '/all/settings#p2p-stats-settings'])('opens the p2p stats section from route %s', (route) => {
render(route);
it.each(['/all/settings?section=p2p-stats-settings', '/all/settings#p2p-stats-settings'])('opens the p2p stats section from route %s', async (route) => {
await render(route);
expect(container.querySelector('[data-testid="p2p-stats-settings-panel"]')).not.toBeNull();
});
it('closes the modal when the overlay is clicked', async () => {
render('/all/settings#interface-settings');
await render('/all/settings#interface-settings');
const overlay = container.querySelector('button');
if (!overlay) {
@@ -255,7 +255,7 @@ describe('SettingsModal', () => {
});
it('closes the modal when Escape is pressed', async () => {
render('/all/settings');
await render('/all/settings');
await act(async () => {
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
@@ -1,22 +1,23 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from 'react';
import { useAccount, usePkcRpcSettings } from '@bitsocial/bitsocial-react-hooks';
import { useLocation, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import styles from './settings-modal.module.css';
import AccountSettings from './account-settings/account-settings';
import CryptoAddressSetting from './crypto-address-setting/crypto-address-setting';
import CryptoWalletsSetting from './crypto-wallets-setting/crypto-wallets-setting';
import InterfaceSettings from './interface-settings/interface-settings';
import MediaHostingSettings from './media-hosting-settings/media-hosting-settings';
import AdvancedSettings from './advanced-settings/advanced-settings';
import SubscriptionsSetting from './subscriptions-setting/subscriptions-setting';
import TrustedBoardLinksSetting from './trusted-board-links-setting/trusted-board-links-setting';
import P2PStatsSettings from './p2p-stats-settings/p2p-stats-settings';
import { P2P_STATS_SECTION_ID, shouldShowP2PSettingsSection } from '../../lib/p2p-runtime';
import { getReviewableSettingsUpgrades, getSettingsUpgradeKey, type SettingsUpgradeAccount } from '../../lib/settings-upgrades';
import useSettingsUpgradeReviewStore from '../../stores/use-settings-upgrade-review-store';
import { getSettingsSectionPath } from '../../lib/utils/route-utils';
const AccountSettings = lazy(() => import('./account-settings/account-settings'));
const CryptoAddressSetting = lazy(() => import('./crypto-address-setting/crypto-address-setting'));
const CryptoWalletsSetting = lazy(() => import('./crypto-wallets-setting/crypto-wallets-setting'));
const InterfaceSettings = lazy(() => import('./interface-settings/interface-settings'));
const MediaHostingSettings = lazy(() => import('./media-hosting-settings/media-hosting-settings'));
const AdvancedSettings = lazy(() => import('./advanced-settings/advanced-settings'));
const SubscriptionsSetting = lazy(() => import('./subscriptions-setting/subscriptions-setting'));
const TrustedBoardLinksSetting = lazy(() => import('./trusted-board-links-setting/trusted-board-links-setting'));
const P2PStatsSettings = lazy(() => import('./p2p-stats-settings/p2p-stats-settings'));
const allSectionIds = [
'interface-settings',
'media-hosting-settings',
@@ -164,14 +165,22 @@ const SettingsModal = () => {
{t('interface')}
</button>
</div>
{showInterfaceSettings && <InterfaceSettings />}
{showInterfaceSettings && (
<Suspense fallback={null}>
<InterfaceSettings />
</Suspense>
)}
<div id='media-hosting-settings' className={`${styles.setting} ${styles.category}`}>
<button type='button' className={styles.categoryButton} onClick={() => handleCategoryClick('media-hosting-settings')}>
<span className={showMediaHostingSettings ? styles.hideButton : styles.showButton} />
{t('media_hosting')}
</button>
</div>
{showMediaHostingSettings && <MediaHostingSettings />}
{showMediaHostingSettings && (
<Suspense fallback={null}>
<MediaHostingSettings />
</Suspense>
)}
<div id='account-settings' className={`${styles.setting} ${styles.category}`}>
<button type='button' className={styles.categoryButton} onClick={() => handleCategoryClick('account-settings')}>
<span className={showAccountSettings ? styles.hideButton : styles.showButton} />
@@ -180,11 +189,17 @@ const SettingsModal = () => {
</div>
{showAccountSettings && (
<>
<AccountSettings />
<Suspense fallback={null}>
<AccountSettings />
</Suspense>
<div className={styles.subSectionHeader}>{t('crypto_address')}</div>
<CryptoAddressSetting />
<Suspense fallback={null}>
<CryptoAddressSetting />
</Suspense>
<div className={styles.subSectionHeader}>{t('crypto_wallets')}</div>
<CryptoWalletsSetting />
<Suspense fallback={null}>
<CryptoWalletsSetting />
</Suspense>
</>
)}
<div id='subscriptions-settings' className={`${styles.setting} ${styles.category}`}>
@@ -193,21 +208,33 @@ const SettingsModal = () => {
{t('board_subscriptions')}
</button>
</div>
{showSubscriptionsSettings && <SubscriptionsSetting />}
{showSubscriptionsSettings && (
<Suspense fallback={null}>
<SubscriptionsSetting />
</Suspense>
)}
<div id='board-link-permissions-settings' className={`${styles.setting} ${styles.category}`}>
<button type='button' className={styles.categoryButton} onClick={() => handleCategoryClick('board-link-permissions-settings')}>
<span className={showBoardLinkPermissionsSettings ? styles.hideButton : styles.showButton} />
{t('board_link_permissions')}
</button>
</div>
{showBoardLinkPermissionsSettings && <TrustedBoardLinksSetting />}
{showBoardLinkPermissionsSettings && (
<Suspense fallback={null}>
<TrustedBoardLinksSetting />
</Suspense>
)}
<div id='advanced-settings' className={`${styles.setting} ${styles.category}`}>
<button type='button' className={styles.categoryButton} onClick={() => handleCategoryClick('advanced-settings')}>
<span className={showAdvancedSettings ? styles.hideButton : styles.showButton} />
{t('advanced_settings')}
</button>
</div>
{showAdvancedSettings && <AdvancedSettings />}
{showAdvancedSettings && (
<Suspense fallback={null}>
<AdvancedSettings />
</Suspense>
)}
{sectionIds.includes(P2P_STATS_SECTION_ID) && (
<>
<div id={P2P_STATS_SECTION_ID} className={`${styles.setting} ${styles.category}`}>
@@ -216,7 +243,11 @@ const SettingsModal = () => {
{t('p2p_stats')}
</button>
</div>
{showP2PStatsSettings && <P2PStatsSettings />}
{showP2PStatsSettings && (
<Suspense fallback={null}>
<P2PStatsSettings />
</Suspense>
)}
</>
)}
{showSettingsUpgradeReview && (
+1 -1
View File
@@ -55,7 +55,7 @@ const renderRoot = async () => {
root.render(
<React.StrictMode>
<Router>
<Router useTransitions={false}>
<AppUpdateRegistration />
<App />
{Analytics && <Analytics />}