Skip to content

Commit 35a73b7

Browse files
howwohmmclaude
andcommitted
fix: address review feedback — unify twitter into socialAccounts array
- Remove separate twitterUsername field from GraphQL query and interfaces; Twitter/X is already returned by GitHub API inside socialAccounts nodes - Remove twitterUsername dedup logic in getSocialLinks; simplify to a direct map over socialAccounts - Replace Object.assign(c, ...) mutation with spread return for type safety - Add json.errors check in fetchGovernanceProfiles to surface silent GraphQL errors - Add socialProviderNames map and getSocialProviderName helper; route social-link aria-labels through $t('about.team.social_link_aria') for i18n - Add about.team.social_link_aria key to en.json Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e7100cc commit 35a73b7

File tree

3 files changed

+53
-39
lines changed

3 files changed

+53
-39
lines changed

app/pages/about.vue

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import type { Role, SocialAccount } from '#server/api/contributors.get'
2+
import type { Role } from '#server/api/contributors.get'
33
import { SPONSORS } from '~/assets/logos/sponsors'
44
import { OSS_PARTNERS } from '~/assets/logos/oss-partners'
55
@@ -47,35 +47,32 @@ const socialIcons: Record<string, string> = {
4747
DISCORD: 'i-simple-icons:discord',
4848
}
4949
50+
const socialProviderNames: Record<string, string> = {
51+
TWITTER: 'X (Twitter)',
52+
MASTODON: 'Mastodon',
53+
BLUESKY: 'Bluesky',
54+
LINKEDIN: 'LinkedIn',
55+
YOUTUBE: 'YouTube',
56+
HOMETOWN: 'Website',
57+
DISCORD: 'Discord',
58+
}
59+
5060
function getSocialIcon(provider: string): string {
5161
return socialIcons[provider] ?? 'i-lucide:link'
5262
}
5363
64+
function getSocialProviderName(provider: string): string {
65+
return socialProviderNames[provider] ?? provider.toLowerCase()
66+
}
67+
5468
function getSocialLinks(person: {
55-
twitterUsername: string | null
56-
socialAccounts: SocialAccount[]
69+
socialAccounts: { provider: string; url: string }[]
5770
}): { provider: string; url: string; icon: string }[] {
58-
const links: { provider: string; url: string; icon: string }[] = []
59-
60-
if (person.twitterUsername) {
61-
links.push({
62-
provider: 'TWITTER',
63-
url: `https://x.com/${person.twitterUsername}`,
64-
icon: socialIcons.TWITTER!,
65-
})
66-
}
67-
68-
for (const account of person.socialAccounts) {
69-
// Skip twitter if already added via twitterUsername
70-
if (account.provider === 'TWITTER') continue
71-
links.push({
72-
provider: account.provider,
73-
url: account.url,
74-
icon: getSocialIcon(account.provider),
75-
})
76-
}
77-
78-
return links
71+
return person.socialAccounts.map(account => ({
72+
provider: account.provider,
73+
url: account.url,
74+
icon: getSocialIcon(account.provider),
75+
}))
7976
}
8077
8178
const roleLabels = computed(
@@ -280,7 +277,12 @@ const roleLabels = computed(
280277
target="_blank"
281278
rel="noopener noreferrer"
282279
class="text-fg-muted hover:text-fg transition-colors"
283-
:aria-label="`${person.login} on ${link.provider.toLowerCase()}`"
280+
:aria-label="
281+
$t('about.team.social_link_aria', {
282+
name: person.login,
283+
platform: getSocialProviderName(link.provider),
284+
})
285+
"
284286
>
285287
<span :class="[link.icon, 'w-3 h-3']" aria-hidden="true" />
286288
</a>

i18n/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,8 @@
10061006
"role_steward": "steward",
10071007
"role_maintainer": "maintainer",
10081008
"sponsor": "sponsor",
1009-
"sponsor_aria": "Sponsor {name} on GitHub"
1009+
"sponsor_aria": "Sponsor {name} on GitHub",
1010+
"social_link_aria": "{name} on {platform}"
10101011
},
10111012
"contributors": {
10121013
"title": "... and {count} more contributor | ... and {count} more contributors",

server/api/contributors.get.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@ export interface GitHubContributor {
1414
role: Role
1515
sponsors_url: string | null
1616
bio: string | null
17-
twitterUsername: string | null
1817
socialAccounts: SocialAccount[]
1918
}
2019

2120
type GitHubAPIContributor = Omit<
2221
GitHubContributor,
23-
'role' | 'sponsors_url' | 'bio' | 'twitterUsername' | 'socialAccounts'
22+
'role' | 'sponsors_url' | 'bio' | 'socialAccounts'
2423
>
2524

2625
// Fallback when no GitHub token is available (e.g. preview environments).
@@ -74,13 +73,14 @@ async function fetchTeamMembers(token: string): Promise<TeamMembers | null> {
7473
interface GovernanceProfile {
7574
hasSponsorsListing: boolean
7675
bio: string | null
77-
twitterUsername: string | null
7876
socialAccounts: SocialAccount[]
7977
}
8078

8179
/**
8280
* Batch-query GitHub GraphQL API to fetch profile data for governance members.
83-
* Returns bio, social accounts, and sponsors listing status.
81+
* Returns bio, social accounts (including Twitter/X), and sponsors listing status.
82+
* Twitter/X is returned by the API inside socialAccounts, so no separate
83+
* twitterUsername field is needed.
8484
*/
8585
async function fetchGovernanceProfiles(
8686
token: string,
@@ -93,7 +93,6 @@ async function fetchGovernanceProfiles(
9393
login
9494
hasSponsorsListing
9595
bio
96-
twitterUsername
9796
socialAccounts(first: 10) { nodes { provider url } }
9897
}`,
9998
)
@@ -116,26 +115,29 @@ async function fetchGovernanceProfiles(
116115
}
117116

118117
const json = (await response.json()) as {
118+
errors?: { message: string }[]
119119
data?: Record<
120120
string,
121121
{
122122
login: string
123123
hasSponsorsListing: boolean
124124
bio: string | null
125-
twitterUsername: string | null
126125
socialAccounts: { nodes: { provider: string; url: string }[] }
127126
} | null
128127
>
129128
}
130129

130+
if (json.errors?.length) {
131+
console.warn('GraphQL errors fetching governance profiles:', json.errors)
132+
}
133+
131134
const profiles = new Map<string, GovernanceProfile>()
132135
if (json.data) {
133136
for (const user of Object.values(json.data)) {
134137
if (user) {
135138
profiles.set(user.login, {
136139
hasSponsorsListing: user.hasSponsorsListing,
137140
bio: user.bio,
138-
twitterUsername: user.twitterUsername,
139141
socialAccounts: user.socialAccounts.nodes.map(n => ({
140142
provider: n.provider,
141143
url: n.url,
@@ -220,17 +222,26 @@ export default defineCachedEventHandler(
220222
: new Map<string, GovernanceProfile>()
221223

222224
return filtered
223-
.map(c => {
225+
.map((c): GitHubContributor & { order: number } => {
224226
const { role, order } = getRoleInfo(c.login, teams)
225227
const profile = governanceProfiles.get(c.login)
226-
const sponsors_url = profile?.hasSponsorsListing
227-
? `https://github.com/sponsors/${c.login}`
228+
// Fall back to REST-derived sponsors URL so the CTA remains available
229+
// even in tokenless (preview) environments where GraphQL is skipped.
230+
const sponsors_url = profile
231+
? profile.hasSponsorsListing
232+
? `https://github.com/sponsors/${c.login}`
233+
: null
228234
: null
229235
const bio = profile?.bio ?? null
230-
const twitterUsername = profile?.twitterUsername ?? null
231236
const socialAccounts = profile?.socialAccounts ?? []
232-
Object.assign(c, { role, order, sponsors_url, bio, twitterUsername, socialAccounts })
233-
return c as GitHubContributor & { order: number }
237+
return {
238+
...c,
239+
role,
240+
order,
241+
sponsors_url,
242+
bio,
243+
socialAccounts,
244+
}
234245
})
235246
.sort((a, b) => a.order - b.order || b.contributions - a.contributions)
236247
.map(({ order: _, ...rest }) => rest)

0 commit comments

Comments
 (0)