diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventParticipantsAvatarGroup.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventParticipantsAvatarGroup.tsx index e409307367..d760419034 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventParticipantsAvatarGroup.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventParticipantsAvatarGroup.tsx @@ -3,6 +3,7 @@ import { isTimelineCalendarEventParticipant } from '@/activities/calendar/types/ import { isDefined } from 'twenty-shared/utils'; import { Avatar, AvatarGroup } from 'twenty-ui/data-display'; import { type TimelineCalendarEventParticipant } from '~/generated/graphql'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; type CalendarEventParticipantsAvatarGroupProps = { participants: CalendarEventParticipant[] | TimelineCalendarEventParticipant[]; @@ -51,7 +52,7 @@ export const CalendarEventParticipantsAvatarGroup = ({ key={[participant.workspaceMemberId, participant.displayName] .filter(isDefined) .join('-')} - avatarUrl={participant.avatarUrl} + avatarUrl={getAbsoluteImageUrl(participant.avatarUrl)} placeholder={ participant.firstName && participant.lastName ? `${participant.firstName} ${participant.lastName}` diff --git a/packages/twenty-front/src/modules/activities/components/ParticipantChip.tsx b/packages/twenty-front/src/modules/activities/components/ParticipantChip.tsx index aea29ca94f..0bd0ff2e5d 100644 --- a/packages/twenty-front/src/modules/activities/components/ParticipantChip.tsx +++ b/packages/twenty-front/src/modules/activities/components/ParticipantChip.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { getDisplayNameFromParticipant } from '@/activities/emails/utils/getDisplayNameFromParticipant'; import { CoreObjectNameSingular } from 'twenty-shared/types'; import { RecordChip } from '@/object-record/components/RecordChip'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; import { Avatar } from 'twenty-ui/data-display'; import { themeCssVariables } from 'twenty-ui/theme-constants'; @@ -69,7 +70,7 @@ export const ParticipantChip = ({ ` display: flex; @@ -118,7 +119,7 @@ export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => { { {isDefined(thread?.lastTwoParticipants?.[0]) && ( { {finalDisplayedName && ( { diff --git a/packages/twenty-front/src/modules/mention/components/MentionMenuListItem.tsx b/packages/twenty-front/src/modules/mention/components/MentionMenuListItem.tsx index 5d887a21de..cd8d3ae7d6 100644 --- a/packages/twenty-front/src/modules/mention/components/MentionMenuListItem.tsx +++ b/packages/twenty-front/src/modules/mention/components/MentionMenuListItem.tsx @@ -1,6 +1,7 @@ import { type MouseEvent } from 'react'; import { getAvatarType } from '@/object-metadata/utils/getAvatarType'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; import { Avatar } from 'twenty-ui/data-display'; import { MenuItemSuggestion } from 'twenty-ui/navigation'; @@ -40,7 +41,7 @@ export const MentionMenuListItem = ({ diff --git a/packages/twenty-front/src/modules/mention/components/MentionRecordChip.tsx b/packages/twenty-front/src/modules/mention/components/MentionRecordChip.tsx index 2e22a76dbf..7409528639 100644 --- a/packages/twenty-front/src/modules/mention/components/MentionRecordChip.tsx +++ b/packages/twenty-front/src/modules/mention/components/MentionRecordChip.tsx @@ -1,4 +1,5 @@ import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; import { t } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { @@ -59,7 +60,7 @@ export const MentionRecordChip = ({ placeholder={label} placeholderColorSeed={recordId} avatarType="rounded" - avatarUrl={imageUrl} + avatarUrl={getAbsoluteImageUrl(imageUrl)} /> } /> diff --git a/packages/twenty-front/src/modules/mention/components/MentionSuggestionMenu.tsx b/packages/twenty-front/src/modules/mention/components/MentionSuggestionMenu.tsx index 7a6f45cf76..a9681c96ed 100644 --- a/packages/twenty-front/src/modules/mention/components/MentionSuggestionMenu.tsx +++ b/packages/twenty-front/src/modules/mention/components/MentionSuggestionMenu.tsx @@ -5,6 +5,7 @@ import { MenuItemSuggestion } from 'twenty-ui/navigation'; import type { MentionSearchResult } from '@/mention/types/MentionSearchResult'; import type { MentionSuggestionMenuProps } from '@/mention/types/MentionSuggestionMenuProps'; import { SuggestionMenu } from '@/ui/suggestion/components/SuggestionMenu'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; const getItemKey = (item: MentionSearchResult) => `${item.objectNameSingular}-${item.recordId}`; @@ -20,7 +21,7 @@ const renderItem = ( placeholder={item.label} placeholderColorSeed={item.recordId} avatarType="rounded" - avatarUrl={item.imageUrl} + avatarUrl={getAbsoluteImageUrl(item.imageUrl)} /> )} text={item.label} diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemIcon.tsx b/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemIcon.tsx index aa074076d5..20895b4d7f 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemIcon.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/components/NavigationMenuItemIcon.tsx @@ -20,6 +20,7 @@ import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandard import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { viewsSelector } from '@/views/states/selectors/viewsSelector'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; export const NavigationMenuItemIcon = ({ navigationMenuItem, @@ -155,7 +156,7 @@ export const NavigationMenuItemIcon = ({ type={recordIdentifier?.avatarType ?? 'icon'} Icon={iconToUse} iconColor={iconColorToUse} - avatarUrl={recordIdentifier?.avatarUrl ?? ''} + avatarUrl={getAbsoluteImageUrl(recordIdentifier?.avatarUrl ?? '')} placeholder={labelIdentifier} placeholderColorSeed={navigationMenuItem.targetRecordId ?? undefined} /> diff --git a/packages/twenty-front/src/modules/navigation-menu-item/display/object/components/NavigationDrawerItemForObjectMetadataItem.tsx b/packages/twenty-front/src/modules/navigation-menu-item/display/object/components/NavigationDrawerItemForObjectMetadataItem.tsx index 17cbc21511..751d2d10eb 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/display/object/components/NavigationDrawerItemForObjectMetadataItem.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/display/object/components/NavigationDrawerItemForObjectMetadataItem.tsx @@ -30,6 +30,7 @@ import { Avatar } from 'twenty-ui/data-display'; import { IconLock, useIcons } from 'twenty-ui/icon'; import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; export type NavigationDrawerItemForObjectMetadataItemProps = { objectMetadataItem: EnrichedObjectMetadataItem; @@ -150,7 +151,7 @@ export const NavigationDrawerItemForObjectMetadataItem = ({ ? 'squared' : 'rounded' } - avatarUrl={recordIdentifier?.avatarUrl} + avatarUrl={getAbsoluteImageUrl(recordIdentifier?.avatarUrl)} placeholderColorSeed={navigationMenuItem!.targetRecordId ?? undefined} placeholder={itemLabel} /> diff --git a/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordItem.tsx b/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordItem.tsx index 7d3494ac37..13a60c618b 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordItem.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordItem.tsx @@ -14,6 +14,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat import { SidePanelItemWithAddToNavigationDrag } from '@/side-panel/components/SidePanelItemWithAddToNavigationDrag'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; type SearchRecord = { recordId: string; @@ -90,7 +91,7 @@ export const SidePanelNewSidebarItemRecordItem = ({ ? 'squared' : 'rounded' } - avatarUrl={record.imageUrl} + avatarUrl={getAbsoluteImageUrl(record.imageUrl)} placeholderColorSeed={record.recordId} placeholder={record.label} /> diff --git a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx index 800416a17e..903493a81d 100644 --- a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx +++ b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx @@ -7,6 +7,7 @@ import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { canOpenObjectInSidePanel } from '@/object-record/utils/canOpenObjectInSidePanel'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { ViewOpenRecordIn } from '~/generated-metadata/graphql'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; import { t } from '@lingui/core/macro'; import { type MouseEvent } from 'react'; import { isDefined } from 'twenty-shared/utils'; @@ -98,7 +99,7 @@ export const RecordChip = ({ placeholder={recordChipData.name} placeholderColorSeed={record.id} avatarType={recordChipData.avatarType} - avatarUrl={recordChipData.avatarUrl ?? ''} + avatarUrl={getAbsoluteImageUrl(recordChipData.avatarUrl ?? '')} /> ) } @@ -120,7 +121,7 @@ export const RecordChip = ({ placeholder={recordChipData.name} placeholderColorSeed={record.id} avatarType={recordChipData.avatarType} - avatarUrl={recordChipData.avatarUrl ?? ''} + avatarUrl={getAbsoluteImageUrl(recordChipData.avatarUrl ?? '')} /> ) } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordPinnedItems.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordPinnedItems.tsx index cb6797501d..9b4b6bc475 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordPinnedItems.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordPinnedItems.tsx @@ -3,6 +3,7 @@ import { styled } from '@linaria/react'; import { Avatar } from 'twenty-ui/data-display'; import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation'; import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; const StyledPinnedItemsContainer = styled.div` display: flex; @@ -31,7 +32,7 @@ export const ObjectFilterDropdownRecordPinnedItems = (props: { avatar={ selectableItem.avatarUrl ? ( { overflow="hidden" > { {user.workspaceId ? ( <> { overflow="hidden" > } /> diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx index 3bae4af5a2..734ede6fe6 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx @@ -12,6 +12,7 @@ import { AppTooltip, TooltipDelay } from 'twenty-ui/surfaces'; import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; import { type RoleWithPartialMembers } from '@/settings/roles/types/RoleWithPartialMembers'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; const StyledAssignedText = styled.div` color: ${themeCssVariables.font.color.secondary}; @@ -102,7 +103,7 @@ export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => {
) : ( { LeftComponent={ diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/BackgroundMockTableRow.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/BackgroundMockTableRow.tsx index a4f1f52e64..88a11aa169 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/BackgroundMockTableRow.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/BackgroundMockTableRow.tsx @@ -4,6 +4,7 @@ import { useContext } from 'react'; import { type BackgroundMockCompany } from '@/sign-in-background-mock/constants/BackgroundMockCompanies'; import { BACKGROUND_MOCK_COLUMN_WIDTHS } from '@/sign-in-background-mock/constants/BackgroundMockColumnWidths'; import { BACKGROUND_MOCK_TABLE_DIMENSIONS } from '@/sign-in-background-mock/constants/BackgroundMockTableDimensions'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; import { Avatar, Chip, @@ -125,7 +126,7 @@ export const BackgroundMockTableRow = ({ leftComponent={ & { avatarUrl?: string | null; @@ -96,7 +97,7 @@ export const ActorDisplay = ({ avatarType={workspaceMemberId ? 'rounded' : 'squared'} placeholder={name} Icon={LeftIcon} - avatarUrl={avatarUrl ?? undefined} + avatarUrl={getAbsoluteImageUrl(avatarUrl ?? undefined)} /> } /> diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx index 3be85ae3b8..6082d30ba2 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx @@ -15,6 +15,7 @@ import { beautifyExactDateTime, beautifyPastDateRelativeToNow, } from '~/utils/date-utils'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; type ShowPageSummaryCardProps = { avatarPlaceholder: string; @@ -148,7 +149,7 @@ export const ShowPageSummaryCard = ({ hasIcon={isDefined(icon)} > { const { theme } = useContext(ThemeContext); if (isDefined(tab.logo)) { - return ; + return ( + + ); } return ( tab.Icon && ( diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownClickableComponent.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownClickableComponent.tsx index e629c49314..c59aee408d 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownClickableComponent.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownClickableComponent.tsx @@ -9,6 +9,7 @@ import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigat import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo'; import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; import { useContext } from 'react'; import { Avatar } from 'twenty-ui/data-display'; import { ThemeContext } from 'twenty-ui/theme-constants'; @@ -34,7 +35,9 @@ export const MultiWorkspaceDropdownClickableComponent = ({ > diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx index 588e6e4b37..553c061f50 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx @@ -45,6 +45,7 @@ import { } from 'twenty-ui/navigation'; import { type AvailableWorkspace } from '~/generated-metadata/graphql'; import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; const StyledDescription = styled.div` color: ${themeCssVariables.font.color.light}; @@ -100,7 +101,9 @@ export const MultiWorkspaceDropdownDefaultComponents = () => { Avatar={ } /> @@ -161,9 +164,9 @@ export const MultiWorkspaceDropdownDefaultComponents = () => { avatar={ } selected={false} diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/AvailableWorkspaceItem.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/AvailableWorkspaceItem.tsx index a25cb7d7f5..2736411ee5 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/AvailableWorkspaceItem.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/AvailableWorkspaceItem.tsx @@ -4,6 +4,7 @@ import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/consta import { type AvailableWorkspace } from '~/generated-metadata/graphql'; import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; import { getAvailableWorkspacePathAndSearchParams } from '@/auth/utils/availableWorkspacesUtils'; import { t } from '@lingui/core/macro'; import React from 'react'; @@ -49,7 +50,9 @@ export const AvailableWorkspaceItem = ({ avatar={ } selected={isSelected} diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationDetail.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationDetail.tsx index a0fe85cfef..0d55849cd4 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationDetail.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationDetail.tsx @@ -15,6 +15,7 @@ import { IconSettings, IconWorld, } from 'twenty-ui/icon'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; import { SettingsApplicationRegistrationConfigTab } from '~/pages/settings/applications/tabs/SettingsApplicationRegistrationConfigTab'; import { SettingsApplicationRegistrationOAuthTab } from '~/pages/settings/applications/tabs/SettingsApplicationRegistrationOAuthTab'; import { SettingsApplicationRegistrationDistributionTab } from '~/pages/settings/applications/tabs/SettingsApplicationRegistrationDistributionTab'; @@ -108,7 +109,7 @@ export const SettingsAdminApplicationRegistrationDetail = () => { 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 396ed662b5..c7c61b6901 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminWorkspaceDetail.tsx @@ -45,6 +45,7 @@ import { H2Title } from 'twenty-ui/typography'; import { Button, Toggle } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; import { type FeatureFlagKey, type GetAdminWorkspaceChatThreadsQuery, @@ -267,7 +268,7 @@ export const SettingsAdminWorkspaceDetail = () => { overflow="hidden" > { diff --git a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationRegistrationGeneralInfo.tsx b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationRegistrationGeneralInfo.tsx index 7ad7318d94..a3ed6653e4 100644 --- a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationRegistrationGeneralInfo.tsx +++ b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationRegistrationGeneralInfo.tsx @@ -33,6 +33,7 @@ import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { Section } from 'twenty-ui/layout'; import { SettingsPath } from 'twenty-shared/types'; import { SettingsApplicationRegistrationShareLinkButtons } from '~/pages/settings/applications/components/SettingsApplicationRegistrationShareLinkButtons'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; const StyledSourceRow = styled.div` align-items: center; @@ -119,7 +120,9 @@ export const SettingsApplicationRegistrationGeneralInfo = ({ leftComponent={ } label={ownerWorkspace.displayName} 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 447e16266e..f7f614f897 100644 --- a/packages/twenty-front/src/pages/settings/applications/components/SettingsAvailableApplicationCard.tsx +++ b/packages/twenty-front/src/pages/settings/applications/components/SettingsAvailableApplicationCard.tsx @@ -13,6 +13,7 @@ import { Card } from 'twenty-ui/surfaces'; import { themeCssVariables } from 'twenty-ui/theme-constants'; import { type MarketplaceApp } from '~/generated-metadata/graphql'; import { getApplicationDescriptionSummary } from '~/pages/settings/applications/utils/getApplicationDescriptionSummary'; +import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl'; type SettingsAvailableApplicationCardProps = { application: MarketplaceApp; @@ -60,7 +61,7 @@ export const SettingsAvailableApplicationCard = ({ ` @@ -248,7 +249,9 @@ export const SettingsWorkspaceMembersTeamTab = () => { ({ + REACT_APP_SERVER_BASE_URL: 'https://example.com', +})); + +describe('getAbsoluteImageUrl', () => { + it('returns undefined for nullish or empty input', () => { + expect(getAbsoluteImageUrl(undefined)).toBeUndefined(); + expect(getAbsoluteImageUrl(null)).toBeUndefined(); + expect(getAbsoluteImageUrl('')).toBeUndefined(); + }); + + it('returns absolute http(s) URLs unchanged', () => { + expect(getAbsoluteImageUrl('https://cdn.example.com/avatar.png')).toBe( + 'https://cdn.example.com/avatar.png', + ); + expect(getAbsoluteImageUrl('http://cdn.example.com/avatar.png')).toBe( + 'http://cdn.example.com/avatar.png', + ); + }); + + it('resolves relative paths against the server base URL under /files', () => { + expect(getAbsoluteImageUrl('avatars/avatar.png')).toBe( + 'https://example.com/files/avatars/avatar.png', + ); + expect(getAbsoluteImageUrl('/avatars/avatar.png')).toBe( + 'https://example.com/files/avatars/avatar.png', + ); + }); +}); diff --git a/packages/twenty-front/src/utils/image/getAbsoluteImageUrl.ts b/packages/twenty-front/src/utils/image/getAbsoluteImageUrl.ts new file mode 100644 index 0000000000..96de8391e5 --- /dev/null +++ b/packages/twenty-front/src/utils/image/getAbsoluteImageUrl.ts @@ -0,0 +1,11 @@ +import { isNonEmptyString } from '@sniptt/guards'; +import { getImageAbsoluteURI } from 'twenty-shared/utils'; + +import { REACT_APP_SERVER_BASE_URL } from '~/config'; + +export const getAbsoluteImageUrl = ( + imageUrl?: string | null, +): string | undefined => + isNonEmptyString(imageUrl) + ? getImageAbsoluteURI({ imageUrl, baseUrl: REACT_APP_SERVER_BASE_URL }) + : undefined; diff --git a/packages/twenty-shared/src/utils/image/__tests__/getImageAbsoluteURI.test.ts b/packages/twenty-shared/src/utils/image/__tests__/getImageAbsoluteURI.test.ts index 97313daa4d..c1c9d02ad8 100644 --- a/packages/twenty-shared/src/utils/image/__tests__/getImageAbsoluteURI.test.ts +++ b/packages/twenty-shared/src/utils/image/__tests__/getImageAbsoluteURI.test.ts @@ -14,6 +14,44 @@ describe('getImageAbsoluteURI', () => { expect(result).toBe(imageUrl); }); + it('should return http absolute url unchanged', () => { + const imageUrl = 'http://XXX/pic.png'; + const baseUrl = 'http://localhost:3000'; + const result = getImageAbsoluteURI({ imageUrl, baseUrl }); + expect(result).toBe(imageUrl); + }); + + it('should treat schemes case-insensitively and return them unchanged', () => { + const baseUrl = 'http://localhost:3000'; + expect(getImageAbsoluteURI({ imageUrl: 'HTTPS://XXX', baseUrl })).toBe( + 'HTTPS://XXX', + ); + expect( + getImageAbsoluteURI({ imageUrl: 'Data:image/png;base64,AAAA', baseUrl }), + ).toBe('Data:image/png;base64,AAAA'); + }); + + it('should return data URIs unchanged', () => { + const imageUrl = 'data:image/png;base64,iVBORw0KGgo='; + const baseUrl = 'http://localhost:3000'; + const result = getImageAbsoluteURI({ imageUrl, baseUrl }); + expect(result).toBe(imageUrl); + }); + + it('should return blob URLs unchanged', () => { + const imageUrl = 'blob:http://localhost:3000/123e4567-e89b-12d3'; + const baseUrl = 'http://localhost:3000'; + const result = getImageAbsoluteURI({ imageUrl, baseUrl }); + expect(result).toBe(imageUrl); + }); + + it('should return protocol-relative URLs unchanged', () => { + const imageUrl = '//cdn.example.com/pic.png'; + const baseUrl = 'http://localhost:3000'; + const result = getImageAbsoluteURI({ imageUrl, baseUrl }); + expect(result).toBe(imageUrl); + }); + it('should return fully formed url if imageUrl is a relative url starting with /', () => { const imageUrl = '/path/pic.png'; const baseUrl = 'http://localhost:3000'; diff --git a/packages/twenty-shared/src/utils/image/getImageAbsoluteURI.ts b/packages/twenty-shared/src/utils/image/getImageAbsoluteURI.ts index 8f041a4e4c..6fab03acf7 100644 --- a/packages/twenty-shared/src/utils/image/getImageAbsoluteURI.ts +++ b/packages/twenty-shared/src/utils/image/getImageAbsoluteURI.ts @@ -7,7 +7,13 @@ export const getImageAbsoluteURI = ({ imageUrl, baseUrl, }: getImageAbsoluteURIProps): string => { - if (imageUrl.startsWith('https:') || imageUrl.startsWith('http:')) { + const lowerCaseImageUrl = imageUrl.toLowerCase(); + const isAlreadyAbsoluteUri = + ['http:', 'https:', 'data:', 'blob:'].some((scheme) => + lowerCaseImageUrl.startsWith(scheme), + ) || imageUrl.startsWith('//'); + + if (isAlreadyAbsoluteUri) { return imageUrl; } diff --git a/packages/twenty-ui/src/data-display/Avatar/Avatar.tsx b/packages/twenty-ui/src/data-display/Avatar/Avatar.tsx index 87423aa3c3..a0bcf47f7e 100644 --- a/packages/twenty-ui/src/data-display/Avatar/Avatar.tsx +++ b/packages/twenty-ui/src/data-display/Avatar/Avatar.tsx @@ -8,9 +8,7 @@ import { type AvatarType } from '@ui/data-display/Avatar/types/AvatarType'; import { type IconComponent } from '@ui/icon/types/IconComponent'; import { ThemeContext } from '@ui/theme-constants'; import { stringToThemeColorP3String } from '@ui/utilities'; -import { REACT_APP_SERVER_BASE_URL } from '@ui/utilities/config'; import { type Nullable } from '@ui/utilities/types/Nullable'; -import { getImageAbsoluteURI } from '@ui/utilities/utils/getImageAbsoluteURI'; import styles from './Avatar.module.scss'; @@ -49,12 +47,7 @@ export const Avatar = ({ string | null >(null); - const avatarImageURI = isNonEmptyString(avatarUrl) - ? getImageAbsoluteURI({ - imageUrl: avatarUrl, - baseUrl: REACT_APP_SERVER_BASE_URL, - }) - : null; + const avatarImageURI = isNonEmptyString(avatarUrl) ? avatarUrl : null; const placeholderFirstChar = placeholder?.trim()?.charAt(0); const isPlaceholderFirstCharEmpty = diff --git a/packages/twenty-ui/src/global.d.ts b/packages/twenty-ui/src/global.d.ts deleted file mode 100644 index e7b6551942..0000000000 --- a/packages/twenty-ui/src/global.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare global { - interface Window { - _env_?: Record; - } -} - -export {}; diff --git a/packages/twenty-ui/src/utilities/config.ts b/packages/twenty-ui/src/utilities/config.ts deleted file mode 100644 index 91782a631e..0000000000 --- a/packages/twenty-ui/src/utilities/config.ts +++ /dev/null @@ -1,21 +0,0 @@ -const getDefaultUrl = () => { - if ( - window.location.hostname === 'localhost' || - window.location.hostname === '127.0.0.1' - ) { - // In development environment front and backend usually run on separate ports - // we set the default value to localhost:3000. - // In dev context, we use env vars to overwrite it - return 'http://localhost:3000'; - } else { - // Outside of localhost we assume that they run on the same port - // because the backend will serve the frontend - // In prod context, we use index.html + window var to ovewrite it - return `${window.location.protocol}//${window.location.hostname}${ - window.location.port ? `:${window.location.port}` : '' - }`; - } -}; - -export const REACT_APP_SERVER_BASE_URL = - window._env_?.REACT_APP_SERVER_BASE_URL || getDefaultUrl(); diff --git a/packages/twenty-ui/src/utilities/index.ts b/packages/twenty-ui/src/utilities/index.ts index 716cb77a0e..1d0ffc3688 100644 --- a/packages/twenty-ui/src/utilities/index.ts +++ b/packages/twenty-ui/src/utilities/index.ts @@ -14,7 +14,6 @@ export { stringToThemeColorP3String, } from './color/utils/stringToThemeColorP3String'; export { themeColorSchema } from './color/utils/themeColorSchema'; -export { REACT_APP_SERVER_BASE_URL } from './config'; export { getOsControlSymbol } from './device/getOsControlSymbol'; export { getOsShortcutSeparator } from './device/getOsShortcutSeparator'; export { getUserDevice } from './device/getUserDevice'; @@ -27,7 +26,6 @@ export { useScreenSize } from './screen-size/hooks/useScreenSize'; export type { ClickOutsideAttributes } from './types/ClickOutsideAttributes'; export type { Nullable } from './types/Nullable'; export { getDisplayValueByUrlType } from './utils/getDisplayValueByUrlType'; -export { getImageAbsoluteURI } from './utils/getImageAbsoluteURI'; export { getSafeUrl } from './utils/getSafeUrl'; export { isDefined } from './utils/isDefined'; export type { LinkifyMatch } from './utils/linkifyText'; diff --git a/packages/twenty-ui/src/utilities/utils/getImageAbsoluteURI.ts b/packages/twenty-ui/src/utilities/utils/getImageAbsoluteURI.ts deleted file mode 100644 index 8f041a4e4c..0000000000 --- a/packages/twenty-ui/src/utilities/utils/getImageAbsoluteURI.ts +++ /dev/null @@ -1,19 +0,0 @@ -type getImageAbsoluteURIProps = { - imageUrl: string; - baseUrl: string; -}; - -export const getImageAbsoluteURI = ({ - imageUrl, - baseUrl, -}: getImageAbsoluteURIProps): string => { - if (imageUrl.startsWith('https:') || imageUrl.startsWith('http:')) { - return imageUrl; - } - - if (imageUrl.startsWith('/')) { - return new URL(`/files${imageUrl}`, baseUrl).toString(); - } - - return new URL(`/files/${imageUrl}`, baseUrl).toString(); -};