11import { Card } from '@vibe/ui' ;
2- import { Download , Package } from 'lucide-react' ;
2+ import { Download , Package , Scale , Users , Calendar , GitBranch } from 'lucide-react' ;
33import { useEffect , useState } from 'react' ;
44
55interface PackageCardProps {
66 packageName : string ;
77}
88
9+ interface PackageData {
10+ version : string ;
11+ license : string ;
12+ maintainers ?: Array < { name : string } > ;
13+ time ?: { [ key : string ] : string } ;
14+ dependencies ?: { [ key : string ] : string } ;
15+ }
16+
917function formatNumber ( num : number ) : string {
1018 if ( num >= 1000000 ) {
1119 return `${ ( num / 1000000 ) . toFixed ( 1 ) } M` ;
@@ -16,17 +24,49 @@ function formatNumber(num: number): string {
1624 return num . toLocaleString ( ) ;
1725}
1826
27+ function formatDate ( dateString : string ) : string {
28+ const date = new Date ( dateString ) ;
29+ const now = new Date ( ) ;
30+ const diffTime = Math . abs ( now . getTime ( ) - date . getTime ( ) ) ;
31+ const diffHours = Math . floor ( diffTime / ( 1000 * 60 * 60 ) ) ;
32+ const diffDays = Math . floor ( diffTime / ( 1000 * 60 * 60 * 24 ) ) ;
33+
34+ // Within 24 hours, show hours
35+ if ( diffHours === 0 ) return 'Just now' ;
36+ if ( diffHours === 1 ) return '1 hour ago' ;
37+ if ( diffHours < 24 ) return `${ diffHours } hours ago` ;
38+
39+ // Days
40+ if ( diffDays === 1 ) return 'Yesterday' ;
41+ if ( diffDays < 7 ) return `${ diffDays } days ago` ;
42+
43+ // Weeks
44+ const diffWeeks = Math . floor ( diffDays / 7 ) ;
45+ if ( diffWeeks === 1 ) return '1 week ago' ;
46+ if ( diffDays < 30 ) return `${ diffWeeks } weeks ago` ;
47+
48+ // Months
49+ const diffMonths = Math . floor ( diffDays / 30 ) ;
50+ if ( diffMonths === 1 ) return '1 month ago' ;
51+ if ( diffDays < 365 ) return `${ diffMonths } months ago` ;
52+
53+ // Years
54+ const diffYears = Math . floor ( diffDays / 365 ) ;
55+ if ( diffYears === 1 ) return '1 year ago' ;
56+ return `${ diffYears } years ago` ;
57+ }
58+
1959export function PackageCard ( { packageName } : PackageCardProps ) {
2060 const [ downloads , setDownloads ] = useState < number | null > ( null ) ;
21- const [ version , setVersion ] = useState < string | null > ( null ) ;
61+ const [ packageData , setPackageData ] = useState < PackageData | null > ( null ) ;
2262 const [ loading , setLoading ] = useState ( true ) ;
2363
2464 useEffect ( ( ) => {
2565 const fetchPackageData = async ( ) => {
2666 try {
2767 const [ downloadsRes , packageRes ] = await Promise . all ( [
2868 fetch ( `https://api.npmjs.org/downloads/point/last-week/${ packageName } ` ) ,
29- fetch ( `https://registry.npmjs.org/${ packageName } /latest ` ) ,
69+ fetch ( `https://registry.npmjs.org/${ packageName } ` ) ,
3070 ] ) ;
3171
3272 if ( downloadsRes . ok ) {
@@ -35,8 +75,18 @@ export function PackageCard({ packageName }: PackageCardProps) {
3575 }
3676
3777 if ( packageRes . ok ) {
38- const packageData = await packageRes . json ( ) ;
39- setVersion ( packageData . version ) ;
78+ const data = await packageRes . json ( ) ;
79+ // Get the latest version data
80+ const latestVersion = data [ 'dist-tags' ] ?. latest || Object . keys ( data . versions || { } ) . pop ( ) ;
81+ const latestVersionData = data . versions ?. [ latestVersion ] || { } ;
82+
83+ setPackageData ( {
84+ version : latestVersion ,
85+ license : latestVersionData . license || data . license ,
86+ maintainers : data . maintainers ,
87+ time : data . time ,
88+ dependencies : latestVersionData . dependencies
89+ } ) ;
4090 }
4191 } catch ( error ) {
4292 console . error ( `Failed to fetch data for ${ packageName } :` , error ) ;
@@ -52,6 +102,11 @@ export function PackageCard({ packageName }: PackageCardProps) {
52102 window . open ( `https://www.npmjs.com/package/${ packageName } ` , '_blank' , 'noopener,noreferrer' ) ;
53103 } ;
54104
105+ // The 'modified' field contains the last modified date
106+ const lastPublishDate = packageData ?. time ?. modified || packageData ?. time ?. [ packageData ?. version || '' ] ;
107+ const dependenciesCount = packageData ?. dependencies ? Object . keys ( packageData . dependencies ) . length : 0 ;
108+ const maintainersCount = packageData ?. maintainers ?. length || 0 ;
109+
55110 return (
56111 < Card className = 'hover:shadow-lg transition-all cursor-pointer group' >
57112 < div
@@ -73,32 +128,32 @@ export function PackageCard({ packageName }: PackageCardProps) {
73128 < Package className = 'w-5 h-5 text-slate-400 dark:text-slate-500' />
74129 </ div >
75130
76- < div className = 'space-y -3' >
131+ < div className = 'grid grid-cols-2 gap -3' >
77132 < div className = 'flex items-center gap-2' >
78- < div className = 'p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg ' >
79- < Package className = 'w-4 h-4 text-blue-600 dark:text-blue-400' />
133+ < div className = 'p-1.5 bg-blue-100 dark:bg-blue-900/30 rounded' >
134+ < Package className = 'w-3 h-3 text-blue-600 dark:text-blue-400' />
80135 </ div >
81136 < div >
82137 < p className = 'text-xs text-slate-500 dark:text-slate-400' > Version</ p >
83138 < p className = 'text-sm font-semibold text-slate-900 dark:text-white' >
84139 { loading ? (
85- < span className = 'inline-block h-4 w-16 bg-slate-200 dark:bg-slate-700 rounded animate-pulse' />
140+ < span className = 'inline-block h-4 w-12 bg-slate-200 dark:bg-slate-700 rounded animate-pulse' />
86141 ) : (
87- version || 'N/A'
142+ packageData ?. version || 'N/A'
88143 ) }
89144 </ p >
90145 </ div >
91146 </ div >
92147
93148 < div className = 'flex items-center gap-2' >
94- < div className = 'p-2 bg-green-100 dark:bg-green-900/30 rounded-lg ' >
95- < Download className = 'w-4 h-4 text-green-600 dark:text-green-400' />
149+ < div className = 'p-1.5 bg-green-100 dark:bg-green-900/30 rounded' >
150+ < Download className = 'w-3 h-3 text-green-600 dark:text-green-400' />
96151 </ div >
97152 < div >
98- < p className = 'text-xs text-slate-500 dark:text-slate-400' > Weekly Downloads </ p >
153+ < p className = 'text-xs text-slate-500 dark:text-slate-400' > Weekly</ p >
99154 < p className = 'text-sm font-semibold text-slate-900 dark:text-white' >
100155 { loading ? (
101- < span className = 'inline-block h-4 w-20 bg-slate-200 dark:bg-slate-700 rounded animate-pulse' />
156+ < span className = 'inline-block h-4 w-14 bg-slate-200 dark:bg-slate-700 rounded animate-pulse' />
102157 ) : downloads !== null ? (
103158 formatNumber ( downloads )
104159 ) : (
@@ -107,6 +162,72 @@ export function PackageCard({ packageName }: PackageCardProps) {
107162 </ p >
108163 </ div >
109164 </ div >
165+
166+ < div className = 'flex items-center gap-2' >
167+ < div className = 'p-1.5 bg-purple-100 dark:bg-purple-900/30 rounded' >
168+ < Calendar className = 'w-3 h-3 text-purple-600 dark:text-purple-400' />
169+ </ div >
170+ < div >
171+ < p className = 'text-xs text-slate-500 dark:text-slate-400' > Updated</ p >
172+ < p className = 'text-sm font-semibold text-slate-900 dark:text-white' >
173+ { loading ? (
174+ < span className = 'inline-block h-4 w-16 bg-slate-200 dark:bg-slate-700 rounded animate-pulse' />
175+ ) : lastPublishDate ? (
176+ formatDate ( lastPublishDate )
177+ ) : (
178+ 'N/A'
179+ ) }
180+ </ p >
181+ </ div >
182+ </ div >
183+
184+ < div className = 'flex items-center gap-2' >
185+ < div className = 'p-1.5 bg-amber-100 dark:bg-amber-900/30 rounded' >
186+ < Users className = 'w-3 h-3 text-amber-600 dark:text-amber-400' />
187+ </ div >
188+ < div >
189+ < p className = 'text-xs text-slate-500 dark:text-slate-400' > Maintainers</ p >
190+ < p className = 'text-sm font-semibold text-slate-900 dark:text-white' >
191+ { loading ? (
192+ < span className = 'inline-block h-4 w-8 bg-slate-200 dark:bg-slate-700 rounded animate-pulse' />
193+ ) : (
194+ maintainersCount
195+ ) }
196+ </ p >
197+ </ div >
198+ </ div >
199+
200+ < div className = 'flex items-center gap-2' >
201+ < div className = 'p-1.5 bg-indigo-100 dark:bg-indigo-900/30 rounded' >
202+ < GitBranch className = 'w-3 h-3 text-indigo-600 dark:text-indigo-400' />
203+ </ div >
204+ < div >
205+ < p className = 'text-xs text-slate-500 dark:text-slate-400' > Deps</ p >
206+ < p className = 'text-sm font-semibold text-slate-900 dark:text-white' >
207+ { loading ? (
208+ < span className = 'inline-block h-4 w-8 bg-slate-200 dark:bg-slate-700 rounded animate-pulse' />
209+ ) : (
210+ dependenciesCount
211+ ) }
212+ </ p >
213+ </ div >
214+ </ div >
215+
216+ < div className = 'flex items-center gap-2' >
217+ < div className = 'p-1.5 bg-slate-100 dark:bg-slate-800 rounded' >
218+ < Scale className = 'w-3 h-3 text-slate-600 dark:text-slate-400' />
219+ </ div >
220+ < div >
221+ < p className = 'text-xs text-slate-500 dark:text-slate-400' > License</ p >
222+ < p className = 'text-sm font-semibold text-slate-900 dark:text-white' >
223+ { loading ? (
224+ < span className = 'inline-block h-4 w-10 bg-slate-200 dark:bg-slate-700 rounded animate-pulse' />
225+ ) : (
226+ packageData ?. license || 'N/A'
227+ ) }
228+ </ p >
229+ </ div >
230+ </ div >
110231 </ div >
111232 </ div >
112233 </ Card >
0 commit comments