Skip to content

Commit ff43927

Browse files
committed
feat(project): added NPM package information acquisition function and updated project card component
1 parent 5e37bc4 commit ff43927

File tree

3 files changed

+95
-51
lines changed

3 files changed

+95
-51
lines changed

components/cards/project/index.tsx

Lines changed: 93 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import clsx from 'clsx'
4-
import { Github } from 'lucide-react'
4+
import { Github, Download } from 'lucide-react'
55
import { Fragment } from 'react'
66
import useSWR from 'swr'
77
import type { BrandsMap } from '~/components/ui/brand'
@@ -12,12 +12,22 @@ import { Image } from '~/components/ui/image'
1212
import { Link } from '~/components/ui/link'
1313
import { TiltedGridBackground } from '~/components/ui/tilted-grid-background'
1414
import type { PROJECTS } from '~/data/projects'
15-
import type { GithubRepository } from '~/types/data'
15+
import type { GithubRepository, NpmPackage } from '~/types/data'
1616
import { fetcher } from '~/utils/misc'
1717

1818
export function ProjectCard({ project }: { project: (typeof PROJECTS)[0] }) {
19-
const { title, description, imgSrc, url, repo, builtWith, links } = project
20-
const { data: repository } = useSWR<GithubRepository>(`/api/github?repo=${repo}`, fetcher)
19+
const { title, description, imgSrc, url, repo, npm, builtWith, links } = project
20+
21+
const { data: repository } = useSWR<GithubRepository>(
22+
repo && typeof repo === 'string' ? `/api/github?repo=${repo}` : null,
23+
fetcher
24+
)
25+
26+
const { data: npmPackage } = useSWR<NpmPackage>(
27+
npm && typeof npm === 'string' ? `/api/npm?package=${npm}` : null,
28+
fetcher
29+
)
30+
2131
const href = url || repository?.url
2232
const lang = repository?.languages?.[0]
2333

@@ -42,58 +52,90 @@ export function ProjectCard({ project }: { project: (typeof PROJECTS)[0] }) {
4252
</div>
4353
</div>
4454
<p className="mb-16 line-clamp-3 grow text-lg">{repository?.description || description}</p>
45-
<div
46-
className={clsx(
47-
'mt-auto flex gap-6 sm:gap-9 md:grid md:gap-0',
48-
repository ? 'grid-cols-3' : 'grid-cols-2'
49-
)}
50-
>
51-
{repository ? (
52-
<div className="space-y-1.5">
53-
<div className="text-xs text-gray-600 dark:text-gray-400">
54-
<span className="hidden sm:inline">Github stars</span>
55-
<span className="sm:hidden">Stars</span>
56-
</div>
57-
<div className="flex items-center gap-2">
58-
<div className="flex items-center space-x-1.5">
59-
<Github size={16} strokeWidth={1.5} />
60-
<span className="font-medium">{repository?.stargazerCount}</span>
55+
<div className={clsx('mt-auto flex gap-6 sm:gap-9 md:grid md:gap-0', `grid-cols-3`)}>
56+
{/* NPM Downloads */}
57+
{npmPackage
58+
? (npmPackage || (npm && typeof npm === 'object')) && (
59+
<div className="space-y-1.5">
60+
<div className="text-xs text-gray-600 dark:text-gray-400">
61+
<span className="hidden sm:inline">Monthly downloads</span>
62+
<span className="sm:hidden">Downloads</span>
63+
</div>
64+
<div className="flex items-center gap-2">
65+
<div className="flex items-center space-x-1.5">
66+
<Download size={16} strokeWidth={1.5} />
67+
<span className="font-medium">
68+
{npmPackage?.downloads ||
69+
(npm && typeof npm === 'object' ? npm.downloads : 0)}
70+
</span>
71+
</div>
72+
</div>
73+
</div>
74+
)
75+
: repository
76+
? (repository || (repo && typeof repo === 'object')) && (
77+
<div className="space-y-1.5">
78+
<div className="text-xs text-gray-600 dark:text-gray-400">
79+
<span className="hidden sm:inline">Github stars</span>
80+
<span className="sm:hidden">Stars</span>
81+
</div>
82+
<div className="flex items-center gap-2">
83+
<div className="flex items-center space-x-1.5">
84+
<Github size={16} strokeWidth={1.5} />
85+
<span className="font-medium">
86+
{repository?.stargazerCount ||
87+
(repo && typeof repo === 'object' ? repo.stargazerCount : 0)}
88+
</span>
89+
</div>
90+
</div>
91+
</div>
92+
)
93+
: null}
94+
95+
{/* Links (only show if no repo and no npm) */}
96+
{!repository &&
97+
!npmPackage &&
98+
!(repo && typeof repo === 'object') &&
99+
!(npm && typeof npm === 'object') &&
100+
links && (
101+
<div className="space-y-1.5">
102+
<div className="text-xs text-gray-600 dark:text-gray-400">Links</div>
103+
<div className="flex flex-col items-start gap-0.5 sm:flex-row sm:items-center sm:gap-1.5">
104+
{links?.map(({ title, url }, idx) => (
105+
<Fragment key={url}>
106+
<Link href={url} className="flex items-center gap-1.5">
107+
<GrowingUnderline className="font-medium" data-umami-event="project-link">
108+
{title}
109+
</GrowingUnderline>
110+
</Link>
111+
{idx !== links.length - 1 && (
112+
<span className="hidden text-gray-400 dark:text-gray-500 md:inline">|</span>
113+
)}
114+
</Fragment>
115+
))}
61116
</div>
62117
</div>
63-
</div>
64-
) : links ? (
118+
)}
119+
120+
{/* Stack */}
121+
{builtWith && builtWith.length > 0 && (
65122
<div className="space-y-1.5">
66-
<div className="text-xs text-gray-600 dark:text-gray-400">Links</div>
67-
<div className="flex flex-col items-start gap-0.5 sm:flex-row sm:items-center sm:gap-1.5">
68-
{links?.map(({ title, url }, idx) => (
69-
<Fragment key={url}>
70-
<Link href={url} className="flex items-center gap-1.5">
71-
<GrowingUnderline className="font-medium" data-umami-event="project-link">
72-
{title}
73-
</GrowingUnderline>
74-
</Link>
75-
{idx !== links.length - 1 && (
76-
<span className="hidden text-gray-400 dark:text-gray-500 md:inline">|</span>
77-
)}
78-
</Fragment>
79-
))}
123+
<div className="text-xs text-gray-600 dark:text-gray-400">Stack</div>
124+
<div className="flex h-6 flex-wrap items-center gap-1.5">
125+
{builtWith?.map((tool) => {
126+
return (
127+
<Brand
128+
key={tool}
129+
name={tool as keyof typeof BrandsMap}
130+
iconClassName={clsx(tool === 'Pygame' ? 'h-4' : 'h-4 w-4')}
131+
/>
132+
)
133+
})}
80134
</div>
81135
</div>
82-
) : null}
83-
<div className="space-y-1.5">
84-
<div className="text-xs text-gray-600 dark:text-gray-400">Stack</div>
85-
<div className="flex h-6 flex-wrap items-center gap-1.5">
86-
{builtWith?.map((tool) => {
87-
return (
88-
<Brand
89-
key={tool}
90-
name={tool as keyof typeof BrandsMap}
91-
iconClassName={clsx(tool === 'Pygame' ? 'h-4' : 'h-4 w-4')}
92-
/>
93-
)
94-
})}
95-
</div>
96-
</div>
136+
)}
137+
138+
{/* Language */}
97139
{lang && (
98140
<div className="space-y-1.5">
99141
<div className="text-xs text-gray-600 dark:text-gray-400">Language</div>

data/projects.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const PROJECTS: Project[] = [
4848
description: `A MCP server for retrieving and displaying current asset price information. This server provides tools to fetch real-time price information for various assets (including precious metals and cryptocurrencies), making it easy for large language models to access and display this data.`,
4949
imgSrc: '/static/images/projects/MCP.webp',
5050
repo: 'mk965/asset-price-mcp',
51+
npm: 'asset-price-mcp',
5152
url: 'https://www.npmjs.com/package/asset-price-mcp',
5253
builtWith: ['Typescript'],
5354
},

types/data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type Project = {
1616
imgSrc: string
1717
url?: string
1818
repo?: string | GithubRepository | null
19+
npm?: string | NpmPackage | null
1920
builtWith: string[]
2021
links?: { title: string; url: string }[]
2122
}

0 commit comments

Comments
 (0)