Skip to content

Commit a3a44c8

Browse files
authored
fix(front): settings skeleton, app-detail header & empty favorites (#21209)
Three small post-redesign UI fixes. Each is an independent commit, so they can be split into separate PRs if preferred. ## 1. Settings loading skeleton — match the rounded-card layout The redesign (#21131) moved settings chrome into a rounded card (`SettingsPageLayout`: bordered header with breadcrumb + centered title, optional secondary bar, 760px body), but `SettingsSkeletonLoader` still rendered the old flat `PageHeader` + `PageBody` — so pages painted as a full-width flat bar then snapped into the card. - `SettingsSkeletonLoader` now reproduces the card and **reuses the real `SettingsPageHeader` + `SettingsPageContainer`**, so the frame aligns by construction; the card CSS is replicated (not `SettingsPageLayout`) to avoid the layout's side effects (hotkeys, side panel, info banner). - It's **composed with `SettingsSectionSkeletonLoader`** so the loading body is identical whether or not chrome is present. Rule: no chrome on screen yet → full-page skeleton; chrome already on screen → body-only `SettingsSectionSkeletonLoader` (the admin Enterprise tab now uses it, matching its sibling tabs). A short comment on each component documents this. ## 2. Application detail header — pass a plain title `SettingsApplicationDetails` / `SettingsAvailableApplicationDetails` passed a custom `SettingsApplicationDetailTitle` (avatar + name + multi-line description, fixed width) into `SettingsPageLayout`'s **centered single-line title slot**, which broke the header. They now pass the app's display name like every other page. The available-app "unlisted" notice moves into the body as a reusable `InlineBanner`; the now-unused `SettingsApplicationDetailTitle` is removed. ## 3. Navigation — hide Favorites when empty Always rendering the Favorites section (#21087) left a stray "Favorites" title above Workspace for users with no favorites. It now renders only when at least one favorite exists (redundant per-child guards dropped). Note: the "+ add favorite" entry point therefore appears once you have ≥1 favorite; the first favorite is created from a record/view as before. ## Verification - `nx typecheck twenty-front` ✅ · `oxlint` + `oxfmt --check` on changed files ✅ - i18n catalogs intentionally untouched — handled by the repo's separate i18n pipeline.
1 parent e99116b commit a3a44c8

6 files changed

Lines changed: 122 additions & 227 deletions

File tree

packages/twenty-front/src/modules/navigation-menu-item/display/sections/favorites/components/FavoritesSection.tsx

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,6 @@ export const FavoritesSection = () => {
9191

9292
const handleAddFavorite = (event?: React.MouseEvent) => {
9393
event?.stopPropagation();
94-
// Expansion is gated on items existing, so this stays collapsed if the user
95-
// cancels and reveals the favorite as soon as it is added.
9694
openNavigationSection();
9795
setNavigationMenuItemEditSection('favorite');
9896
setPendingInsertionNavigationMenuItem(null);
@@ -119,10 +117,14 @@ export const FavoritesSection = () => {
119117
[deleteManyNavigationMenuItems],
120118
);
121119

120+
if (topLevelItems.length === 0) {
121+
return null;
122+
}
123+
122124
return (
123125
<NavigationMenuItemSection
124126
title={t`Favorites`}
125-
isOpen={topLevelItems.length > 0 && isNavigationSectionOpen}
127+
isOpen={isNavigationSectionOpen}
126128
onToggle={toggleNavigationSection}
127129
rightIcon={
128130
<LightIconButton
@@ -132,66 +134,64 @@ export const FavoritesSection = () => {
132134
/>
133135
}
134136
>
135-
{topLevelItems.length > 0 && (
136-
<StyledList>
137-
{topLevelItems.map((item, index) => (
138-
<StyledListItemRow key={item.id}>
139-
{index === 0 ? (
140-
<NavigationMenuItemDroppableSlot
141-
droppableId={ORPHAN_DROPPABLE_ID}
142-
index={0}
143-
disabled={favoritesDropDisabled}
144-
>
145-
<NavigationMenuItemOrphanDropTarget
146-
index={0}
147-
compact
148-
sectionId={NavigationSections.FAVORITES}
149-
droppableId={ORPHAN_DROPPABLE_ID}
150-
/>
151-
</NavigationMenuItemDroppableSlot>
152-
) : (
137+
<StyledList>
138+
{topLevelItems.map((item, index) => (
139+
<StyledListItemRow key={item.id}>
140+
{index === 0 ? (
141+
<NavigationMenuItemDroppableSlot
142+
droppableId={ORPHAN_DROPPABLE_ID}
143+
index={0}
144+
disabled={favoritesDropDisabled}
145+
>
153146
<NavigationMenuItemOrphanDropTarget
154-
index={index}
147+
index={0}
155148
compact
156149
sectionId={NavigationSections.FAVORITES}
157150
droppableId={ORPHAN_DROPPABLE_ID}
158151
/>
159-
)}
160-
<NavigationMenuItemSortableItem
161-
id={item.id}
152+
</NavigationMenuItemDroppableSlot>
153+
) : (
154+
<NavigationMenuItemOrphanDropTarget
162155
index={index}
163-
group={ORPHAN_DROPPABLE_ID}
164-
disabled={favoritesDropDisabled}
165-
>
166-
<NavigationMenuItemDisplay
167-
item={item}
168-
isEditInPlace={isNavigationMenuItemFolder(item)}
169-
isDragging={isDragging}
170-
folderChildrenById={folderChildrenById}
171-
folderCount={folderCount}
172-
rightOptions={
173-
isNavigationMenuItemFolder(item)
174-
? undefined
175-
: makeRightOptions(item)
176-
}
177-
/>
178-
</NavigationMenuItemSortableItem>
179-
</StyledListItemRow>
180-
))}
181-
<NavigationMenuItemDroppableSlot
182-
droppableId={ORPHAN_DROPPABLE_ID}
156+
compact
157+
sectionId={NavigationSections.FAVORITES}
158+
droppableId={ORPHAN_DROPPABLE_ID}
159+
/>
160+
)}
161+
<NavigationMenuItemSortableItem
162+
id={item.id}
163+
index={index}
164+
group={ORPHAN_DROPPABLE_ID}
165+
disabled={favoritesDropDisabled}
166+
>
167+
<NavigationMenuItemDisplay
168+
item={item}
169+
isEditInPlace={isNavigationMenuItemFolder(item)}
170+
isDragging={isDragging}
171+
folderChildrenById={folderChildrenById}
172+
folderCount={folderCount}
173+
rightOptions={
174+
isNavigationMenuItemFolder(item)
175+
? undefined
176+
: makeRightOptions(item)
177+
}
178+
/>
179+
</NavigationMenuItemSortableItem>
180+
</StyledListItemRow>
181+
))}
182+
<NavigationMenuItemDroppableSlot
183+
droppableId={ORPHAN_DROPPABLE_ID}
184+
index={topLevelItems.length}
185+
disabled={favoritesDropDisabled}
186+
>
187+
<NavigationMenuItemOrphanDropTarget
183188
index={topLevelItems.length}
184-
disabled={favoritesDropDisabled}
185-
>
186-
<NavigationMenuItemOrphanDropTarget
187-
index={topLevelItems.length}
188-
compact
189-
sectionId={NavigationSections.FAVORITES}
190-
droppableId={ORPHAN_DROPPABLE_ID}
191-
/>
192-
</NavigationMenuItemDroppableSlot>
193-
</StyledList>
194-
)}
189+
compact
190+
sectionId={NavigationSections.FAVORITES}
191+
droppableId={ORPHAN_DROPPABLE_ID}
192+
/>
193+
</NavigationMenuItemDroppableSlot>
194+
</StyledList>
195195
</NavigationMenuItemSection>
196196
);
197197
};

packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabContent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { SettingsAdminConfigVariables } from '@/settings/admin-panel/config-vari
55
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
66
import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId';
77
import { SettingsAdminHealthStatus } from '@/settings/admin-panel/health-status/components/SettingsAdminHealthStatus';
8-
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
8+
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
99
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
1010
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
1111
import { lazy, Suspense } from 'react';
@@ -34,7 +34,7 @@ export const SettingsAdminTabContent = () => {
3434
return <SettingsAdminHealthStatus />;
3535
case SETTINGS_ADMIN_TABS.ENTERPRISE:
3636
return (
37-
<Suspense fallback={<SettingsSkeletonLoader />}>
37+
<Suspense fallback={<SettingsSectionSkeletonLoader />}>
3838
<SettingsEnterprise isAdminPanelTab />
3939
</Suspense>
4040
);
Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,70 @@
11
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
2-
import { PageBody } from '@/ui/layout/page/components/PageBody';
3-
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
2+
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
3+
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
4+
import { SettingsPageHeader } from '@/settings/components/layout/SettingsPageHeader';
5+
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
46
import { styled } from '@linaria/react';
57
import { useContext } from 'react';
68
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
79
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
810

9-
const StyledContainer = styled.div`
11+
const StyledRoot = styled.div<{ isMobile: boolean }>`
1012
display: flex;
11-
flex-direction: column;
12-
width: 100%;
13+
flex: 1;
14+
min-height: 0;
15+
min-width: 0;
16+
padding: ${({ isMobile }) =>
17+
isMobile ? themeCssVariables.spacing[1] : themeCssVariables.spacing[2]};
1318
`;
1419

15-
const StyledTitleLoaderContainer = styled.div`
16-
margin: ${themeCssVariables.spacing[8]} ${themeCssVariables.spacing[8]}
17-
${themeCssVariables.spacing[2]};
20+
const StyledCard = styled.div`
21+
background: ${themeCssVariables.background.primary};
22+
border: 1px solid ${themeCssVariables.border.color.medium};
23+
border-radius: ${themeCssVariables.border.radius.md};
24+
box-sizing: border-box;
25+
display: flex;
26+
flex: 1;
27+
flex-direction: column;
28+
min-height: 0;
29+
overflow: hidden;
30+
width: 100%;
1831
`;
1932

2033
export const SettingsSkeletonLoader = () => {
34+
const isMobile = useIsMobile();
2135
const { theme } = useContext(ThemeContext);
36+
2237
return (
23-
<StyledContainer>
24-
<PageHeader
25-
title={
26-
<SkeletonTheme
27-
baseColor={theme.background.tertiary}
28-
highlightColor={theme.background.transparent.lighter}
29-
borderRadius={4}
30-
>
31-
<Skeleton
32-
height={SKELETON_LOADER_HEIGHT_SIZES.standard.m}
33-
width={120}
34-
/>{' '}
35-
</SkeletonTheme>
36-
}
37-
/>
38-
<PageBody>
39-
<StyledTitleLoaderContainer>
40-
<SkeletonTheme
41-
baseColor={theme.background.tertiary}
42-
highlightColor={theme.background.transparent.lighter}
43-
borderRadius={4}
44-
>
45-
<Skeleton
46-
height={SKELETON_LOADER_HEIGHT_SIZES.standard.m}
47-
width={200}
48-
/>
49-
</SkeletonTheme>
50-
</StyledTitleLoaderContainer>
51-
</PageBody>
52-
</StyledContainer>
38+
<StyledRoot isMobile={isMobile}>
39+
<StyledCard>
40+
<SkeletonTheme
41+
baseColor={theme.background.tertiary}
42+
highlightColor={theme.background.transparent.lighter}
43+
borderRadius={4}
44+
>
45+
<SettingsPageHeader
46+
links={[
47+
{
48+
children: (
49+
<Skeleton
50+
width={64}
51+
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
52+
/>
53+
),
54+
},
55+
]}
56+
title={
57+
<Skeleton
58+
width={120}
59+
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
60+
/>
61+
}
62+
/>
63+
<SettingsPageContainer>
64+
<SettingsSectionSkeletonLoader />
65+
</SettingsPageContainer>
66+
</SkeletonTheme>
67+
</StyledCard>
68+
</StyledRoot>
5369
);
5470
};

packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import {
4141
} from '~/generated-metadata/graphql';
4242
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
4343
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
44-
import { SettingsApplicationDetailTitle } from '~/pages/settings/applications/components/SettingsApplicationDetailTitle';
4544
import { CUSTOM_APPLICATION_ILLUSTRATIONS } from '~/pages/settings/applications/constants/CustomApplicationIllustrations';
4645
import { STANDARD_APPLICATION_ILLUSTRATIONS } from '~/pages/settings/applications/constants/StandardApplicationIllustrations';
4746
import { useFindApplicationConnectionProviders } from '~/pages/settings/applications/hooks/useFindApplicationConnectionProviders';
@@ -326,13 +325,7 @@ export const SettingsApplicationDetails = () => {
326325
return (
327326
<CurrentApplicationContext.Provider value={application?.id ?? null}>
328327
<SettingsPageLayout
329-
title={
330-
<SettingsApplicationDetailTitle
331-
displayName={displayName}
332-
description={description}
333-
applicationId={application?.id}
334-
/>
335-
}
328+
title={displayName}
336329
links={[
337330
{
338331
children: t`Workspace`,

packages/twenty-front/src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,21 @@ import {
1919
IconBook,
2020
IconBox,
2121
IconCommand,
22+
IconEyeOff,
2223
IconGraph,
2324
IconInfoCircle,
2425
IconLego,
2526
IconListDetails,
2627
IconLock,
2728
IconShield,
29+
InlineBanner,
2830
} from 'twenty-ui/display';
2931
import {
3032
ApplicationRegistrationSourceType,
3133
FindMarketplaceAppDetailDocument,
3234
FindOneApplicationByUniversalIdentifierDocument,
3335
PermissionFlagType,
3436
} from '~/generated-metadata/graphql';
35-
import { SettingsApplicationDetailTitle } from '~/pages/settings/applications/components/SettingsApplicationDetailTitle';
3637
import { SettingsApplicationDetailAboutTab } from '~/pages/settings/applications/tabs/SettingsApplicationDetailAboutTab';
3738
import { SettingsApplicationDetailContentTab } from '~/pages/settings/applications/tabs/SettingsApplicationDetailContentTab';
3839
import { SettingsApplicationPermissionsTab } from '~/pages/settings/applications/tabs/SettingsApplicationPermissionsTab';
@@ -283,16 +284,15 @@ export const SettingsAvailableApplicationDetails = () => {
283284
},
284285
{ children: displayName },
285286
]}
286-
title={
287-
<SettingsApplicationDetailTitle
288-
displayName={displayName}
289-
description={description}
290-
applicationId={application?.id}
291-
isUnlisted={isUnlisted}
292-
/>
293-
}
287+
title={displayName}
294288
>
295289
<SettingsPageContainer>
290+
{isUnlisted && (
291+
<InlineBanner
292+
LeftIcon={IconEyeOff}
293+
message={t`Application not listed on the marketplace. It was shared via a direct link`}
294+
/>
295+
)}
296296
<TabList
297297
tabs={tabs}
298298
componentInstanceId={AVAILABLE_APPLICATION_DETAIL_ID}

0 commit comments

Comments
 (0)