Skip to content

Commit 42a17ba

Browse files
committed
feat: add contributors + cta to about page
1 parent 08ff6a7 commit 42a17ba

7 files changed

Lines changed: 195 additions & 65 deletions

File tree

app/pages/about.vue

Lines changed: 131 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
<script setup lang="ts">
2+
interface GitHubContributor {
3+
login: string
4+
id: number
5+
avatar_url: string
6+
html_url: string
7+
contributions: number
8+
}
9+
210
useSeoMeta({
311
title: () => `${$t('about.title')} - npmx`,
412
description: () => $t('about.meta_description'),
@@ -17,6 +25,19 @@ const pmLinks = {
1725
deno: 'https://deno.com/',
1826
vlt: 'https://www.vlt.sh/',
1927
}
28+
29+
const socialLinks = {
30+
github: 'https://repo.npmx.dev',
31+
discord: 'https://chat.npmx.dev',
32+
bluesky: 'https://social.npmx.dev',
33+
}
34+
35+
const { data: contributors, status: contributorsStatus } = useFetch<GitHubContributor[]>(
36+
'/api/contributors',
37+
{
38+
lazy: true,
39+
},
40+
)
2041
</script>
2142

2243
<template>
@@ -178,39 +199,118 @@ const pmLinks = {
178199

179200
<div>
180201
<h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-4">
181-
{{ $t('about.open_source.title') }}
202+
{{ $t('about.contributors.title') }}
182203
</h2>
183-
<p class="text-fg-muted leading-relaxed">
184-
<i18n-t keypath="about.open_source.description" tag="span">
185-
<template #github>
186-
<a
187-
href="https://repo.npmx.dev"
188-
target="_blank"
189-
rel="noopener noreferrer"
190-
class="link text-fg"
191-
>{{ $t('about.open_source.github') }}</a
192-
>
193-
</template>
194-
<template #discord>
195-
<a
196-
href="https://chat.npmx.dev"
197-
target="_blank"
198-
rel="noopener noreferrer"
199-
class="link text-fg"
200-
>{{ $t('about.open_source.discord') }}</a
201-
>
202-
</template>
203-
<template #bluesky>
204-
<a
205-
href="https://social.npmx.dev"
206-
target="_blank"
207-
rel="noopener noreferrer"
208-
class="link text-fg"
209-
>{{ $t('about.open_source.bluesky') }}</a
210-
>
211-
</template>
212-
</i18n-t>
204+
<p class="text-fg-muted leading-relaxed mb-6">
205+
{{ $t('about.contributors.description') }}
213206
</p>
207+
208+
<!-- Contributors cloud -->
209+
<div v-if="contributorsStatus === 'pending'" class="text-fg-subtle text-sm">
210+
{{ $t('about.contributors.loading') }}
211+
</div>
212+
<div v-else-if="contributorsStatus === 'error'" class="text-fg-subtle text-sm">
213+
{{ $t('about.contributors.error') }}
214+
</div>
215+
<div v-else-if="contributors?.length" class="flex flex-wrap gap-2">
216+
<a
217+
v-for="contributor in contributors"
218+
:key="contributor.id"
219+
:href="contributor.html_url"
220+
target="_blank"
221+
rel="noopener noreferrer"
222+
class="group relative"
223+
:title="$t('about.contributors.view_profile', { name: contributor.login })"
224+
>
225+
<img
226+
:src="`${contributor.avatar_url}&s=64`"
227+
:alt="contributor.login"
228+
width="32"
229+
height="32"
230+
class="w-8 h-8 rounded-full ring-2 ring-transparent group-hover:ring-accent transition-all duration-200"
231+
loading="lazy"
232+
/>
233+
</a>
234+
</div>
235+
</div>
236+
237+
<!-- Get Involved CTAs -->
238+
<div>
239+
<h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-6">
240+
{{ $t('about.get_involved.title') }}
241+
</h2>
242+
243+
<div class="grid gap-4 sm:grid-cols-3">
244+
<!-- Contribute CTA -->
245+
<a
246+
:href="socialLinks.github"
247+
target="_blank"
248+
rel="noopener noreferrer"
249+
class="group flex flex-col gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200"
250+
>
251+
<div class="flex items-center gap-2">
252+
<span class="i-carbon-logo-github w-5 h-5 text-fg" aria-hidden="true" />
253+
<span class="font-medium text-fg">{{
254+
$t('about.get_involved.contribute.title')
255+
}}</span>
256+
</div>
257+
<p class="text-sm text-fg-muted leading-relaxed">
258+
{{ $t('about.get_involved.contribute.description') }}
259+
</p>
260+
<span
261+
class="text-sm text-fg-muted group-hover:text-fg inline-flex items-center gap-1 mt-auto"
262+
>
263+
{{ $t('about.get_involved.contribute.cta') }}
264+
<span class="i-carbon-arrow-right w-3 h-3" aria-hidden="true" />
265+
</span>
266+
</a>
267+
268+
<!-- Community CTA -->
269+
<a
270+
:href="socialLinks.discord"
271+
target="_blank"
272+
rel="noopener noreferrer"
273+
class="group flex flex-col gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200"
274+
>
275+
<div class="flex items-center gap-2">
276+
<span class="i-carbon-chat w-5 h-5 text-fg" aria-hidden="true" />
277+
<span class="font-medium text-fg">{{
278+
$t('about.get_involved.community.title')
279+
}}</span>
280+
</div>
281+
<p class="text-sm text-fg-muted leading-relaxed">
282+
{{ $t('about.get_involved.community.description') }}
283+
</p>
284+
<span
285+
class="text-sm text-fg-muted group-hover:text-fg inline-flex items-center gap-1 mt-auto"
286+
>
287+
{{ $t('about.get_involved.community.cta') }}
288+
<span class="i-carbon-arrow-right w-3 h-3" aria-hidden="true" />
289+
</span>
290+
</a>
291+
292+
<!-- Follow CTA -->
293+
<a
294+
:href="socialLinks.bluesky"
295+
target="_blank"
296+
rel="noopener noreferrer"
297+
class="group flex flex-col gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200"
298+
>
299+
<div class="flex items-center gap-2">
300+
<span class="i-simple-icons-bluesky w-5 h-5 text-fg" aria-hidden="true" />
301+
<span class="font-medium text-fg">{{ $t('about.get_involved.follow.title') }}</span>
302+
</div>
303+
<p class="text-sm text-fg-muted leading-relaxed">
304+
{{ $t('about.get_involved.follow.description') }}
305+
</p>
306+
<span
307+
class="text-sm text-fg-muted group-hover:text-fg inline-flex items-center gap-1 mt-auto"
308+
>
309+
{{ $t('about.get_involved.follow.cta') }}
310+
<span class="i-carbon-arrow-right w-3 h-3" aria-hidden="true" />
311+
</span>
312+
</a>
313+
</div>
214314
</div>
215315
</section>
216316

i18n/locales/de.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -531,13 +531,6 @@
531531
"url_compatible_description": "Ersetze {npmjs} durch {npmx} in jeder URL und es sollte funktionieren und die gleichen Informationen mit besserer Erfahrung bieten.",
532532
"simplicity": "Einfachheit",
533533
"simplicity_description": "Kein Rauschen, überladene Anzeige oder verwirrende UI."
534-
},
535-
"open_source": {
536-
"title": "Open Source",
537-
"description": "npmx ist vollständig Open Source. Schau dir den {github} an, tritt dem {discord} bei oder folge uns auf {bluesky}.",
538-
"github": "Quellcode auf GitHub",
539-
"discord": "Community auf Discord",
540-
"bluesky": "Bluesky"
541534
}
542535
},
543536
"header": {

i18n/locales/en.json

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -548,12 +548,30 @@
548548
"simplicity": "Simplicity",
549549
"simplicity_description": "No noise, cluttered display, or confusing UI."
550550
},
551-
"open_source": {
552-
"title": "Open source",
553-
"description": "npmx is fully open source. Check out the {github}, join the {discord}, or follow us on {bluesky}.",
554-
"github": "source code on GitHub",
555-
"discord": "community on Discord",
556-
"bluesky": "Bluesky"
551+
"contributors": {
552+
"title": "Contributors",
553+
"description": "npmx is built by an amazing community of contributors.",
554+
"loading": "Loading contributors...",
555+
"error": "Failed to load contributors",
556+
"view_profile": "View {name}'s GitHub profile"
557+
},
558+
"get_involved": {
559+
"title": "Get involved",
560+
"contribute": {
561+
"title": "Contribute",
562+
"description": "Help us build a better npm experience. Check out our GitHub repo to get started.",
563+
"cta": "View on GitHub"
564+
},
565+
"community": {
566+
"title": "Join the community",
567+
"description": "Chat with other developers, ask questions, and share ideas on Discord.",
568+
"cta": "Join Discord"
569+
},
570+
"follow": {
571+
"title": "Stay updated",
572+
"description": "Follow us on Bluesky for the latest news, updates, and tips.",
573+
"cta": "Follow on Bluesky"
574+
}
557575
}
558576
},
559577
"header": {

i18n/locales/fr.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -537,13 +537,6 @@
537537
"url_compatible_description": "Remplacez {npmjs} par {npmx} dans n'importe quelle URL et cela devrait fonctionner, fournissant les mêmes informations avec une meilleure expérience.",
538538
"simplicity": "Simplicité",
539539
"simplicity_description": "Pas de bruit, d'affichage encombré ou d'interface confuse."
540-
},
541-
"open_source": {
542-
"title": "Open source",
543-
"description": "npmx est entièrement open source. Consultez le {github}, rejoignez la {discord}, ou suivez-nous sur {bluesky}.",
544-
"github": "code source sur GitHub",
545-
"discord": "communauté sur Discord",
546-
"bluesky": "Bluesky"
547540
}
548541
},
549542
"header": {

i18n/locales/it.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -547,13 +547,6 @@
547547
"url_compatible_description": "Sostituisci {npmjs} con {npmx} in qualsiasi URL e dovrebbe funzionare, fornendo le stesse informazioni con un'esperienza migliore.",
548548
"simplicity": "Semplicità",
549549
"simplicity_description": "Niente rumore, display disordinato o interfaccia confusa."
550-
},
551-
"open_source": {
552-
"title": "Open source",
553-
"description": "npmx è completamente open source. Dai un'occhiata al {github}, unisciti alla {discord}, o seguici su {bluesky}.",
554-
"github": "codice sorgente su GitHub",
555-
"discord": "community su Discord",
556-
"bluesky": "Bluesky"
557550
}
558551
},
559552
"header": {

i18n/locales/zh-CN.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -547,13 +547,6 @@
547547
"url_compatible_description": "将任何 URL 中的 {npmjs} 替换为 {npmx},它应该可以工作,提供相同的信息和更好的体验。",
548548
"simplicity": "简洁",
549549
"simplicity_description": "没有噪音、杂乱的显示或令人困惑的界面。"
550-
},
551-
"open_source": {
552-
"title": "开源",
553-
"description": "npmx 完全开源。查看 {github},加入 {discord},或在 {bluesky} 上关注我们。",
554-
"github": "GitHub 上的源代码",
555-
"discord": "Discord 社区",
556-
"bluesky": "Bluesky"
557550
}
558551
},
559552
"header": {

server/api/contributors.get.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { H3Event } from 'h3'
2+
3+
export interface GitHubContributor {
4+
login: string
5+
id: number
6+
avatar_url: string
7+
html_url: string
8+
contributions: number
9+
}
10+
11+
export default defineCachedEventHandler(
12+
async (_event: H3Event): Promise<GitHubContributor[]> => {
13+
const response = await fetch(
14+
'https://api.github.com/repos/npmx-dev/npmx.dev/contributors?per_page=50',
15+
{
16+
headers: {
17+
'Accept': 'application/vnd.github.v3+json',
18+
'User-Agent': 'npmx.dev',
19+
},
20+
},
21+
)
22+
23+
if (!response.ok) {
24+
throw createError({
25+
statusCode: response.status,
26+
message: 'Failed to fetch contributors',
27+
})
28+
}
29+
30+
const contributors = (await response.json()) as GitHubContributor[]
31+
32+
// Filter out bots
33+
return contributors.filter(c => !c.login.includes('[bot]'))
34+
},
35+
{
36+
maxAge: 3600, // Cache for 1 hour
37+
name: 'github-contributors',
38+
getKey: () => 'contributors',
39+
},
40+
)

0 commit comments

Comments
 (0)