Skip to content

Commit 0052a8b

Browse files
committed
improve npm packages
1 parent 9a8a544 commit 0052a8b

File tree

9 files changed

+208
-132
lines changed

9 files changed

+208
-132
lines changed

apps/dashboard/src/App.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Layout from './components/Layout';
55
// Lazy load all page components for code-splitting
66
const DashboardPage = lazy(() => import('./pages/DashboardPage'));
77
const MinificationBenchmarksPage = lazy(() => import('./pages/MinificationBenchmarksPage'));
8-
const NpmDownloadsPage = lazy(() => import('./pages/NpmDownloadsPage'));
8+
const NpmPackagesPage = lazy(() => import('./pages/NpmPackagesPage'));
99
const RolldownStatsPage = lazy(() => import('./pages/RolldownStatsPage'));
1010
const DependentsPage = lazy(() => import('./pages/DependentsPage'));
1111

@@ -45,10 +45,10 @@ function App() {
4545
}
4646
/>
4747
<Route
48-
path='npm-downloads'
48+
path='npm-packages'
4949
element={
5050
<Suspense fallback={<PageLoader />}>
51-
<NpmDownloadsPage />
51+
<NpmPackagesPage />
5252
</Suspense>
5353
}
5454
/>

apps/dashboard/src/NpmDownloads.tsx

Lines changed: 0 additions & 36 deletions
This file was deleted.

apps/dashboard/src/NpmPackages.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { PackageDownloadsList } from './components/npm/PackageDownloadsList';
2+
3+
// List of npm packages to display download counts for
4+
const packages = [
5+
'vite',
6+
'vitest',
7+
'rolldown-vite',
8+
'rolldown',
9+
'tsdown',
10+
'oxlint',
11+
'oxc-parser',
12+
'oxc-transform',
13+
'oxc-minify',
14+
'oxc-resolver',
15+
];
16+
17+
function NpmPackages() {
18+
return (
19+
<>
20+
<main className='max-w-7xl mx-auto px-8 py-8'>
21+
<h2 className='mb-6 text-slate-800 dark:text-slate-100 text-3xl font-bold tracking-tight'>
22+
NPM Package Statistics
23+
</h2>
24+
25+
<PackageDownloadsList packages={packages} />
26+
</main>
27+
</>
28+
);
29+
}
30+
31+
export default NpmPackages;

apps/dashboard/src/__tests__/pages.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { MemoryRouter } from 'react-router-dom';
33
import { describe, expect, it } from 'vitest';
44
import App from '../App';
55
import MinificationBenchmarksPage from '../pages/MinificationBenchmarksPage';
6-
import NpmDownloadsPage from '../pages/NpmDownloadsPage';
6+
import NpmPackagesPage from '../pages/NpmPackagesPage';
77
import RolldownStatsPage from '../pages/RolldownStatsPage';
88

99
describe('Pages rendering tests', () => {
@@ -34,17 +34,17 @@ describe('Pages rendering tests', () => {
3434
expect(container).toBeTruthy();
3535
});
3636

37-
it('should render NpmDownloadsPage without errors', () => {
37+
it('should render NpmPackagesPage without errors', () => {
3838
const { container } = render(
3939
<MemoryRouter>
40-
<NpmDownloadsPage />
40+
<NpmPackagesPage />
4141
</MemoryRouter>,
4242
);
4343
expect(container).toBeTruthy();
4444
});
4545

4646
it('should render all routes without errors', () => {
47-
const routes = ['/', '/minification', '/npm-downloads'];
47+
const routes = ['/', '/minification', '/npm-packages'];
4848

4949
routes.forEach(route => {
5050
const { container } = render(

apps/dashboard/src/components/dashboard/StatsGrid.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ export function StatsGrid() {
4848
linkTo='/minification'
4949
/>
5050
<StatCard
51-
title='NPM Downloads'
52-
value='24.5K'
51+
title='NPM Packages'
52+
value='10'
5353
change={12}
54-
changeLabel='this week'
54+
changeLabel='tracked'
5555
icon={<Download className='w-6 h-6 text-green-600 dark:text-green-400' />}
5656
trend='up'
57-
linkTo='/npm-downloads'
57+
linkTo='/npm-packages'
5858
/>
5959
</CardGrid>
6060
);

apps/dashboard/src/components/layout/Sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ const navItems: NavItem[] = [
2727
badge: 'Benchmarks',
2828
},
2929
{
30-
path: '/npm-downloads',
31-
label: 'NPM Downloads',
30+
path: '/npm-packages',
31+
label: 'NPM Packages',
3232
icon: <Download size={20} />,
3333
},
3434
{

apps/dashboard/src/components/npm/PackageCard.tsx

Lines changed: 135 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import { Card } from '@vibe/ui';
2-
import { Download, Package } from 'lucide-react';
2+
import { Download, Package, Scale, Users, Calendar, GitBranch } from 'lucide-react';
33
import { useEffect, useState } from 'react';
44

55
interface 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+
917
function 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+
1959
export 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

Comments
 (0)