Fix broken image urls in Settings > Profile and Invite To Workspace Email (#8942)
Fixes #8601 We had 3 implementations of getImageAbsoluteURI: in twenty-front, in twenty-ui and in twenty-emails. I was able to remove the one in twenty-front but I could not remove it from twenty-emails as this is a standalone for now. The vision is to introduce shared utils in a twenty-shared package
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||
import { getImageAbsoluteURI, isDefined } from 'twenty-ui';
|
||||
|
||||
type LogoProps = {
|
||||
primaryLogo?: string | null;
|
||||
@ -49,7 +48,9 @@ export const Logo = (props: LogoProps) => {
|
||||
const primaryLogoUrl = getImageAbsoluteURI(
|
||||
props.primaryLogo ?? defaultPrimaryLogoUrl,
|
||||
);
|
||||
const secondaryLogoUrl = getImageAbsoluteURI(props.secondaryLogo);
|
||||
const secondaryLogoUrl = isDefined(props.secondaryLogo)
|
||||
? getImageAbsoluteURI(props.secondaryLogo)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
|
||||
@ -8,11 +8,10 @@ import {
|
||||
NavigationDrawerProps,
|
||||
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
|
||||
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||
|
||||
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
|
||||
|
||||
import { AdvancedSettingsToggle } from 'twenty-ui';
|
||||
import { AdvancedSettingsToggle, getImageAbsoluteURI } from 'twenty-ui';
|
||||
import { MainNavigationDrawerItems } from './MainNavigationDrawerItems';
|
||||
|
||||
export type AppNavigationDrawerProps = {
|
||||
|
||||
@ -2,11 +2,11 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
|
||||
import { getImageAbsoluteURI } from 'twenty-ui';
|
||||
import { getImageIdentifierFieldValue } from './getImageIdentifierFieldValue';
|
||||
|
||||
export const getAvatarUrl = (
|
||||
@ -25,7 +25,9 @@ export const getAvatarUrl = (
|
||||
}
|
||||
|
||||
if (objectNameSingular === CoreObjectNameSingular.Person) {
|
||||
return getImageAbsoluteURI(record.avatarUrl) ?? '';
|
||||
return isDefined(record.avatarUrl)
|
||||
? getImageAbsoluteURI(record.avatarUrl)
|
||||
: '';
|
||||
}
|
||||
|
||||
const imageIdentifierFieldValue = getImageIdentifierFieldValue(
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Button, IconPhotoUp, IconTrash, IconUpload, IconX } from 'twenty-ui';
|
||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
IconPhotoUp,
|
||||
IconTrash,
|
||||
IconUpload,
|
||||
IconX,
|
||||
getImageAbsoluteURI,
|
||||
} from 'twenty-ui';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@ -109,7 +115,7 @@ export const ImageInput = ({
|
||||
hiddenFileInput.current?.click();
|
||||
};
|
||||
|
||||
const pictureURI = useMemo(() => getImageAbsoluteURI(picture), [picture]);
|
||||
const pictureURI = isDefined(picture) ? getImageAbsoluteURI(picture) : null;
|
||||
|
||||
return (
|
||||
<StyledContainer className={className}>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { Workspaces } from '@/auth/states/workspaces';
|
||||
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
@ -17,9 +18,8 @@ import {
|
||||
IconChevronDown,
|
||||
MenuItemSelectAvatar,
|
||||
UndecoratedLink,
|
||||
getImageAbsoluteURI,
|
||||
} from 'twenty-ui';
|
||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
|
||||
|
||||
const StyledLogo = styled.div<{ logo: string }>`
|
||||
background: url(${({ logo }) => logo});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||
import { getImageAbsoluteURI } from 'twenty-ui';
|
||||
|
||||
export const PageFavicon = () => {
|
||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||
|
||||
@ -107,7 +107,7 @@ export const SettingsAdminFeatureFlags = () => {
|
||||
title: workspace.name,
|
||||
logo:
|
||||
getImageAbsoluteURI(
|
||||
workspace.logo === null ? DEFAULT_WORKSPACE_LOGO : workspace.logo,
|
||||
isDefined(workspace.logo) ? workspace.logo : DEFAULT_WORKSPACE_LOGO,
|
||||
) ?? '',
|
||||
})) ?? [];
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { within } from '@storybook/test';
|
||||
import { graphql, http, HttpResponse } from 'msw';
|
||||
import { HttpResponse, graphql, http } from 'msw';
|
||||
import { getImageAbsoluteURI } from 'twenty-ui';
|
||||
import { SettingsServerlessFunctionDetail } from '~/pages/settings/serverless-functions/SettingsServerlessFunctionDetail';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
|
||||
const SOURCE_CODE_FULL_PATH =
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import { getImageAbsoluteURI } from '../getImageAbsoluteURI';
|
||||
|
||||
describe('getImageAbsoluteURI', () => {
|
||||
it('should return null if imageUrl is null', () => {
|
||||
const imageUrl = null;
|
||||
const result = getImageAbsoluteURI(imageUrl);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return absolute url if the imageUrl is an absolute url', () => {
|
||||
const imageUrl = 'https://XXX';
|
||||
const result = getImageAbsoluteURI(imageUrl);
|
||||
expect(result).toBe(imageUrl);
|
||||
});
|
||||
|
||||
it('should return fully formed url if imageUrl is a relative url', () => {
|
||||
const imageUrl = 'XXX';
|
||||
const result = getImageAbsoluteURI(imageUrl);
|
||||
expect(result).toBe('http://localhost:3000/files/XXX');
|
||||
});
|
||||
});
|
||||
@ -1,26 +0,0 @@
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
|
||||
type ImageAbsoluteURI<T extends string | null | undefined> = T extends string
|
||||
? string
|
||||
: null;
|
||||
|
||||
export const getImageAbsoluteURI = <T extends string | null | undefined>(
|
||||
imageUrl: T,
|
||||
): ImageAbsoluteURI<T> => {
|
||||
if (!imageUrl) {
|
||||
return null as ImageAbsoluteURI<T>;
|
||||
}
|
||||
|
||||
if (imageUrl.startsWith('https:') || imageUrl.startsWith('http:')) {
|
||||
return imageUrl as ImageAbsoluteURI<T>;
|
||||
}
|
||||
|
||||
const serverFilesUrl = new URL(REACT_APP_SERVER_BASE_URL);
|
||||
|
||||
serverFilesUrl.pathname = `/files/`;
|
||||
serverFilesUrl.pathname += imageUrl.startsWith('/')
|
||||
? imageUrl.slice(1)
|
||||
: imageUrl;
|
||||
|
||||
return serverFilesUrl.toString() as ImageAbsoluteURI<T>;
|
||||
};
|
||||
Reference in New Issue
Block a user