From ec98130defb414d739e0bce202617c50cfc1560f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Thu, 23 Apr 2026 21:58:33 +0200 Subject: [PATCH] fix(admin-panel): inline skeleton loaders for table sections (#20014) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary \`SettingsSkeletonLoader\` wraps its content in \`PageHeader\` + \`PageBody\`, which is right for **full-page replacement** (user detail, config variable detail, etc.) but renders as a large empty stub with one tiny floating bar when placed **inline inside a section that's already scaffolded**. That's what showed up in Recent Users, Top Workspaces, and the Chats tab in the admin panel — a jarring white page flash where a few row placeholders should be. This PR adds \`SettingsAdminSectionSkeletonLoader\` — a small, configurable-row-count skeleton that uses the project's standard \`SkeletonTheme\` pattern (\`theme.background.tertiary\` / \`theme.background.transparent.lighter\` + \`borderRadius: 4\`, matching \`PageContentSkeletonLoader\` and \`SettingsAdminTabSkeletonLoader\`) and renders row-height bars that match \`TableRow\` spacing. Swaps it into the three inline call sites. Full-page usages of \`SettingsSkeletonLoader\` are unchanged. ### Changed - **New**: \`packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminSectionSkeletonLoader.tsx\` - **Swapped** (3 inline loading states): \`SettingsAdminGeneral.tsx\` (Recent Users + Top Workspaces), \`SettingsAdminWorkspaceDetail.tsx\` (Chats tab) ## Test plan - [x] typecheck (\`npx nx typecheck twenty-front --skip-nx-cache\`) — clean - [x] oxlint — 0 warnings - [x] prettier — clean - [ ] Visual: navigate to admin panel, observe Recent Users / Top Workspaces / Chats tab loading — should see a tight stack of row-height placeholder bars instead of a big empty page stub 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .../components/SettingsAccountLoader.tsx | 16 ------ .../ai/components/SettingsAdminAI.tsx | 4 +- .../components/SettingsAdminGeneral.tsx | 6 +-- .../SettingsAdminTabSkeletonLoader.tsx | 18 ------- .../SettingsAdminConfigVariables.tsx | 4 +- .../components/SettingsAdminHealthStatus.tsx | 4 +- .../SettingsSectionSkeletonLoader.tsx | 38 +++++++++++++ .../settings/accounts/SettingsAccounts.tsx | 4 +- .../SettingsAdminWorkspaceDetail.tsx | 3 +- .../SettingsApplicationDetails.tsx | 4 +- ...ettingsApplicationDetailSkeletonLoader.tsx | 53 ------------------- 11 files changed, 53 insertions(+), 101 deletions(-) delete mode 100644 packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountLoader.tsx delete mode 100644 packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabSkeletonLoader.tsx create mode 100644 packages/twenty-front/src/modules/settings/components/SettingsSectionSkeletonLoader.tsx delete mode 100644 packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationDetailSkeletonLoader.tsx diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountLoader.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountLoader.tsx deleted file mode 100644 index c5d50418d3..0000000000 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountLoader.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useContext } from 'react'; -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; -import { ThemeContext } from 'twenty-ui/theme-constants'; - -export const SettingsAccountLoader = () => { - const { theme } = useContext(ThemeContext); - return ( - - - - ); -}; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAI.tsx b/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAI.tsx index a815d64d33..34f22c5ece 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAI.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAI.tsx @@ -25,7 +25,7 @@ import { GET_AI_PROVIDERS } from '@/settings/admin-panel/ai/graphql/queries/getA import { type GetAiProvidersResult } from '@/settings/admin-panel/ai/types/GetAiProvidersResult'; import { parseProviderItems } from '@/settings/admin-panel/ai/utils/parseProviderItems'; import { getModelIcon } from '@/settings/ai/utils/getModelIcon'; -import { SettingsAdminTabSkeletonLoader } from '@/settings/admin-panel/components/SettingsAdminTabSkeletonLoader'; +import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader'; import { SettingsEnterpriseFeatureGateCard } from '@/settings/components/SettingsEnterpriseFeatureGateCard'; import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect'; import { useUsageValueFormatter } from '@/settings/usage/hooks/useUsageValueFormatter'; @@ -128,7 +128,7 @@ export const SettingsAdminAI = () => { ); if (isLoadingProviders || isLoadingModels) { - return ; + return ; } const handleRecommendedToggle = async ( diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx index 500349bc75..4a2c0b879a 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx @@ -1,9 +1,9 @@ import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState'; import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient'; +import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader'; import { SettingsAdminVersionContainer } from '@/settings/admin-panel/components/SettingsAdminVersionContainer'; import { ADMIN_PANEL_RECENT_USERS } from '@/settings/admin-panel/graphql/queries/adminPanelRecentUsers'; import { ADMIN_PANEL_TOP_WORKSPACES } from '@/settings/admin-panel/graphql/queries/adminPanelTopWorkspaces'; -import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { Table } from '@/ui/layout/table/components/Table'; import { TableBody } from '@/ui/layout/table/components/TableBody'; @@ -105,7 +105,7 @@ export const SettingsAdminGeneral = () => { fullWidth /> {isLoadingUsers ? ( - + ) : recentUsers.length === 0 ? ( {t`No users found matching your search criteria.`} @@ -154,7 +154,7 @@ export const SettingsAdminGeneral = () => { fullWidth /> {isLoadingWorkspaces ? ( - + ) : topWorkspaces.length === 0 ? ( {t`No workspaces found matching your search criteria.`} diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabSkeletonLoader.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabSkeletonLoader.tsx deleted file mode 100644 index a57a03cd43..0000000000 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabSkeletonLoader.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useContext } from 'react'; -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; - -import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; -import { ThemeContext } from 'twenty-ui/theme-constants'; - -export const SettingsAdminTabSkeletonLoader = () => { - const { theme } = useContext(ThemeContext); - return ( - - - - ); -}; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables.tsx b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables.tsx index b0d045a008..f33af86578 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables.tsx @@ -1,4 +1,4 @@ -import { SettingsAdminTabSkeletonLoader } from '@/settings/admin-panel/components/SettingsAdminTabSkeletonLoader'; +import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader'; import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient'; import { ConfigVariableFilterContainer } from '@/settings/admin-panel/config-variables/components/ConfigVariableFilterContainer'; import { ConfigVariableFilterDropdown } from '@/settings/admin-panel/config-variables/components/ConfigVariableFilterDropdown'; @@ -160,7 +160,7 @@ export const SettingsAdminConfigVariables = () => { }, [filteredVariables, allGroups]); if (configVariablesLoading) { - return ; + return ; } return ( diff --git a/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsAdminHealthStatus.tsx b/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsAdminHealthStatus.tsx index b79211ca8a..ef72e23398 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsAdminHealthStatus.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsAdminHealthStatus.tsx @@ -1,5 +1,5 @@ import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient'; -import { SettingsAdminTabSkeletonLoader } from '@/settings/admin-panel/components/SettingsAdminTabSkeletonLoader'; +import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader'; import { SettingsAdminHealthStatusListCard } from '@/settings/admin-panel/health-status/components/SettingsAdminHealthStatusListCard'; import { SettingsAdminMaintenanceModeFetchEffect } from '@/settings/admin-panel/health-status/maintenance-mode/components/SettingsAdminMaintenanceModeFetchEffect'; import { SettingsAdminMaintenanceMode } from '@/settings/admin-panel/health-status/maintenance-mode/components/SettingsAdminMaintenanceMode'; @@ -22,7 +22,7 @@ export const SettingsAdminHealthStatus = () => { const services = data?.getSystemHealthStatus.services ?? []; if (loadingHealthStatus) { - return ; + return ; } return ( diff --git a/packages/twenty-front/src/modules/settings/components/SettingsSectionSkeletonLoader.tsx b/packages/twenty-front/src/modules/settings/components/SettingsSectionSkeletonLoader.tsx new file mode 100644 index 0000000000..72af8476be --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/SettingsSectionSkeletonLoader.tsx @@ -0,0 +1,38 @@ +import { styled } from '@linaria/react'; +import { useContext } from 'react'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; + +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; + +type SettingsSectionSkeletonLoaderProps = { + rowCount?: number; +}; + +const StyledRows = styled.div` + display: flex; + flex-direction: column; + gap: ${themeCssVariables.spacing[2]}; + width: 100%; +`; + +export const SettingsSectionSkeletonLoader = ({ + rowCount = 4, +}: SettingsSectionSkeletonLoaderProps) => { + const { theme } = useContext(ThemeContext); + + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx index 539f270b7b..a25028f81e 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx @@ -1,5 +1,5 @@ import { SettingsPath } from 'twenty-shared/types'; -import { SettingsAccountLoader } from '@/settings/accounts/components/SettingsAccountLoader'; +import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader'; import { SettingsAccountsBlocklistSection } from '@/settings/accounts/components/SettingsAccountsBlocklistSection'; import { SettingsAccountsConnectedAccountsListCard } from '@/settings/accounts/components/SettingsAccountsConnectedAccountsListCard'; import { SettingsAccountsSettingsSection } from '@/settings/accounts/components/SettingsAccountsSettingsSection'; @@ -29,7 +29,7 @@ export const SettingsAccounts = () => { > {loading ? ( - + ) : ( <>
diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx index ff83f7d0d9..2162e67f9e 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx @@ -10,6 +10,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState'; import { AI_ADMIN_PATH } from '@/settings/admin-panel/ai/constants/AiAdminPath'; import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient'; +import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader'; import { SettingsAdminWorkspaceContent } from '@/settings/admin-panel/components/SettingsAdminWorkspaceContent'; import { GET_ADMIN_WORKSPACE_CHAT_THREADS } from '@/settings/admin-panel/graphql/queries/getAdminWorkspaceChatThreads'; import { WORKSPACE_LOOKUP_ADMIN_PANEL } from '@/settings/admin-panel/graphql/queries/workspaceLookupAdminPanel'; @@ -304,7 +305,7 @@ export const SettingsAdminWorkspaceDetail = () => { description={t`AI chat threads for this workspace`} /> {isLoadingThreads ? ( - + ) : threads.length === 0 ? ( diff --git a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx index fb7d56aadc..0d5893fd3e 100644 --- a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx +++ b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx @@ -40,7 +40,7 @@ import { UninstallApplicationDocument, } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; -import { SettingsApplicationDetailSkeletonLoader } from '~/pages/settings/applications/components/SettingsApplicationDetailSkeletonLoader'; +import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader'; import { SettingsApplicationDetailTitle } from '~/pages/settings/applications/components/SettingsApplicationDetailTitle'; import { CUSTOM_APPLICATION_ILLUSTRATIONS } from '~/pages/settings/applications/constants/CustomApplicationIllustrations'; import { STANDARD_APPLICATION_ILLUSTRATIONS } from '~/pages/settings/applications/constants/StandardApplicationIllustrations'; @@ -241,7 +241,7 @@ export const SettingsApplicationDetails = () => { const renderActiveTabContent = () => { if (!isDefined(application)) { - return ; + return ; } switch (activeTabId) { diff --git a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationDetailSkeletonLoader.tsx b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationDetailSkeletonLoader.tsx deleted file mode 100644 index 3fd78e9ecd..0000000000 --- a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationDetailSkeletonLoader.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { styled } from '@linaria/react'; -import { useContext } from 'react'; -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; - -import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; -import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; - -const StyledSkeletonContainer = styled.div` - display: flex; - flex-direction: column; - gap: ${themeCssVariables.spacing[8]}; -`; - -const StyledFormSection = styled.div` - display: flex; - flex-direction: column; - gap: ${themeCssVariables.spacing[2]}; -`; - -export const SettingsApplicationDetailSkeletonLoader = () => { - const { theme } = useContext(ThemeContext); - return ( - - - - - - - - - - - - - - ); -};