[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add contributors + cta to about page

+195 -65
+131 -31
app/pages/about.vue
··· 1 1 <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 + 2 10 useSeoMeta({ 3 11 title: () => `${$t('about.title')} - npmx`, 4 12 description: () => $t('about.meta_description'), ··· 17 25 deno: 'https://deno.com/', 18 26 vlt: 'https://www.vlt.sh/', 19 27 } 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 + ) 20 41 </script> 21 42 22 43 <template> ··· 178 199 179 200 <div> 180 201 <h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-4"> 181 - {{ $t('about.open_source.title') }} 202 + {{ $t('about.contributors.title') }} 182 203 </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') }} 213 206 </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> 214 314 </div> 215 315 </section> 216 316
-7
i18n/locales/de.json
··· 531 531 "url_compatible_description": "Ersetze {npmjs} durch {npmx} in jeder URL und es sollte funktionieren und die gleichen Informationen mit besserer Erfahrung bieten.", 532 532 "simplicity": "Einfachheit", 533 533 "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" 541 534 } 542 535 }, 543 536 "header": {
+24 -6
i18n/locales/en.json
··· 548 548 "simplicity": "Simplicity", 549 549 "simplicity_description": "No noise, cluttered display, or confusing UI." 550 550 }, 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 + } 557 575 } 558 576 }, 559 577 "header": {
-7
i18n/locales/fr.json
··· 537 537 "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.", 538 538 "simplicity": "Simplicité", 539 539 "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" 547 540 } 548 541 }, 549 542 "header": {
-7
i18n/locales/it.json
··· 547 547 "url_compatible_description": "Sostituisci {npmjs} con {npmx} in qualsiasi URL e dovrebbe funzionare, fornendo le stesse informazioni con un'esperienza migliore.", 548 548 "simplicity": "Semplicità", 549 549 "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" 557 550 } 558 551 }, 559 552 "header": {
-7
i18n/locales/zh-CN.json
··· 547 547 "url_compatible_description": "将任何 URL 中的 {npmjs} 替换为 {npmx},它应该可以工作,提供相同的信息和更好的体验。", 548 548 "simplicity": "简洁", 549 549 "simplicity_description": "没有噪音、杂乱的显示或令人困惑的界面。" 550 - }, 551 - "open_source": { 552 - "title": "开源", 553 - "description": "npmx 完全开源。查看 {github},加入 {discord},或在 {bluesky} 上关注我们。", 554 - "github": "GitHub 上的源代码", 555 - "discord": "Discord 社区", 556 - "bluesky": "Bluesky" 557 550 } 558 551 }, 559 552 "header": {
+40
server/api/contributors.get.ts
··· 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 + )