Fix application icons (#20142)

fixes application chip (icon Name) in all setting tables

## After
<img width="1200" height="896" alt="image"
src="https://github.com/user-attachments/assets/bd377f47-1d52-4142-b904-f2ce90c1db78"
/>
<img width="1200" height="917" alt="image"
src="https://github.com/user-attachments/assets/f49cc742-f11e-47e3-86ed-34beffe493c7"
/>
<img width="1234" height="878" alt="image"
src="https://github.com/user-attachments/assets/2ab459de-5f9d-4d39-9490-eec4ed9ee432"
/>
<img width="1239" height="845" alt="image"
src="https://github.com/user-attachments/assets/3c1bf258-285a-47b9-a60d-05ba1564334d"
/>
<img width="1183" height="907" alt="image"
src="https://github.com/user-attachments/assets/715b2470-2d88-48e3-88ac-d3daf3451717"
/>
<img width="1300" height="912" alt="image"
src="https://github.com/user-attachments/assets/d7c829fa-bf1d-4f19-82de-a8bf29e22bfa"
/>
This commit is contained in:
martmull
2026-04-30 11:32:04 +02:00
committed by GitHub
parent 842e679cc6
commit bddd23fd9c
59 changed files with 772 additions and 205 deletions
@@ -1,10 +1,19 @@
import { useApplicationChipData } from '@/applications/hooks/useApplicationChipData';
import { styled } from '@linaria/react';
import { Avatar } from 'twenty-ui/display';
import {
Avatar,
type AvatarSize,
OverflowingTextWithTooltip,
} from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type AppChipProps = {
applicationId: string;
size?: AvatarSize;
applicationId?: string | null;
fallbackApplicationData?: {
logo?: string | null;
name?: string | null;
};
className?: string;
};
@@ -20,29 +29,30 @@ const StyledContainer = styled.div`
overflow: hidden;
`;
const StyledLabel = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export const AppChip = ({ applicationId, className }: AppChipProps) => {
const { applicationChipData } = useApplicationChipData({ applicationId });
export const AppChip = ({
applicationId,
size = 'sm',
fallbackApplicationData,
className,
}: AppChipProps) => {
const { applicationChipData } = useApplicationChipData({
applicationId,
fallbackApplicationData,
});
return (
<StyledContainer className={className}>
<Avatar
type="app"
size="sm"
size={size}
avatarUrl={applicationChipData.logo}
placeholder={applicationChipData.name}
placeholderColorSeed={applicationChipData.seed}
color={applicationChipData.colors?.color}
backgroundColor={applicationChipData.colors?.backgroundColor}
borderColor={applicationChipData.colors?.borderColor}
/>
<StyledLabel title={applicationChipData.name}>
{applicationChipData.name}
</StyledLabel>
<OverflowingTextWithTooltip text={applicationChipData.name} />
</StyledContainer>
);
};
@@ -1,41 +1,29 @@
import { useApplicationAvatarColors } from '@/applications/hooks/useApplicationAvatarColors';
import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui/display';
type ApplicationDisplayData = {
id?: string | null;
name?: string | null;
universalIdentifier?: string | null;
logoUrl?: string | null;
applicationRegistration?: {
logoUrl?: string | null;
} | null;
};
import { type ApplicationDisplayData } from '@/applications/types/applicationDisplayData.type';
import { AppChip } from '@/applications/components/AppChip';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { styled } from '@linaria/react';
type ApplicationDisplayProps = {
application: ApplicationDisplayData;
application?: ApplicationDisplayData;
};
const StyledAppChip = styled(AppChip)`
color: ${themeCssVariables.font.color.primary};
font-size: ${themeCssVariables.font.size.md};
gap: ${themeCssVariables.spacing[2]};
`;
export const ApplicationDisplay = ({
application,
}: ApplicationDisplayProps) => {
const colors = useApplicationAvatarColors(application);
const name = application.name ?? '';
const logoUrl =
application.logoUrl ?? application.applicationRegistration?.logoUrl;
return (
<>
<Avatar
type="app"
size="md"
avatarUrl={logoUrl ?? undefined}
placeholder={name}
placeholderColorSeed={application.universalIdentifier ?? name}
color={colors?.color}
backgroundColor={colors?.backgroundColor}
borderColor={colors?.borderColor}
/>
<OverflowingTextWithTooltip text={name} />
</>
<StyledAppChip
size="md"
applicationId={application?.id}
fallbackApplicationData={{
logo: application?.logo,
name: application?.name,
}}
/>
);
};
@@ -11,6 +11,7 @@ export const APPLICATION_FRAGMENT = gql`
id
name
description
logo
version
universalIdentifier
applicationRegistrationId
@@ -6,6 +6,7 @@ export const FIND_MANY_APPLICATIONS = gql`
id
name
description
logo
version
universalIdentifier
applicationRegistrationId
@@ -1,24 +1,30 @@
import { CurrentApplicationContext } from '@/applications/contexts/CurrentApplicationContext';
import {
useApplicationAvatarColors,
type ApplicationAvatarColors,
useApplicationAvatarColors,
} 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 { isDefined } from 'twenty-shared/utils';
import { buildApplicationLogoUrl } from '@/applications/utils/buildApplicationLogoUrl';
import CustomLogo from '~/pages/settings/applications/assets/custom-illustrations/custom-logo.webp';
import StandardLogo from '~/pages/settings/applications/assets/standard-illustrations/standard-logo.webp';
type UseApplicationChipDataArgs = {
applicationId: string;
applicationId?: string | null;
fallbackApplicationData?: {
logo?: string | null;
name?: string | null;
};
};
type ApplicationChipData = {
name: string;
seed: string;
colors?: ApplicationAvatarColors;
logo?: string;
};
type UseApplicationChipDataReturnType = {
@@ -27,8 +33,8 @@ type UseApplicationChipDataReturnType = {
export const useApplicationChipData = ({
applicationId,
fallbackApplicationData,
}: UseApplicationChipDataArgs): UseApplicationChipDataReturnType => {
const currentApplicationId = useContext(CurrentApplicationContext);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const application = currentWorkspace?.installedApplications.find(
@@ -40,28 +46,39 @@ export const useApplicationChipData = ({
if (!isDefined(application)) {
return {
applicationChipData: {
name: '',
seed: applicationId,
name: fallbackApplicationData?.name ?? '',
logo: fallbackApplicationData?.logo ?? '',
seed: fallbackApplicationData?.name ?? '',
},
};
}
const isCurrent =
isDefined(currentApplicationId) && currentApplicationId === applicationId;
const isStandard = isTwentyStandardApplication(application);
const displayName = isCurrent
? t`This app`
: isTwentyStandardApplication(application)
? t`Standard`
: isWorkspaceCustomApplication(application, currentWorkspace)
? t`Custom`
: application.name;
const isCustom = isWorkspaceCustomApplication(application, currentWorkspace);
const displayName = isStandard
? t`Standard`
: isCustom
? t`Custom`
: application.name;
const logo = isStandard
? new URL(StandardLogo, window.location.href).toString()
: isCustom
? new URL(CustomLogo, window.location.href).toString()
: buildApplicationLogoUrl({
applicationId: application.id,
logo: application.logo,
workspaceId: currentWorkspace?.id,
});
return {
applicationChipData: {
name: displayName,
seed: application.universalIdentifier ?? application.name,
seed: application.name,
colors,
logo,
},
};
};
@@ -0,0 +1,6 @@
export type ApplicationDisplayData = {
id?: string | null;
name?: string | null;
universalIdentifier?: string | null;
logo?: string | null;
};
@@ -0,0 +1,26 @@
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { isDefined } from 'twenty-shared/utils';
export const buildApplicationLogoUrl = ({
workspaceId,
applicationId,
logo,
}: {
workspaceId?: string | null;
applicationId?: string | null;
logo?: string | null;
}): string | undefined => {
if (
!isDefined(logo) ||
logo.startsWith('http://') ||
logo.startsWith('https://')
) {
return logo ?? undefined;
}
if (!isDefined(workspaceId) || !isDefined(applicationId)) {
return undefined;
}
return `${REACT_APP_SERVER_BASE_URL}/public-assets/${workspaceId}/${applicationId}/${logo}`;
};