Add List View lab icon and make lab feature flag icons mandatory (#23930)
## Context The List View toggle in Settings → Lab rendered without an icon: the front end kept a hardcoded `Partial<Record<FeatureFlagKey, IconComponent>>` map, so nothing caught a public feature flag added without an icon. ## What this PR does Moves the icon into the public feature flag metadata, next to `label` and `description`, as a **required** field — so a lab flag now registers in one place and can't be declared without an icon (server typecheck fails otherwise): - `twenty-server`: `FeatureFlagMetadata` gains required `icon: string`; each `PUBLIC_FEATURE_FLAGS` entry declares its icon (`IconList` for List View — the icon already used for the List view type). `PublicFeatureFlagMetadata` GraphQL entity exposes it. - `twenty-front`: `SettingsLabContent` renders `getIcon(flag.metadata.icon)` via `useIcons`, the same metadata-driven icon pattern used across the app; the hardcoded icon map is deleted. - `twenty-ui`: registers `IconCalendarWeek` in `AllIcons` — it was importable but not resolvable by name through `useIcons`, so it would have silently fallen back to the default icon. - Generated artifacts (`twenty-front/src/generated-metadata`, `twenty-client-sdk/src/metadata/generated`) updated for the new field. ## Verification - `nx run-many -t typecheck,lint -p twenty-front twenty-server twenty-shared twenty-client-sdk twenty-ui` passes (oxlint + oxfmt clean). - Negative case: removing `icon` from a `PUBLIC_FEATURE_FLAGS` entry fails `twenty-server:typecheck` with `TS2741: Property 'icon' is missing … but required in type 'FeatureFlagMetadata'`.
This commit is contained in:
@@ -1968,6 +1968,7 @@ type ApiConfig {
|
|||||||
type PublicFeatureFlagMetadata {
|
type PublicFeatureFlagMetadata {
|
||||||
label: String!
|
label: String!
|
||||||
description: String!
|
description: String!
|
||||||
|
icon: String!
|
||||||
imagePath: String
|
imagePath: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1600,6 +1600,7 @@ export interface ApiConfig {
|
|||||||
export interface PublicFeatureFlagMetadata {
|
export interface PublicFeatureFlagMetadata {
|
||||||
label: Scalars['String']
|
label: Scalars['String']
|
||||||
description: Scalars['String']
|
description: Scalars['String']
|
||||||
|
icon: Scalars['String']
|
||||||
imagePath?: Scalars['String']
|
imagePath?: Scalars['String']
|
||||||
__typename: 'PublicFeatureFlagMetadata'
|
__typename: 'PublicFeatureFlagMetadata'
|
||||||
}
|
}
|
||||||
@@ -4867,6 +4868,7 @@ export interface ApiConfigGenqlSelection{
|
|||||||
export interface PublicFeatureFlagMetadataGenqlSelection{
|
export interface PublicFeatureFlagMetadataGenqlSelection{
|
||||||
label?: boolean | number
|
label?: boolean | number
|
||||||
description?: boolean | number
|
description?: boolean | number
|
||||||
|
icon?: boolean | number
|
||||||
imagePath?: boolean | number
|
imagePath?: boolean | number
|
||||||
__typename?: boolean | number
|
__typename?: boolean | number
|
||||||
__scalar?: boolean | number
|
__scalar?: boolean | number
|
||||||
|
|||||||
@@ -3769,6 +3769,9 @@ export default {
|
|||||||
"description": [
|
"description": [
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
|
"icon": [
|
||||||
|
1
|
||||||
|
],
|
||||||
"imagePath": [
|
"imagePath": [
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4502,6 +4502,7 @@ export type PublicFeatureFlag = {
|
|||||||
export type PublicFeatureFlagMetadata = {
|
export type PublicFeatureFlagMetadata = {
|
||||||
__typename?: 'PublicFeatureFlagMetadata';
|
__typename?: 'PublicFeatureFlagMetadata';
|
||||||
description: Scalars['String']['output'];
|
description: Scalars['String']['output'];
|
||||||
|
icon: Scalars['String']['output'];
|
||||||
imagePath?: Maybe<Scalars['String']['output']>;
|
imagePath?: Maybe<Scalars['String']['output']>;
|
||||||
label: Scalars['String']['output'];
|
label: Scalars['String']['output'];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,15 +5,10 @@ import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomState
|
|||||||
import { styled } from '@linaria/react';
|
import { styled } from '@linaria/react';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import { useIcons } from 'twenty-ui/icon';
|
||||||
IconCalendarWeek,
|
|
||||||
IconRelationManyToMany,
|
|
||||||
IconSparkles,
|
|
||||||
type IconComponent,
|
|
||||||
} from 'twenty-ui/icon';
|
|
||||||
import { Card } from 'twenty-ui/surfaces';
|
import { Card } from 'twenty-ui/surfaces';
|
||||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
import { type FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
const StyledCardGrid = styled.div`
|
const StyledCardGrid = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -29,14 +24,9 @@ const StyledImage = styled.img`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const labFeatureFlagIcons: Partial<Record<FeatureFlagKey, IconComponent>> = {
|
|
||||||
[FeatureFlagKey.IS_CALENDAR_WEEK_VIEW_ENABLED]: IconCalendarWeek,
|
|
||||||
[FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED]: IconRelationManyToMany,
|
|
||||||
[FeatureFlagKey.IS_SETTINGS_DISCOVERY_HERO_ENABLED]: IconSparkles,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SettingsLabContent = () => {
|
export const SettingsLabContent = () => {
|
||||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||||
|
const { getIcon } = useIcons();
|
||||||
const { labPublicFeatureFlags, handleLabPublicFeatureFlagUpdate } =
|
const { labPublicFeatureFlags, handleLabPublicFeatureFlagUpdate } =
|
||||||
useLabPublicFeatureFlags();
|
useLabPublicFeatureFlags();
|
||||||
const [hasImageLoadingError, setHasImageLoadingError] = useState<
|
const [hasImageLoadingError, setHasImageLoadingError] = useState<
|
||||||
@@ -75,7 +65,7 @@ export const SettingsLabContent = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<SettingsOptionCardContentToggle
|
<SettingsOptionCardContentToggle
|
||||||
Icon={labFeatureFlagIcons[flag.key]}
|
Icon={getIcon(flag.metadata.icon)}
|
||||||
title={flag.metadata.label}
|
title={flag.metadata.label}
|
||||||
description={flag.metadata.description}
|
description={flag.metadata.description}
|
||||||
checked={flag.value}
|
checked={flag.value}
|
||||||
@@ -93,7 +83,7 @@ export const SettingsLabContent = () => {
|
|||||||
{labPublicFeatureFlagsWithoutImage.map((flag, index) => (
|
{labPublicFeatureFlagsWithoutImage.map((flag, index) => (
|
||||||
<SettingsOptionCardContentToggle
|
<SettingsOptionCardContentToggle
|
||||||
key={flag.key}
|
key={flag.key}
|
||||||
Icon={labFeatureFlagIcons[flag.key]}
|
Icon={getIcon(flag.metadata.icon)}
|
||||||
title={flag.metadata.label}
|
title={flag.metadata.label}
|
||||||
description={flag.metadata.description}
|
description={flag.metadata.description}
|
||||||
checked={flag.value}
|
checked={flag.value}
|
||||||
|
|||||||
@@ -226,6 +226,9 @@ export class PublicFeatureFlagMetadata {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
icon: string;
|
||||||
|
|
||||||
@Field(() => String, { nullable: true })
|
@Field(() => String, { nullable: true })
|
||||||
imagePath?: string;
|
imagePath?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -3,6 +3,7 @@ import { FeatureFlagKey } from 'twenty-shared/types';
|
|||||||
type FeatureFlagMetadata = {
|
type FeatureFlagMetadata = {
|
||||||
label: string;
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
icon: string;
|
||||||
imagePath?: string;
|
imagePath?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
|
|||||||
label: 'Calendar Day and Week Views',
|
label: 'Calendar Day and Week Views',
|
||||||
description:
|
description:
|
||||||
'Display calendar records in daily or weekly layouts with optional end dates',
|
'Display calendar records in daily or weekly layouts with optional end dates',
|
||||||
|
icon: 'IconCalendarWeek',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -26,6 +28,7 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
|
|||||||
label: 'Junction Relations',
|
label: 'Junction Relations',
|
||||||
description:
|
description:
|
||||||
'Enable many-to-many relations through junction tables configuration',
|
'Enable many-to-many relations through junction tables configuration',
|
||||||
|
icon: 'IconRelationManyToMany',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -34,6 +37,7 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
|
|||||||
label: 'List View',
|
label: 'List View',
|
||||||
description:
|
description:
|
||||||
'Display records in a list layout with collapsible groups and inline fields',
|
'Display records in a list layout with collapsible groups and inline fields',
|
||||||
|
icon: 'IconList',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -42,6 +46,7 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
|
|||||||
label: 'Settings Discovery Hero',
|
label: 'Settings Discovery Hero',
|
||||||
description:
|
description:
|
||||||
'Show the per-page hero illustration + video walkthrough modal on settings pages',
|
'Show the per-page hero illustration + video walkthrough modal on settings pages',
|
||||||
|
icon: 'IconSparkles',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...(process.env.CLOUDFLARE_API_KEY
|
...(process.env.CLOUDFLARE_API_KEY
|
||||||
|
|||||||
@@ -965,6 +965,7 @@ import {
|
|||||||
IconCalendarStats,
|
IconCalendarStats,
|
||||||
IconCalendarTime,
|
IconCalendarTime,
|
||||||
IconCalendarUp,
|
IconCalendarUp,
|
||||||
|
IconCalendarWeek,
|
||||||
IconCalendarX,
|
IconCalendarX,
|
||||||
IconCamera,
|
IconCamera,
|
||||||
IconCameraBolt,
|
IconCameraBolt,
|
||||||
@@ -5149,6 +5150,7 @@ export const ALL_ICONS = {
|
|||||||
IconCalendarStats,
|
IconCalendarStats,
|
||||||
IconCalendarTime,
|
IconCalendarTime,
|
||||||
IconCalendarUp,
|
IconCalendarUp,
|
||||||
|
IconCalendarWeek,
|
||||||
IconCalendarX,
|
IconCalendarX,
|
||||||
IconCamera,
|
IconCamera,
|
||||||
IconCameraBolt,
|
IconCameraBolt,
|
||||||
|
|||||||
Reference in New Issue
Block a user