Skip to content

Commit 346f832

Browse files
timothejoubertTimothe Joubert
andauthored
Refactor(useRoadizHeadSocials): use map and add order property (#606)
* refactor: streamline getSocialLinks function and improve key normalization * chore: run lint * refactor: update social links handling and improve storybook examples * chore: run lint fix --------- Co-authored-by: Timothe Joubert <timothejoubert26@gmail.com>
1 parent 98f8a88 commit 346f832

2 files changed

Lines changed: 61 additions & 58 deletions

File tree

app/composables/use-roadiz-head-socials.ts

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,56 +5,56 @@ export interface SocialLink {
55
icon?: string
66
}
77

8-
export function getSocialLinks({ key, url }: { key: string, url: string }) {
9-
// Remove trailing "url" and convert to lowercase for better matching
10-
// For example, "twitterUrl" becomes "twitter", "linkedin_url" becomes "linkedin"
11-
const formattedKey = key.toLowerCase().replace(/_?url$/, '')
8+
// useRoadizHeadSocialLinks will sort the social links based on the order defined in SOCIAL_NETWORKS
9+
// and will use a default icon for unknown networks
10+
const SOCIAL_NETWORKS = [
11+
{ prefixes: ['instagram'], name: 'Instagram', icon: 'social-instagram' },
12+
{ prefixes: ['facebook'], name: 'Facebook', icon: 'social-facebook' },
13+
{ prefixes: ['tiktok'], name: 'Tiktok', icon: 'social-tiktok' },
14+
{ prefixes: ['linkedin'], name: 'LinkedIn', icon: 'social-linkedin' },
15+
{ prefixes: ['mastodon'], name: 'Mastodon', icon: 'social-mastodon' },
16+
{ prefixes: ['pinterest'], name: 'Pinterest', icon: 'social-pinterest' },
17+
{ prefixes: ['snapchat'], name: 'Snapchat', icon: 'social-snapchat' },
18+
{ prefixes: ['youtube'], name: 'Youtube', icon: 'social-youtube' },
19+
{ prefixes: ['spotify'], name: 'Spotify', icon: 'social-spotify' },
20+
{ prefixes: ['twitter', 'x'], name: 'X', icon: 'social-x' },
21+
] as const
1222

13-
if (formattedKey.startsWith('mastodon')) {
14-
return { url: url, name: 'Mastodon', icon: 'social-mastodon' }
15-
}
16-
if (formattedKey.startsWith('pinterest')) {
17-
return { url: url, name: 'Pinterest', icon: 'social-pinterest' }
18-
}
19-
if (formattedKey.startsWith('snapchat')) {
20-
return { url: url, name: 'Snapchat', icon: 'social-snapchat' }
21-
}
22-
if (formattedKey.startsWith('instagram')) {
23-
return { url: url, name: 'Instagram', icon: 'social-instagram' }
24-
}
25-
if (formattedKey.startsWith('youtube')) {
26-
return { url: url, name: 'Youtube', icon: 'social-youtube' }
27-
}
28-
if (formattedKey.startsWith('linkedin')) {
29-
return { url: url, name: 'LinkedIn', icon: 'social-linkedin' }
30-
}
31-
if (formattedKey.startsWith('facebook')) {
32-
return { url: url, name: 'Facebook', icon: 'social-facebook' }
33-
}
34-
if (formattedKey.startsWith('tiktok')) {
35-
return { url: url, name: 'Tiktok', icon: 'social-tiktok' }
36-
}
37-
if (formattedKey.startsWith('spotify')) {
38-
return { url: url, name: 'Spotify', icon: 'social-spotify' }
39-
}
40-
if (formattedKey.startsWith('twitter') || formattedKey === 'x') {
41-
return { url: url, name: 'X', icon: 'social-x' }
23+
// Api keys in commonContent.urls can be in snake_case or camelCase,
24+
// so we need to normalize them to match the prefixes in SOCIAL_NETWORKS
25+
function normalizeKey(key: string): string {
26+
return key.replace(/_url$/i, '').replace(/Url$/, '').toLowerCase()
27+
}
28+
29+
export function getSocialLinks(key: string, url: string): SocialLink {
30+
const normalized = normalizeKey(key)
31+
const network = SOCIAL_NETWORKS.find(({ prefixes }) => prefixes.some(p => normalized === p))
32+
33+
if (network) {
34+
return {
35+
url,
36+
name: network.name,
37+
icon: network.icon,
38+
}
4239
}
4340

4441
return {
45-
url: url,
46-
name: key,
42+
url,
43+
name: normalized,
4744
icon: 'link',
4845
}
4946
}
5047

5148
export function useRoadizHeadSocialLinks() {
5249
const { data } = useCommonContent()
5350

54-
return computed(() => {
55-
const urlEntriesList = Object.entries(data.value?.urls || {})
56-
.filter(([_key, value]) => typeof value === 'string') as [string, string][]
57-
58-
return urlEntriesList.map(([key, url]) => getSocialLinks({ key, url }))
59-
})
51+
return computed(() =>
52+
Object.entries(data.value?.urls || {})
53+
.filter(([, value]) => typeof value === 'string')
54+
.map(([key, url]) => getSocialLinks(key, url as string))
55+
.sort((a, b) => {
56+
const ai = SOCIAL_NETWORKS.findIndex(({ name }) => name === a.name)
57+
const bi = SOCIAL_NETWORKS.findIndex(({ name }) => name === b.name)
58+
return (ai === -1 ? SOCIAL_NETWORKS.length : ai) - (bi === -1 ? SOCIAL_NETWORKS.length : bi)
59+
}))
6060
}

stories/app/components/VSocialLinks/Default.stories.vue

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
11
<script setup lang="ts">
2-
const list = [
3-
{
4-
name: 'Twitter',
5-
url: 'https://twitter.com/roadiz_io',
6-
},
7-
{
8-
name: 'Facebook',
9-
url: 'https://www.facebook.com/roadiz.io/',
10-
},
11-
{
12-
name: 'LinkedIn',
13-
url: 'https://www.linkedin.com/company/roadiz/',
14-
},
2+
// Keys as they come from the API — snake_case or camelCase are both supported
3+
const known = [
4+
getSocialLinks('instagram_url', 'https://instagram.com/roadiz'),
5+
getSocialLinks('facebookUrl', 'https://facebook.com/roadiz'),
6+
getSocialLinks('linkedin_url', 'https://linkedin.com/company/roadiz'),
7+
getSocialLinks('youtubeUrl', 'https://youtube.com/@roadiz'),
8+
getSocialLinks('twitter_url', 'https://twitter.com/roadiz_io'),
159
]
1610
17-
const socials = list.map(social => getSocialLinks(social.name, social.url))
11+
// An unrecognized key falls back to a generic 'link' icon
12+
const withUnknown = [
13+
getSocialLinks('instagram_url', 'https://instagram.com/roadiz'),
14+
getSocialLinks('discord_url', 'https://discord.gg/roadiz'),
15+
getSocialLinks('linkedin_url', 'https://linkedin.com/company/roadiz'),
16+
]
1817
</script>
1918

2019
<template>
2120
<NuxtStory>
2221
<NuxtStoryVariant title="Default">
23-
<VSocialLinks :socials="socials" />
22+
<VSocialLinks :socials="known" />
23+
</NuxtStoryVariant>
24+
25+
<NuxtStoryVariant title="With unknown network">
26+
<VSocialLinks :socials="withUnknown" />
2427
</NuxtStoryVariant>
2528

26-
<NuxtStoryVariant title="Slot">
27-
<VSocialLinks :socials="socials">
29+
<NuxtStoryVariant title="Custom slot">
30+
<VSocialLinks :socials="known">
2831
<template #default="{ social }">
2932
<NuxtLink
3033
:to="social.url"

0 commit comments

Comments
 (0)