Skip to content

Commit 307c21c

Browse files
shuuji3danielroe
andauthored
feat: sort contributors list by role (#1369)
Co-authored-by: Daniel Roe <daniel@roe.dev>
1 parent 0a2b395 commit 307c21c

File tree

7 files changed

+332
-46
lines changed

7 files changed

+332
-46
lines changed

app/pages/about.vue

Lines changed: 134 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import type { Role } from '#server/api/contributors.get'
3+
24
const router = useRouter()
35
const canGoBack = useCanGoBack()
46
@@ -27,6 +29,22 @@ const pmLinks = {
2729
}
2830
2931
const { data: contributors, status: contributorsStatus } = useLazyFetch('/api/contributors')
32+
33+
const governanceMembers = computed(
34+
() => contributors.value?.filter(c => c.role !== 'contributor') ?? [],
35+
)
36+
37+
const communityContributors = computed(
38+
() => contributors.value?.filter(c => c.role === 'contributor') ?? [],
39+
)
40+
41+
const roleLabels = computed(
42+
() =>
43+
({
44+
steward: $t('about.team.role_steward'),
45+
maintainer: $t('about.team.role_maintainer'),
46+
}) as Partial<Record<Role, string>>,
47+
)
3048
</script>
3149

3250
<template>
@@ -139,56 +157,134 @@ const { data: contributors, status: contributorsStatus } = useLazyFetch('/api/co
139157

140158
<div>
141159
<h2 class="text-lg text-fg-subtle uppercase tracking-wider mb-4">
142-
{{
143-
$t(
144-
'about.contributors.title',
145-
{ count: $n(contributors?.length ?? 0) },
146-
contributors?.length ?? 0,
147-
)
148-
}}
160+
{{ $t('about.team.title') }}
149161
</h2>
150162
<p class="text-fg-muted leading-relaxed mb-6">
151163
{{ $t('about.contributors.description') }}
152164
</p>
153165

154-
<!-- Contributors cloud -->
155-
<div v-if="contributorsStatus === 'pending'" class="text-fg-subtle text-sm">
156-
{{ $t('about.contributors.loading') }}
157-
</div>
158-
<div v-else-if="contributorsStatus === 'error'" class="text-fg-subtle text-sm">
159-
{{ $t('about.contributors.error') }}
160-
</div>
161-
<div
162-
v-else-if="contributors?.length"
163-
class="grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-2"
166+
<!-- Governance: stewards + maintainers -->
167+
<section
168+
v-if="governanceMembers.length"
169+
class="mb-12"
170+
aria-labelledby="governance-heading"
164171
>
165-
<a
166-
v-for="contributor in contributors"
167-
:key="contributor.id"
168-
:href="contributor.html_url"
169-
target="_blank"
170-
rel="noopener noreferrer"
171-
class="group relative"
172-
:aria-label="$t('about.contributors.view_profile', { name: contributor.login })"
172+
<h3
173+
id="governance-heading"
174+
class="text-sm text-fg-subtle uppercase tracking-wider mb-4"
173175
>
174-
<div class="relative flex items-center">
176+
{{ $t('about.team.governance') }}
177+
</h3>
178+
179+
<ul class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 list-none p-0">
180+
<li
181+
v-for="person in governanceMembers"
182+
:key="person.id"
183+
class="relative flex items-center gap-3 p-3 border border-border rounded-lg hover:border-border-hover hover:bg-bg-muted transition-[border-color,background-color] duration-200 cursor-pointer focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
184+
>
175185
<img
176-
:src="`${contributor.avatar_url}&s=64`"
177-
:alt="contributor.login"
178-
width="32"
179-
height="32"
180-
class="w-12 h-12 rounded-lg ring-2 ring-transparent group-hover:ring-accent transition-all duration-200 ease-out hover:scale-125 will-change-transform"
186+
:src="`${person.avatar_url}&s=80`"
187+
:alt="`${person.login}'s avatar`"
188+
class="w-12 h-12 rounded-md ring-1 ring-border shrink-0"
181189
loading="lazy"
182190
/>
191+
<div class="min-w-0 flex-1">
192+
<div class="font-mono text-sm text-fg truncate">
193+
<NuxtLink
194+
:to="person.html_url"
195+
target="_blank"
196+
class="decoration-none after:content-[''] after:absolute after:inset-0"
197+
:aria-label="$t('about.contributors.view_profile', { name: person.login })"
198+
>
199+
@{{ person.login }}
200+
</NuxtLink>
201+
</div>
202+
<div class="text-xs text-fg-muted tracking-tight">
203+
{{ roleLabels[person.role] ?? person.role }}
204+
</div>
205+
<LinkBase
206+
v-if="person.sponsors_url"
207+
:to="person.sponsors_url"
208+
no-underline
209+
no-external-icon
210+
classicon="i-carbon:favorite"
211+
class="relative z-10 text-xs text-fg-muted hover:text-pink-400 mt-0.5"
212+
:aria-label="$t('about.team.sponsor_aria', { name: person.login })"
213+
>
214+
{{ $t('about.team.sponsor') }}
215+
</LinkBase>
216+
</div>
183217
<span
184-
class="pointer-events-none absolute -top-9 inset-is-1/2 -translate-x-1/2 whitespace-nowrap rounded-md bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900 text-xs px-2 py-1 shadow-lg opacity-0 scale-95 transition-all duration-150 group-hover:opacity-100 group-hover:scale-100"
185-
dir="ltr"
218+
class="i-carbon:launch rtl-flip w-3.5 h-3.5 text-fg-muted opacity-50 shrink-0 self-start mt-0.5"
219+
aria-hidden="true"
220+
/>
221+
</li>
222+
</ul>
223+
</section>
224+
225+
<!-- Contributors cloud -->
226+
<section aria-labelledby="contributors-heading">
227+
<h3
228+
id="contributors-heading"
229+
class="text-sm text-fg-subtle uppercase tracking-wider mb-4"
230+
>
231+
{{
232+
$t(
233+
'about.contributors.title',
234+
{ count: $n(communityContributors.length) },
235+
communityContributors.length,
236+
)
237+
}}
238+
</h3>
239+
240+
<div
241+
v-if="contributorsStatus === 'pending'"
242+
class="text-fg-subtle text-sm"
243+
role="status"
244+
>
245+
{{ $t('about.contributors.loading') }}
246+
</div>
247+
<div
248+
v-else-if="contributorsStatus === 'error'"
249+
class="text-fg-subtle text-sm"
250+
role="alert"
251+
>
252+
{{ $t('about.contributors.error') }}
253+
</div>
254+
<ul
255+
v-else-if="communityContributors.length"
256+
class="grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-2 list-none p-0"
257+
>
258+
<li
259+
v-for="contributor in communityContributors"
260+
:key="contributor.id"
261+
class="group relative"
262+
>
263+
<LinkBase
264+
:to="contributor.html_url"
265+
no-underline
266+
no-external-icon
267+
:aria-label="$t('about.contributors.view_profile', { name: contributor.login })"
186268
>
187-
@{{ contributor.login }}
188-
</span>
189-
</div>
190-
</a>
191-
</div>
269+
<img
270+
:src="`${contributor.avatar_url}&s=64`"
271+
:alt="`${contributor.login}'s avatar`"
272+
width="48"
273+
height="48"
274+
class="w-12 h-12 rounded-lg ring-2 ring-transparent group-hover:ring-accent transition-all duration-200 ease-out hover:scale-125 will-change-transform"
275+
loading="lazy"
276+
/>
277+
<span
278+
class="pointer-events-none absolute -top-9 inset-is-1/2 -translate-x-1/2 whitespace-nowrap rounded-md bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900 text-xs px-2 py-1 shadow-lg opacity-0 scale-95 transition-all duration-150 group-hover:opacity-100 group-hover:scale-100"
279+
dir="ltr"
280+
role="tooltip"
281+
>
282+
@{{ contributor.login }}
283+
</span>
284+
</LinkBase>
285+
</li>
286+
</ul>
287+
</section>
192288
</div>
193289

194290
<CallToAction />

i18n/locales/en.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,8 +810,16 @@
810810
"managers": "package managers"
811811
}
812812
},
813+
"team": {
814+
"title": "Team",
815+
"governance": "Governance",
816+
"role_steward": "steward",
817+
"role_maintainer": "maintainer",
818+
"sponsor": "sponsor",
819+
"sponsor_aria": "Sponsor {name} on GitHub"
820+
},
813821
"contributors": {
814-
"title": "{count} Contributor | {count} Contributors",
822+
"title": "... and {count} more contributor | ... and {count} more contributors",
815823
"description": "npmx is fully open source, built by an amazing community of contributors. Join us and let's build the npm browsing experience we always wanted, together.",
816824
"loading": "Loading contributors...",
817825
"error": "Failed to load contributors",

i18n/schema.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,6 +2434,30 @@
24342434
},
24352435
"additionalProperties": false
24362436
},
2437+
"team": {
2438+
"type": "object",
2439+
"properties": {
2440+
"title": {
2441+
"type": "string"
2442+
},
2443+
"governance": {
2444+
"type": "string"
2445+
},
2446+
"role_steward": {
2447+
"type": "string"
2448+
},
2449+
"role_maintainer": {
2450+
"type": "string"
2451+
},
2452+
"sponsor": {
2453+
"type": "string"
2454+
},
2455+
"sponsor_aria": {
2456+
"type": "string"
2457+
}
2458+
},
2459+
"additionalProperties": false
2460+
},
24372461
"contributors": {
24382462
"type": "object",
24392463
"properties": {

lunaria/files/en-GB.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,8 +809,16 @@
809809
"managers": "package managers"
810810
}
811811
},
812+
"team": {
813+
"title": "Team",
814+
"governance": "Governance",
815+
"role_steward": "steward",
816+
"role_maintainer": "maintainer",
817+
"sponsor": "sponsor",
818+
"sponsor_aria": "Sponsor {name} on GitHub"
819+
},
812820
"contributors": {
813-
"title": "{count} Contributor | {count} Contributors",
821+
"title": "... and {count} more contributor | ... and {count} more contributors",
814822
"description": "npmx is fully open source, built by an amazing community of contributors. Join us and let's build the npm browsing experience we always wanted, together.",
815823
"loading": "Loading contributors...",
816824
"error": "Failed to load contributors",

lunaria/files/en-US.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,8 +809,16 @@
809809
"managers": "package managers"
810810
}
811811
},
812+
"team": {
813+
"title": "Team",
814+
"governance": "Governance",
815+
"role_steward": "steward",
816+
"role_maintainer": "maintainer",
817+
"sponsor": "sponsor",
818+
"sponsor_aria": "Sponsor {name} on GitHub"
819+
},
812820
"contributors": {
813-
"title": "{count} Contributor | {count} Contributors",
821+
"title": "... and {count} more contributor | ... and {count} more contributors",
814822
"description": "npmx is fully open source, built by an amazing community of contributors. Join us and let's build the npm browsing experience we always wanted, together.",
815823
"loading": "Loading contributors...",
816824
"error": "Failed to load contributors",

nuxt.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ export default defineNuxtConfig({
3434

3535
runtimeConfig: {
3636
sessionPassword: '',
37+
github: {
38+
orgToken: '',
39+
},
3740
// Upstash Redis for distributed OAuth token refresh locking in production
3841
upstash: {
3942
redisRestUrl: process.env.UPSTASH_KV_REST_API_URL || process.env.KV_REST_API_URL || '',

0 commit comments

Comments
 (0)