diff --git a/packages/twenty-front/src/modules/applications/components/AppChip.tsx b/packages/twenty-front/src/modules/applications/components/AppChip.tsx index fbaeb1cfee..6c4bbaaa6b 100644 --- a/packages/twenty-front/src/modules/applications/components/AppChip.tsx +++ b/packages/twenty-front/src/modules/applications/components/AppChip.tsx @@ -14,7 +14,7 @@ const StyledContainer = styled.div` display: inline-flex; font-size: ${themeCssVariables.font.size.sm}; font-weight: ${themeCssVariables.font.weight.regular}; - gap: ${themeCssVariables.spacing[2]}; + gap: ${themeCssVariables.spacing[1]}; max-width: 100%; min-width: 0; overflow: hidden; @@ -33,7 +33,7 @@ export const AppChip = ({ applicationId, className }: AppChipProps) => { { @@ -39,24 +28,19 @@ export const useApplicationAvatarColors = ( return undefined; } - const isStandard = - isDefined(application.universalIdentifier) && - application.universalIdentifier === - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER; - - const isCustom = - isDefined(currentWorkspace?.workspaceCustomApplication?.id) && - currentWorkspace.workspaceCustomApplication.id === application.id; - - if (isStandard) { - return STANDARD_APPLICATION_AVATAR_COLORS; + if (isTwentyStandardApplication(application)) { + return { + backgroundColor: theme.color.blue3, + borderColor: theme.color.blue4, + color: theme.color.blue9, + }; } - if (isCustom) { + if (isWorkspaceCustomApplication(application, currentWorkspace)) { return { - backgroundColor: theme.color.orange5, - borderColor: theme.color.orange6, - color: theme.color.orange12, + backgroundColor: theme.color.orange3, + borderColor: theme.color.orange4, + color: theme.color.orange9, }; } diff --git a/packages/twenty-front/src/modules/applications/hooks/useApplicationChipData.ts b/packages/twenty-front/src/modules/applications/hooks/useApplicationChipData.ts index 0bb1b8f1cd..43ff32730c 100644 --- a/packages/twenty-front/src/modules/applications/hooks/useApplicationChipData.ts +++ b/packages/twenty-front/src/modules/applications/hooks/useApplicationChipData.ts @@ -3,11 +3,12 @@ import { useApplicationAvatarColors, type ApplicationAvatarColors, } from '@/applications/hooks/useApplicationAvatarColors'; +import { isTwentyStandardApplication } from '@/applications/utils/isTwentyStandardApplication'; +import { isWorkspaceCustomApplication } from '@/applications/utils/isWorkspaceCustomApplication'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { t } from '@lingui/core/macro'; import { useContext } from 'react'; -import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application'; import { isDefined } from 'twenty-shared/utils'; type UseApplicationChipDataArgs = { @@ -48,20 +49,11 @@ export const useApplicationChipData = ({ const isCurrent = isDefined(currentApplicationId) && currentApplicationId === applicationId; - const isStandard = - isDefined(application.universalIdentifier) && - application.universalIdentifier === - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER; - - const isCustom = - isDefined(currentWorkspace?.workspaceCustomApplication?.id) && - currentWorkspace.workspaceCustomApplication.id === application.id; - const displayName = isCurrent ? t`This app` - : isStandard + : isTwentyStandardApplication(application) ? t`Standard` - : isCustom + : isWorkspaceCustomApplication(application, currentWorkspace) ? t`Custom` : application.name; diff --git a/packages/twenty-front/src/modules/applications/hooks/useResolvedApplicationDescription.ts b/packages/twenty-front/src/modules/applications/hooks/useResolvedApplicationDescription.ts new file mode 100644 index 0000000000..3c454e2a04 --- /dev/null +++ b/packages/twenty-front/src/modules/applications/hooks/useResolvedApplicationDescription.ts @@ -0,0 +1,28 @@ +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { isTwentyStandardApplication } from '@/applications/utils/isTwentyStandardApplication'; +import { isWorkspaceCustomApplication } from '@/applications/utils/isWorkspaceCustomApplication'; +import { getCustomApplicationDescription } from '~/pages/settings/applications/utils/getCustomApplicationDescription'; +import { getStandardApplicationDescription } from '~/pages/settings/applications/utils/getStandardApplicationDescription'; + +type ApplicationLike = { + id?: string | null; + universalIdentifier?: string | null; + description?: string | null; +}; + +export const useResolvedApplicationDescription = ( + application: ApplicationLike | null | undefined, +): string => { + const currentWorkspace = useAtomStateValue(currentWorkspaceState); + + if (isTwentyStandardApplication(application)) { + return getStandardApplicationDescription(); + } + + if (isWorkspaceCustomApplication(application, currentWorkspace)) { + return getCustomApplicationDescription(); + } + + return application?.description ?? ''; +}; diff --git a/packages/twenty-front/src/modules/applications/utils/isTwentyStandardApplication.ts b/packages/twenty-front/src/modules/applications/utils/isTwentyStandardApplication.ts new file mode 100644 index 0000000000..72c4151722 --- /dev/null +++ b/packages/twenty-front/src/modules/applications/utils/isTwentyStandardApplication.ts @@ -0,0 +1,13 @@ +import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application'; +import { isDefined } from 'twenty-shared/utils'; + +type ApplicationLike = { + universalIdentifier?: string | null; +}; + +export const isTwentyStandardApplication = ( + application: ApplicationLike | null | undefined, +): boolean => + isDefined(application?.universalIdentifier) && + application.universalIdentifier === + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER; diff --git a/packages/twenty-front/src/modules/applications/utils/isWorkspaceCustomApplication.ts b/packages/twenty-front/src/modules/applications/utils/isWorkspaceCustomApplication.ts new file mode 100644 index 0000000000..93c6123aa1 --- /dev/null +++ b/packages/twenty-front/src/modules/applications/utils/isWorkspaceCustomApplication.ts @@ -0,0 +1,17 @@ +import { isDefined } from 'twenty-shared/utils'; + +type ApplicationLike = { + id?: string | null; +}; + +type WorkspaceLike = { + workspaceCustomApplication?: { id?: string | null } | null; +}; + +export const isWorkspaceCustomApplication = ( + application: ApplicationLike | null | undefined, + currentWorkspace: WorkspaceLike | null | undefined, +): boolean => + isDefined(application?.id) && + isDefined(currentWorkspace?.workspaceCustomApplication?.id) && + currentWorkspace.workspaceCustomApplication.id === application.id; diff --git a/packages/twenty-front/src/modules/settings/applications/components/SettingsApplicationScreenshotGallery.tsx b/packages/twenty-front/src/modules/settings/applications/components/SettingsApplicationScreenshotGallery.tsx index c63f21f07f..3362004175 100644 --- a/packages/twenty-front/src/modules/settings/applications/components/SettingsApplicationScreenshotGallery.tsx +++ b/packages/twenty-front/src/modules/settings/applications/components/SettingsApplicationScreenshotGallery.tsx @@ -7,28 +7,38 @@ type SettingsApplicationScreenshotGalleryProps = { displayName: string; }; +const StyledGalleryContainer = styled.div` + display: flex; + flex-direction: column; + gap: ${themeCssVariables.spacing[2]}; + margin-bottom: ${themeCssVariables.spacing[6]}; + min-width: 0; + width: 100%; +`; + const StyledScreenshotsContainer = styled.div` align-items: center; + aspect-ratio: 8 / 5; background-color: ${themeCssVariables.background.secondary}; border: 1px solid ${themeCssVariables.border.color.medium}; border-radius: ${themeCssVariables.border.radius.md}; display: flex; - height: 300px; justify-content: center; - margin-bottom: ${themeCssVariables.spacing[2]}; overflow: hidden; `; const StyledScreenshotImage = styled.img` height: 100%; - object-fit: contain; + object-fit: cover; + object-position: center; width: 100%; `; const StyledScreenshotThumbnails = styled.div` display: flex; gap: ${themeCssVariables.spacing[2]}; - margin-bottom: ${themeCssVariables.spacing[6]}; + min-width: 0; + overflow-x: auto; `; const StyledThumbnail = styled.div<{ isSelected?: boolean }>` @@ -42,8 +52,8 @@ const StyledThumbnail = styled.div<{ isSelected?: boolean }>` border-radius: ${themeCssVariables.border.radius.sm}; cursor: pointer; display: flex; - flex: 1; - height: 60px; + flex: 0 0 96px; + height: 56px; justify-content: center; overflow: hidden; @@ -54,7 +64,8 @@ const StyledThumbnail = styled.div<{ isSelected?: boolean }>` const StyledThumbnailImage = styled.img` height: 100%; - object-fit: contain; + object-fit: cover; + object-position: center; width: 100%; `; @@ -71,7 +82,7 @@ export const SettingsApplicationScreenshotGallery = ({ const safeIndex = Math.min(selectedScreenshotIndex, screenshots.length - 1); return ( - <> + ))} - + ); }; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx b/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx index 5ed2dd0502..786076399d 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx @@ -28,7 +28,7 @@ export const SettingsItemTypeTag = ({ leftComponent={ diff --git a/packages/twenty-front/src/modules/settings/data-model/assets/cover-dark.png b/packages/twenty-front/src/modules/settings/data-model/assets/cover-dark.png index e463476627..317aa0ba68 100644 Binary files a/packages/twenty-front/src/modules/settings/data-model/assets/cover-dark.png and b/packages/twenty-front/src/modules/settings/data-model/assets/cover-dark.png differ diff --git a/packages/twenty-front/src/modules/settings/data-model/assets/cover-light.png b/packages/twenty-front/src/modules/settings/data-model/assets/cover-light.png index fad6f00fea..4cabceeda2 100644 Binary files a/packages/twenty-front/src/modules/settings/data-model/assets/cover-light.png and b/packages/twenty-front/src/modules/settings/data-model/assets/cover-light.png differ diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents.tsx index 56cb0acecf..095385c930 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents.tsx @@ -5,7 +5,10 @@ import React from 'react'; export const SETTINGS_OBJECT_TABLE_COLUMN_WIDTH = '98.7px'; -export const SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS = `180px ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} 36px`; +const SETTINGS_OBJECT_TABLE_APP_COLUMN_WIDTH = '140px'; +const SETTINGS_OBJECT_TABLE_FIELDS_COLUMN_WIDTH = '72px'; + +export const SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS = `180px ${SETTINGS_OBJECT_TABLE_APP_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_FIELDS_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} 36px`; export const SETTINGS_OBJECT_TABLE_ROW_MOBILE_MIN_WIDTH = '520px'; diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsObjectCoverImage.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsObjectCoverImage.tsx index 035b7b431a..ccdfded184 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsObjectCoverImage.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsObjectCoverImage.tsx @@ -12,43 +12,60 @@ import LightCoverImage from '@/settings/data-model/assets/cover-light.png'; import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; const StyledCoverImageContainer = styled.div` - align-items: center; - background-size: cover; border: 1px solid ${themeCssVariables.border.color.medium}; border-radius: ${themeCssVariables.border.radius.md}; box-sizing: border-box; - display: flex; - justify-content: center; margin-bottom: ${themeCssVariables.spacing[8]}; min-height: 153px; overflow: hidden; position: relative; `; -const StyledButtonContainer = styled.div` - padding-top: ${themeCssVariables.spacing[5]}; +const StyledCoverImage = styled.img` + display: block; + height: 100%; + inset: 0; + object-fit: cover; + object-position: center; + position: absolute; + width: 100%; +`; + +const StyledButtonOverlay = styled.div` + align-items: center; + display: flex; + inset: 0; + justify-content: center; + pointer-events: none; + position: absolute; + + & > * { + pointer-events: auto; + } `; export const SettingsObjectCoverImage = () => { const { colorScheme } = useContext(ThemeContext); const { t } = useLingui(); return ( - + - + ? LightCoverImage.toString() + : DarkCoverImage.toString() + } + alt="" + aria-hidden + /> + - + ); }; diff --git a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx index 07c34db4f1..fb7d56aadc 100644 --- a/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx +++ b/packages/twenty-front/src/pages/settings/applications/SettingsApplicationDetails.tsx @@ -1,4 +1,8 @@ import { CurrentApplicationContext } from '@/applications/contexts/CurrentApplicationContext'; +import { useResolvedApplicationDescription } from '@/applications/hooks/useResolvedApplicationDescription'; +import { isTwentyStandardApplication } from '@/applications/utils/isTwentyStandardApplication'; +import { isWorkspaceCustomApplication } from '@/applications/utils/isWorkspaceCustomApplication'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useUpgradeApplication } from '@/marketplace/hooks/useUpgradeApplication'; import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; @@ -38,6 +42,8 @@ import { import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { SettingsApplicationDetailSkeletonLoader } from '~/pages/settings/applications/components/SettingsApplicationDetailSkeletonLoader'; 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'; import { SettingsApplicationCustomTab } from '~/pages/settings/applications/tabs/SettingsApplicationCustomTab'; import { SettingsApplicationDetailAboutTab } from '~/pages/settings/applications/tabs/SettingsApplicationDetailAboutTab'; import { SettingsApplicationDetailContentTab } from '~/pages/settings/applications/tabs/SettingsApplicationDetailContentTab'; @@ -70,13 +76,30 @@ export const SettingsApplicationDetails = () => { const detail = detailData?.findMarketplaceAppDetail; const manifest = detail?.manifest as Manifest | undefined; const app = manifest?.application; + const currentWorkspace = useAtomStateValue(currentWorkspaceState); + const isStandardApplication = isTwentyStandardApplication(application); + const isCustomApplication = isWorkspaceCustomApplication( + application, + currentWorkspace, + ); + + const resolvedDescription = useResolvedApplicationDescription(application); const displayName = app?.displayName ?? application?.name ?? t`Application details`; - const description = app?.description ?? application?.description ?? undefined; + const description = app?.description ?? resolvedDescription; const logoUrl = app?.logoUrl ?? application?.applicationRegistration?.logoUrl ?? undefined; + const getScreenshots = () => { + if (app?.screenshots?.length) return app.screenshots; + if (isStandardApplication) return STANDARD_APPLICATION_ILLUSTRATIONS; + if (isCustomApplication) return CUSTOM_APPLICATION_ILLUSTRATIONS; + return undefined; + }; + + const screenshots = getScreenshots(); + const settingsCustomTabFrontComponentId = application?.settingsCustomTabFrontComponentId; @@ -228,7 +251,7 @@ export const SettingsApplicationDetails = () => { displayName={displayName} description={description} aboutDescription={app?.aboutDescription} - screenshots={app?.screenshots} + screenshots={screenshots} author={app?.author} category={app?.category} contentEntries={contentEntries} diff --git a/packages/twenty-front/src/pages/settings/applications/assets/custom-illustrations/custom-fields-and-objects.webp b/packages/twenty-front/src/pages/settings/applications/assets/custom-illustrations/custom-fields-and-objects.webp new file mode 100644 index 0000000000..2664e180c4 Binary files /dev/null and b/packages/twenty-front/src/pages/settings/applications/assets/custom-illustrations/custom-fields-and-objects.webp differ diff --git a/packages/twenty-front/src/pages/settings/applications/assets/custom-illustrations/custom-layouts.webp b/packages/twenty-front/src/pages/settings/applications/assets/custom-illustrations/custom-layouts.webp new file mode 100644 index 0000000000..95ac5126db Binary files /dev/null and b/packages/twenty-front/src/pages/settings/applications/assets/custom-illustrations/custom-layouts.webp differ diff --git a/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/dashboards.webp b/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/dashboards.webp new file mode 100644 index 0000000000..ece01c4da1 Binary files /dev/null and b/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/dashboards.webp differ diff --git a/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/kanbans.webp b/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/kanbans.webp new file mode 100644 index 0000000000..486b82f326 Binary files /dev/null and b/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/kanbans.webp differ diff --git a/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/people-and-companies.webp b/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/people-and-companies.webp new file mode 100644 index 0000000000..84ce5f61cb Binary files /dev/null and b/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/people-and-companies.webp differ diff --git a/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/workflows.webp b/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/workflows.webp new file mode 100644 index 0000000000..58404eadcd Binary files /dev/null and b/packages/twenty-front/src/pages/settings/applications/assets/standard-illustrations/workflows.webp differ diff --git a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationDetailTitle.tsx b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationDetailTitle.tsx index 8d249cff3c..9d707acf84 100644 --- a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationDetailTitle.tsx +++ b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationDetailTitle.tsx @@ -4,6 +4,7 @@ import { styled } from '@linaria/react'; import { t } from '@lingui/core/macro'; import { Avatar, IconEyeOff } from 'twenty-ui/display'; import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { getApplicationDescriptionSummary } from '~/pages/settings/applications/utils/getApplicationDescriptionSummary'; type SettingsApplicationDetailTitleProps = { displayName: string; @@ -37,7 +38,7 @@ const StyledHeaderLeft = styled.div` const StyledHeaderTop = styled.div` align-items: center; display: flex; - gap: ${themeCssVariables.spacing[2]}; + gap: ${themeCssVariables.spacing[1]}; `; const StyledAppName = styled.div` @@ -82,6 +83,7 @@ export const SettingsApplicationDetailTitle = ({ name: applicationName, universalIdentifier, }); + const descriptionSummary = getApplicationDescriptionSummary(description); return ( @@ -106,8 +108,8 @@ export const SettingsApplicationDetailTitle = ({ /> {displayName} - {description && ( - {description} + {descriptionSummary && ( + {descriptionSummary} )} diff --git a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationTableRow.tsx b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationTableRow.tsx index 17a61420e5..8fff451b06 100644 --- a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationTableRow.tsx +++ b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationTableRow.tsx @@ -1,6 +1,7 @@ import { type ReactNode } from 'react'; import { useApplicationAvatarColors } from '@/applications/hooks/useApplicationAvatarColors'; +import { useResolvedApplicationDescription } from '@/applications/hooks/useResolvedApplicationDescription'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import { t } from '@lingui/core/macro'; @@ -8,6 +9,7 @@ import { Tag } from 'twenty-ui/components'; import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui/display'; import { themeCssVariables } from 'twenty-ui/theme-constants'; import { type ApplicationWithoutRelation } from '~/pages/settings/applications/types/applicationWithoutRelation'; +import { getApplicationDescriptionSummary } from '~/pages/settings/applications/utils/getApplicationDescriptionSummary'; export type SettingsApplicationTableRowProps = { action: ReactNode; @@ -26,6 +28,9 @@ export const SettingsApplicationTableRow = ({ link, }: SettingsApplicationTableRowProps) => { const colors = useApplicationAvatarColors(application); + const resolvedDescription = useResolvedApplicationDescription(application); + const descriptionSummary = + getApplicationDescriptionSummary(resolvedDescription); return ( - + {hasUpdate === true && ( )} diff --git a/packages/twenty-front/src/pages/settings/applications/components/SettingsAvailableApplicationCard.tsx b/packages/twenty-front/src/pages/settings/applications/components/SettingsAvailableApplicationCard.tsx index 767deda550..53cf44bcaa 100644 --- a/packages/twenty-front/src/pages/settings/applications/components/SettingsAvailableApplicationCard.tsx +++ b/packages/twenty-front/src/pages/settings/applications/components/SettingsAvailableApplicationCard.tsx @@ -12,6 +12,7 @@ import { Avatar } from 'twenty-ui/display'; import { Card } from 'twenty-ui/layout'; import { themeCssVariables } from 'twenty-ui/theme-constants'; import { type MarketplaceApp } from '~/generated-metadata/graphql'; +import { getApplicationDescriptionSummary } from '~/pages/settings/applications/utils/getApplicationDescriptionSummary'; type SettingsAvailableApplicationCardProps = { application: MarketplaceApp; @@ -45,6 +46,10 @@ const StyledDescription = styled.div` export const SettingsAvailableApplicationCard = ({ application, }: SettingsAvailableApplicationCardProps) => { + const descriptionSummary = getApplicationDescriptionSummary( + application.description, + ); + return ( {application.name} - {application.description} + {descriptionSummary} {t`by {author}`} {application.author} diff --git a/packages/twenty-front/src/pages/settings/applications/constants/CustomApplicationIllustrations.ts b/packages/twenty-front/src/pages/settings/applications/constants/CustomApplicationIllustrations.ts new file mode 100644 index 0000000000..24740398ac --- /dev/null +++ b/packages/twenty-front/src/pages/settings/applications/constants/CustomApplicationIllustrations.ts @@ -0,0 +1,7 @@ +import CustomFieldsAndObjectsIllustration from '~/pages/settings/applications/assets/custom-illustrations/custom-fields-and-objects.webp'; +import CustomLayoutsIllustration from '~/pages/settings/applications/assets/custom-illustrations/custom-layouts.webp'; + +export const CUSTOM_APPLICATION_ILLUSTRATIONS = [ + CustomFieldsAndObjectsIllustration, + CustomLayoutsIllustration, +]; diff --git a/packages/twenty-front/src/pages/settings/applications/constants/StandardApplicationIllustrations.ts b/packages/twenty-front/src/pages/settings/applications/constants/StandardApplicationIllustrations.ts new file mode 100644 index 0000000000..50c9f6620b --- /dev/null +++ b/packages/twenty-front/src/pages/settings/applications/constants/StandardApplicationIllustrations.ts @@ -0,0 +1,11 @@ +import DashboardsIllustration from '~/pages/settings/applications/assets/standard-illustrations/dashboards.webp'; +import KanbansIllustration from '~/pages/settings/applications/assets/standard-illustrations/kanbans.webp'; +import PeopleAndCompaniesIllustration from '~/pages/settings/applications/assets/standard-illustrations/people-and-companies.webp'; +import WorkflowsIllustration from '~/pages/settings/applications/assets/standard-illustrations/workflows.webp'; + +export const STANDARD_APPLICATION_ILLUSTRATIONS = [ + WorkflowsIllustration, + KanbansIllustration, + DashboardsIllustration, + PeopleAndCompaniesIllustration, +]; diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailAboutTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailAboutTab.tsx index 346799ccb4..96d508d493 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailAboutTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailAboutTab.tsx @@ -57,6 +57,48 @@ const StyledMainContent = styled.div` overflow: hidden; `; +const StyledMarkdownContent = styled.div` + .markdown-section { + margin: 0; + } + + .markdown-section h4 { + font-size: ${themeCssVariables.font.size.lg}; + font-weight: ${themeCssVariables.font.weight.semiBold}; + line-height: 1.35; + margin-bottom: ${themeCssVariables.spacing[2]}; + margin-top: ${themeCssVariables.spacing[5]}; + } + + .markdown-section ul { + margin-bottom: ${themeCssVariables.spacing[3]}; + margin-top: ${themeCssVariables.spacing[2]}; + padding-left: ${themeCssVariables.spacing[4]}; + } + + .markdown-section li { + margin-bottom: ${themeCssVariables.spacing[1]} !important; + padding-bottom: 0 !important; + padding-top: 0 !important; + } + + .markdown-section .markdown-code-outer-container { + margin: ${themeCssVariables.spacing[3]} 0 ${themeCssVariables.spacing[4]}; + } + + .markdown-section .markdown-block-code { + padding: ${themeCssVariables.spacing[3]} ${themeCssVariables.spacing[4]}; + } + + .markdown-section .markdown-block-code code { + color: ${themeCssVariables.font.color.primary}; + display: block; + font-family: ${themeCssVariables.code.font.family}, monospace; + font-size: ${themeCssVariables.font.size.sm}; + line-height: 1.6; + } +`; + export const SettingsApplicationDetailAboutTab = ({ displayName, description, @@ -161,7 +203,9 @@ export const SettingsApplicationDetailAboutTab = ({
- + + +
diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsDeveloperTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsDeveloperTab.tsx index c88f41ec11..e862a896af 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsDeveloperTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsDeveloperTab.tsx @@ -15,6 +15,7 @@ import { useQuery } from '@apollo/client/react'; import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; import { useContext, useMemo, useState } from 'react'; +import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { @@ -29,7 +30,6 @@ import { } from 'twenty-ui/display'; import { Button, SearchInput } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; -import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { type ApplicationRegistrationFragmentFragment, @@ -42,6 +42,7 @@ import { APPLICATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS, SettingsApplicationTableRow, } from '~/pages/settings/applications/components/SettingsApplicationTableRow'; +import { getApplicationDescriptionSummary } from '~/pages/settings/applications/utils/getApplicationDescriptionSummary'; const StyledButtonContainer = styled.div` display: flex; @@ -276,7 +277,9 @@ export const SettingsApplicationsDeveloperTab = () => { whiteSpace="nowrap" >
diff --git a/packages/twenty-front/src/pages/settings/applications/utils/getApplicationDescriptionSummary.ts b/packages/twenty-front/src/pages/settings/applications/utils/getApplicationDescriptionSummary.ts new file mode 100644 index 0000000000..64d93d202f --- /dev/null +++ b/packages/twenty-front/src/pages/settings/applications/utils/getApplicationDescriptionSummary.ts @@ -0,0 +1,28 @@ +const stripMarkdown = (value: string) => + value + .replace(/```[\s\S]*?```/g, ' ') + .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1') + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') + .replace(/^>\s?/gm, '') + .replace(/^\s{0,3}(?:[-*+]\s+|\d+\.\s+)/gm, '') + .replace(/[*_~`#]/g, '') + .replace(/\s+/g, ' ') + .trim(); + +export const getApplicationDescriptionSummary = ( + description?: string | null, +): string => { + if (!description) { + return ''; + } + + for (const block of description.split(/\n\s*\n/)) { + const summary = stripMarkdown(block); + + if (summary.length > 0) { + return summary; + } + } + + return ''; +}; diff --git a/packages/twenty-front/src/pages/settings/applications/utils/getCustomApplicationDescription.ts b/packages/twenty-front/src/pages/settings/applications/utils/getCustomApplicationDescription.ts new file mode 100644 index 0000000000..fc04656e53 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/applications/utils/getCustomApplicationDescription.ts @@ -0,0 +1,26 @@ +import { t } from '@lingui/core/macro'; + +export const getCustomApplicationDescription = + (): string => t`Host your workspace's customizations and overrides. + +#### What it includes +Every extension you create on top of the standard app is grouped under Custom. It keeps your schema changes, interface changes, and workspace-specific logic in one place. + +- Custom objects and fields for your own data model +- Views, navigation items, and record layouts that shape how your team works +- Logic functions, front components, and agents that automate or extend the workspace + +#### Why it exists +Use this app for workspace-specific customization that should stay local to this workspace. + +If you are shaping one workspace for one business, keep it here. If you are building a reusable business app with its own data model, UI, and automation that should be versioned, shared, or installed across workspaces, create a dedicated app instead. + +#### Build your own app + +Scaffold a new app in one command: + +\`\`\`bash +npx create-twenty-app@latest my-twenty-app +\`\`\` + +See the [Getting Started guide](https://twenty.com/developers/extend/apps/getting-started) for the full walkthrough, and [Building Apps](https://twenty.com/developers/extend/apps/building) for the \`defineApplication\` / \`defineEntity\` APIs.`; diff --git a/packages/twenty-front/src/pages/settings/applications/utils/getStandardApplicationDescription.ts b/packages/twenty-front/src/pages/settings/applications/utils/getStandardApplicationDescription.ts new file mode 100644 index 0000000000..1c540a0ae4 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/applications/utils/getStandardApplicationDescription.ts @@ -0,0 +1,33 @@ +import { t } from '@lingui/core/macro'; + +export const getStandardApplicationDescription = + (): string => t`The base data model every Twenty workspace runs on. + +#### What "foundation" means + +Every Twenty workspace starts with this set of objects. They define the shape of your CRM, including relationships, activity, and reporting. Everything else, including marketplace apps, AI agents, and custom objects, plugs into them. + +#### Included objects +- **People & Companies**: contact and account records +- **Opportunities**: your sales pipeline +- **Notes & Tasks**: activity and follow-ups +- **Workflows & Dashboards**: automation and reporting + +Remove this app and the rest of Twenty has nothing to hang off. + +#### Build your own app + +Extend Twenty with your own objects, fields, logic functions, or AI skills. Scaffold a new app in one command: + +\`\`\`bash +npx create-twenty-app@latest my-twenty-app +\`\`\` + +Then inside the folder: + +\`\`\`bash +cd my-twenty-app +yarn twenty dev +\`\`\` + +See the [Getting Started guide](https://twenty.com/developers/extend/apps/getting-started) for the full walkthrough, and [Building Apps](https://twenty.com/developers/extend/apps/building) for the \`defineApplication\` / \`defineEntity\` APIs.`; diff --git a/packages/twenty-server/src/engine/core-modules/application/application.service.ts b/packages/twenty-server/src/engine/core-modules/application/application.service.ts index 9101e72c1e..6e33557ee0 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application.service.ts @@ -354,7 +354,7 @@ export class ApplicationService { const workspaceCustomApplication = await this.create( { - description: 'Workspace custom application', + description: null, name: 'Custom', sourcePath: 'workspace-custom', version: '1.0.1', diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications.ts index 36ea536d48..027eeb72f6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications.ts @@ -6,11 +6,11 @@ import { import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum'; import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; +// description is owned by the frontend (translated) — see getStandardApplicationDescription. export const TWENTY_STANDARD_APPLICATION = { universalIdentifier: TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, name: TWENTY_STANDARD_APPLICATION_NAME, - description: - 'Twenty is an open-source CRM that allows you to manage your sales and customer relationships', + description: null, version: '1.0.1', sourcePath: 'cli-sync', sourceType: ApplicationRegistrationSourceType.LOCAL,