diff --git a/packages/twenty-front/src/modules/ui/field/display/components/LinkDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/LinkDisplay.tsx index 50b10e58f0..4e8047ba2f 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/LinkDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/LinkDisplay.tsx @@ -1,5 +1,6 @@ import { isNonEmptyString } from '@sniptt/guards'; import { LinkType, RoundedLink, SocialLink } from 'twenty-ui/navigation'; +import { checkUrlType } from '~/utils/checkUrlType'; type LinkDisplayProps = { value: { url: string; label?: string | null }; @@ -22,13 +23,13 @@ export const LinkDisplay = ({ value }: LinkDisplayProps) => { ? value.label : url?.replace(/^http[s]?:\/\/(?:[w]+\.)?/gm, '').replace(/^[w]+\./gm, ''); - const type = displayedValue.startsWith('linkedin.') - ? LinkType.LinkedIn - : displayedValue.startsWith('twitter.') - ? LinkType.Twitter - : LinkType.Url; + const type = checkUrlType(absoluteUrl); - if (type === LinkType.LinkedIn || type === LinkType.Twitter) { + if ( + type === LinkType.LinkedIn || + type === LinkType.Twitter || + type === LinkType.Facebook + ) { return ; } diff --git a/packages/twenty-front/src/modules/ui/field/display/components/LinksDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/LinksDisplay.tsx index 05e0f197dc..828fee4750 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/LinksDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/LinksDisplay.tsx @@ -43,7 +43,9 @@ export const LinksDisplay = ({ value, onLinkClick }: LinksDisplayProps) => { return ( {links.map(({ url, label, type }, index) => - type === LinkType.LinkedIn || type === LinkType.Twitter ? ( + type === LinkType.LinkedIn || + type === LinkType.Twitter || + type === LinkType.Facebook ? ( { const type = checkUrlType(absoluteUrl); - if (type === LinkType.LinkedIn || type === LinkType.Twitter) { + if ( + type === LinkType.LinkedIn || + type === LinkType.Twitter || + type === LinkType.Facebook + ) { return ( { it('should return "url", if neither linkedin nor twitter url', () => { expect(checkUrlType('https://www.example.com')).toBe('url'); }); + + it('should return "facebook", if facebook url', () => { + expect(checkUrlType('https://www.facebook.com/john-doe')).toBe('facebook'); + }); }); diff --git a/packages/twenty-front/src/utils/__tests__/getDisplayValueByUrlType.test.ts b/packages/twenty-front/src/utils/__tests__/getDisplayValueByUrlType.test.ts deleted file mode 100644 index 1b988874d7..0000000000 --- a/packages/twenty-front/src/utils/__tests__/getDisplayValueByUrlType.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getDisplayValueByUrlType } from '~/utils/getDisplayValueByUrlType'; -import { LinkType } from 'twenty-ui/navigation'; - -describe('getDisplayValueByUrlType', () => { - it('should return the linkedin username from the url', () => { - expect( - getDisplayValueByUrlType({ - type: LinkType.LinkedIn, - href: 'https://www.linkedin.com/in/håkan-fisk', - }), - ).toBe('håkan-fisk'); - expect( - getDisplayValueByUrlType({ - type: LinkType.LinkedIn, - href: 'https://www.linkedin.com/in/Matías', - }), - ).toBe('Matías'); - expect( - getDisplayValueByUrlType({ - type: LinkType.LinkedIn, - href: 'https://www.linkedin.com/in/Mårten', - }), - ).toBe('Mårten'); - expect( - getDisplayValueByUrlType({ - type: LinkType.LinkedIn, - href: 'https://www.linkedin.com/in/Sörvik', - }), - ).toBe('Sörvik'); - }); - - it('should return the twitter username from the url', () => { - expect( - getDisplayValueByUrlType({ - type: LinkType.Twitter, - href: 'https://www.twitter.com/john-doe', - }), - ).toBe('@john-doe'); - }); -}); diff --git a/packages/twenty-front/src/utils/checkUrlType.ts b/packages/twenty-front/src/utils/checkUrlType.ts index fb8627c3f7..f156b33a15 100644 --- a/packages/twenty-front/src/utils/checkUrlType.ts +++ b/packages/twenty-front/src/utils/checkUrlType.ts @@ -9,6 +9,9 @@ export const checkUrlType = (url: string) => { if (/^(https?:\/\/)?(www\.)?x\.com\/.+$/.test(url)) { return LinkType.Twitter; } + if (/^(https?:\/\/)?(www\.)?facebook\.com\/.+$/.test(url)) { + return LinkType.Facebook; + } return LinkType.Url; }; diff --git a/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts b/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts deleted file mode 100644 index 83c7acc582..0000000000 --- a/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { isDefined } from 'twenty-shared/utils'; -import { type LinkType } from 'twenty-ui/navigation'; - -type getUrlDisplayValueByUrlTypeProps = { - type: LinkType; - href: string; -}; - -export const getDisplayValueByUrlType = ({ - type, - href, -}: getUrlDisplayValueByUrlTypeProps) => { - if (type === 'linkedin') { - const matches = href.match( - /(?:https?:\/\/)?(?:www.)?linkedin.com\/(?:in|company|school)\/(.*)/, - ); - if (isDefined(matches?.[1])) { - return decodeURIComponent(matches?.[1]); - } else { - return 'LinkedIn'; - } - } - - if (type === 'twitter') { - const matches = href.match( - /(?:https?:\/\/)?(?:www.)?twitter.com\/([-a-zA-Z0-9@:%_+.~#?&//=]*)/, - ); - if (isDefined(matches?.[1])) { - return `@${matches?.[1]}`; - } else { - return '@twitter'; - } - } -}; diff --git a/packages/twenty-ui/src/navigation/link/components/SocialLink.tsx b/packages/twenty-ui/src/navigation/link/components/SocialLink.tsx index 06646c796c..13b69a24db 100644 --- a/packages/twenty-ui/src/navigation/link/components/SocialLink.tsx +++ b/packages/twenty-ui/src/navigation/link/components/SocialLink.tsx @@ -7,6 +7,7 @@ export enum LinkType { Url = 'url', LinkedIn = 'linkedin', Twitter = 'twitter', + Facebook = 'facebook', } type SocialLinkProps = { diff --git a/packages/twenty-ui/src/utilities/utils/__tests__/getDisplayValueByUrlType.test.ts b/packages/twenty-ui/src/utilities/utils/__tests__/getDisplayValueByUrlType.test.ts new file mode 100644 index 0000000000..2aa2e162f8 --- /dev/null +++ b/packages/twenty-ui/src/utilities/utils/__tests__/getDisplayValueByUrlType.test.ts @@ -0,0 +1,149 @@ +import { LinkType } from '@ui/navigation/link/components/SocialLink'; + +import { getDisplayValueByUrlType } from '../getDisplayValueByUrlType'; + +describe('getDisplayValueByUrlType', () => { + describe('linkedin', () => { + it('should extract username from LinkedIn profile URL', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.LinkedIn, + href: 'https://www.linkedin.com/in/johndoe', + }); + expect(result).toBe('johndoe'); + }); + + it('should extract company name from LinkedIn company URL', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.LinkedIn, + href: 'https://www.linkedin.com/company/acme-corp', + }); + expect(result).toBe('acme-corp'); + }); + + it('should extract school name from LinkedIn school URL', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.LinkedIn, + href: 'https://www.linkedin.com/school/mit', + }); + expect(result).toBe('mit'); + }); + + it('should handle LinkedIn URL without protocol', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.LinkedIn, + href: 'linkedin.com/in/johndoe', + }); + expect(result).toBe('johndoe'); + }); + + it('should handle LinkedIn URL without www', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.LinkedIn, + href: 'https://linkedin.com/in/johndoe', + }); + expect(result).toBe('johndoe'); + }); + + it('should decode URL-encoded characters in LinkedIn username', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.LinkedIn, + href: 'https://www.linkedin.com/in/john%20doe', + }); + expect(result).toBe('john doe'); + }); + + it('should return "LinkedIn" for invalid LinkedIn URLs', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.LinkedIn, + href: 'https://www.linkedin.com/feed', + }); + expect(result).toBe('LinkedIn'); + }); + }); + + describe('twitter', () => { + it('should extract username from Twitter URL with @ prefix', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Twitter, + href: 'https://www.twitter.com/johndoe', + }); + expect(result).toBe('@johndoe'); + }); + + it('should handle Twitter URL without protocol', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Twitter, + href: 'twitter.com/johndoe', + }); + expect(result).toBe('@johndoe'); + }); + + it('should handle Twitter URL without www', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Twitter, + href: 'https://twitter.com/johndoe', + }); + expect(result).toBe('@johndoe'); + }); + + it('should return "@twitter" when no username can be extracted', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Twitter, + href: 'https://www.twitter.com', + }); + expect(result).toBe('@twitter'); + }); + }); + + describe('facebook', () => { + it('should extract username from Facebook profile URL', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Facebook, + href: 'https://www.facebook.com/johndoe', + }); + expect(result).toBe('johndoe'); + }); + + it('should handle Facebook URL without protocol', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Facebook, + href: 'facebook.com/johndoe', + }); + expect(result).toBe('johndoe'); + }); + + it('should handle Facebook URL without www', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Facebook, + href: 'https://facebook.com/johndoe', + }); + expect(result).toBe('johndoe'); + }); + + it('should decode URL-encoded characters in Facebook username', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Facebook, + href: 'https://www.facebook.com/john%20doe', + }); + expect(result).toBe('john doe'); + }); + + it('should return "Facebook" for invalid Facebook URLs', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Facebook, + href: 'https://www.facebook.com/', + }); + expect(result).toBe('Facebook'); + }); + }); + + describe('url type', () => { + it('should return undefined for generic url type', () => { + const result = getDisplayValueByUrlType({ + type: LinkType.Url, + href: 'https://example.com', + }); + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/packages/twenty-ui/src/utilities/utils/getDisplayValueByUrlType.ts b/packages/twenty-ui/src/utilities/utils/getDisplayValueByUrlType.ts index 7da8fc210b..3db51f9cbe 100644 --- a/packages/twenty-ui/src/utilities/utils/getDisplayValueByUrlType.ts +++ b/packages/twenty-ui/src/utilities/utils/getDisplayValueByUrlType.ts @@ -31,4 +31,13 @@ export const getDisplayValueByUrlType = ({ return '@twitter'; } } + + if (type === 'facebook') { + const matches = href.match(/(?:https?:\/\/)?(?:www.)?facebook.com\/(.+)/); + if (isDefined(matches?.[1])) { + return decodeURIComponent(matches?.[1]); + } else { + return 'Facebook'; + } + } };