mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 09:02:11 +03:00
Sanitize url before fetching favicon and display letter avatar if it can't be retrieved (#1035)
* Sanitize url before fetching favicon and display letter avatar if it can't be retrieved Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: RubensRafael <rubensrafael2@live.com> * Priorotise www for apple.com domain Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: RubensRafael <rubensrafael2@live.com> * Add requested changes Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: RubensRafael <rubensrafael2@live.com> * Fix the tests Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: RubensRafael <rubensrafael2@live.com> * Change avatar generation strategy Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: RubensRafael <rubensrafael2@live.com> --------- Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: RubensRafael <rubensrafael2@live.com>
This commit is contained in:
parent
bfd748e175
commit
2680289ff7
@ -8,6 +8,7 @@ import {
|
|||||||
ViewFieldDefinition,
|
ViewFieldDefinition,
|
||||||
ViewFieldURLMetadata,
|
ViewFieldURLMetadata,
|
||||||
} from '@/ui/table/types/ViewField';
|
} from '@/ui/table/types/ViewField';
|
||||||
|
import { sanitizeURL } from '~/utils';
|
||||||
|
|
||||||
import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode';
|
import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode';
|
||||||
|
|
||||||
@ -33,7 +34,9 @@ export function GenericEditableURLCell({
|
|||||||
<EditableCell
|
<EditableCell
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeContent={<GenericEditableURLCellEditMode viewField={viewField} />}
|
editModeContent={<GenericEditableURLCellEditMode viewField={viewField} />}
|
||||||
nonEditModeContent={<InplaceInputURLDisplayMode value={fieldValue} />}
|
nonEditModeContent={
|
||||||
|
<InplaceInputURLDisplayMode value={sanitizeURL(fieldValue)} />
|
||||||
|
}
|
||||||
></EditableCell>
|
></EditableCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||||
@ -86,6 +87,20 @@ export function Avatar({
|
|||||||
type = 'squared',
|
type = 'squared',
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const noAvatarUrl = !isNonEmptyString(avatarUrl);
|
const noAvatarUrl = !isNonEmptyString(avatarUrl);
|
||||||
|
const [isInvalidAvatarUrl, setIsInvalidAvatarUrl] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (avatarUrl) {
|
||||||
|
new Promise((resolve) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => resolve(false);
|
||||||
|
img.onerror = () => resolve(true);
|
||||||
|
img.src = getImageAbsoluteURIOrBase64(avatarUrl) as string;
|
||||||
|
}).then((res) => {
|
||||||
|
setIsInvalidAvatarUrl(res as boolean);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [avatarUrl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledAvatar
|
<StyledAvatar
|
||||||
@ -95,7 +110,8 @@ export function Avatar({
|
|||||||
type={type}
|
type={type}
|
||||||
colorId={colorId}
|
colorId={colorId}
|
||||||
>
|
>
|
||||||
{noAvatarUrl && placeholder[0]?.toLocaleUpperCase()}
|
{(noAvatarUrl || isInvalidAvatarUrl) &&
|
||||||
|
placeholder[0]?.toLocaleUpperCase()}
|
||||||
</StyledAvatar>
|
</StyledAvatar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,49 @@
|
|||||||
import { getLogoUrlFromDomainName } from '..';
|
import { getLogoUrlFromDomainName, sanitizeURL } from '..';
|
||||||
|
|
||||||
|
describe('sanitizeURL', () => {
|
||||||
|
test('should sanitize the URL correctly', () => {
|
||||||
|
expect(sanitizeURL('http://example.com/')).toBe('example.com');
|
||||||
|
expect(sanitizeURL('https://www.example.com/')).toBe('example.com');
|
||||||
|
expect(sanitizeURL('www.example.com')).toBe('example.com');
|
||||||
|
expect(sanitizeURL('example.com')).toBe('example.com');
|
||||||
|
expect(sanitizeURL('example.com/')).toBe('example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle undefined input', () => {
|
||||||
|
expect(sanitizeURL(undefined)).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getLogoUrlFromDomainName', () => {
|
describe('getLogoUrlFromDomainName', () => {
|
||||||
it(`should generate logo url if undefined `, () => {
|
test('should return the correct logo URL for a given domain', () => {
|
||||||
|
expect(getLogoUrlFromDomainName('example.com')).toBe(
|
||||||
|
'https://favicon.twenty.com/example.com',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getLogoUrlFromDomainName('http://example.com/')).toBe(
|
||||||
|
'https://favicon.twenty.com/example.com',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getLogoUrlFromDomainName('https://www.example.com/')).toBe(
|
||||||
|
'https://favicon.twenty.com/example.com',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getLogoUrlFromDomainName('www.example.com')).toBe(
|
||||||
|
'https://favicon.twenty.com/example.com',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getLogoUrlFromDomainName('example.com/')).toBe(
|
||||||
|
'https://favicon.twenty.com/example.com',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getLogoUrlFromDomainName('apple.com')).toBe(
|
||||||
|
'https://favicon.twenty.com/apple.com',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle undefined input', () => {
|
||||||
expect(getLogoUrlFromDomainName(undefined)).toBe(
|
expect(getLogoUrlFromDomainName(undefined)).toBe(
|
||||||
'https://api.faviconkit.com/undefined/144',
|
'https://favicon.twenty.com/',
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should generate logo url if defined `, () => {
|
|
||||||
expect(getLogoUrlFromDomainName('test.com')).toBe(
|
|
||||||
'https://api.faviconkit.com/test.com/144',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should generate logo url if empty `, () => {
|
|
||||||
expect(getLogoUrlFromDomainName('')).toBe(
|
|
||||||
'https://api.faviconkit.com//144',
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,15 @@ export function formatToHumanReadableDate(date: Date | string) {
|
|||||||
}).format(parsedJSDate);
|
}).format(parsedJSDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getLogoUrlFromDomainName = (domainName?: string): string => {
|
export function sanitizeURL(link: string | null | undefined) {
|
||||||
return `https://api.faviconkit.com/${domainName}/144`;
|
return link
|
||||||
};
|
? link.replace(/(https?:\/\/)|(www\.)/g, '').replace(/\/$/, '')
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLogoUrlFromDomainName(
|
||||||
|
domainName?: string,
|
||||||
|
): string | undefined {
|
||||||
|
const sanitizedDomain = sanitizeURL(domainName);
|
||||||
|
return `https://favicon.twenty.com/${sanitizedDomain}`;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user