Skip to content

Commit a48375b

Browse files
committed
purlpage final
1 parent 427161d commit a48375b

4 files changed

Lines changed: 79 additions & 144 deletions

File tree

Lines changed: 17 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import React from 'react'
2-
import { Label, Pie, PieChart } from 'recharts'
32
import { cn } from '@/lib/utils'
4-
import { ChartContainer } from '@/components/ui/chart'
5-
import { ScoreCardCheck } from './types'
63

74
interface OverallScoreGaugeProps {
85
score: number
96
maxScore?: number
10-
checks?: ScoreCardCheck[]
117
}
128

139
function getScoreLabel(score: number): string {
@@ -18,130 +14,36 @@ function getScoreLabel(score: number): string {
1814
return 'Critical'
1915
}
2016

21-
function getScoreLabelColor(score: number): string {
22-
if (score >= 7) return 'text-green-400'
23-
if (score >= 4) return 'text-yellow-400'
24-
return 'text-red-400'
25-
}
26-
27-
function getScoreFill(score: number): string {
28-
if (score >= 7) return '#4ade80'
29-
if (score >= 4) return '#facc15'
30-
return '#f87171'
17+
function getScoreBadgeClasses(score: number): string {
18+
if (score >= 8) return 'bg-green-400/15 text-green-400 border-green-400/30'
19+
if (score >= 6) return 'bg-green-400/10 text-green-300 border-green-300/30'
20+
if (score >= 4) return 'bg-yellow-400/15 text-yellow-400 border-yellow-400/30'
21+
if (score >= 2) return 'bg-orange-400/15 text-orange-400 border-orange-400/30'
22+
return 'bg-red-400/15 text-red-400 border-red-400/30'
3123
}
3224

3325
export default function OverallScoreGauge({
3426
score,
3527
maxScore = 10,
36-
checks,
3728
}: OverallScoreGaugeProps) {
38-
const passingCount = checks?.filter((c) => c.score > 0).length ?? 0
39-
const failingCount = checks?.filter((c) => c.score === 0).length ?? 0
40-
const naCount = checks?.filter((c) => c.score < 0).length ?? 0
41-
const data = [
42-
{
43-
name: 'Score',
44-
value: score,
45-
fill: getScoreFill(score),
46-
},
47-
{
48-
name: 'Remaining',
49-
value: maxScore - score,
50-
fill: 'hsl(var(--secondary))',
51-
},
52-
]
53-
5429
return (
55-
<div className="flex items-center">
56-
<div className="flex flex-col items-center pr-5">
57-
<h3 className="mb-2 text-base font-semibold text-white">
58-
Overall Score
59-
</h3>
60-
<div className="h-[154px] w-[170px]">
61-
<ChartContainer config={{}} className="aspect-square w-full">
62-
<PieChart>
63-
<Pie
64-
data={data}
65-
startAngle={-270}
66-
dataKey="value"
67-
nameKey="name"
68-
innerRadius={42}
69-
strokeWidth={2}
70-
>
71-
<Label
72-
content={({ viewBox }) => {
73-
if (
74-
viewBox &&
75-
'cx' in viewBox &&
76-
'cy' in viewBox
77-
) {
78-
return (
79-
<text
80-
x={viewBox.cx}
81-
y={viewBox.cy}
82-
textAnchor="middle"
83-
dominantBaseline="middle"
84-
>
85-
<tspan
86-
x={viewBox.cx}
87-
y={viewBox.cy}
88-
className="fill-foreground text-3xl font-bold"
89-
>
90-
{score.toFixed(1)}
91-
</tspan>
92-
<tspan
93-
x={viewBox.cx}
94-
y={(viewBox.cy || 0) + 19}
95-
className="fill-muted-foreground text-sm"
96-
>
97-
/ {maxScore}
98-
</tspan>
99-
</text>
100-
)
101-
}
102-
}}
103-
/>
104-
</Pie>
105-
</PieChart>
106-
</ChartContainer>
107-
</div>
30+
<div className="flex items-center gap-3">
31+
<span className="text-sm text-gray-400">Overall Score</span>
32+
<span className="font-mono text-lg font-bold text-white">
33+
{score.toFixed(1)}
34+
<span className="text-sm font-normal text-gray-500">
35+
{' '}
36+
/ {maxScore}
37+
</span>
38+
</span>
10839
<span
10940
className={cn(
110-
'mt-2 text-base font-semibold',
111-
getScoreLabelColor(score),
41+
'rounded-full border px-2.5 py-0.5 text-xs font-semibold',
42+
getScoreBadgeClasses(score),
11243
)}
11344
>
11445
{getScoreLabel(score)}
11546
</span>
116-
</div>
117-
{checks && (
118-
<div className="mt-3 flex flex-col items-center gap-3">
119-
<div className="text-center">
120-
<div className="font-mono text-base font-bold text-green-400">
121-
{passingCount}
122-
</div>
123-
<div className="text-[10px] text-gray-500">
124-
passing
125-
</div>
126-
</div>
127-
<div className="w-full h-px bg-white/[0.06]" />
128-
<div className="text-center">
129-
<div className="font-mono text-base font-bold text-red-400">
130-
{failingCount}
131-
</div>
132-
<div className="text-[10px] text-gray-500">
133-
failing
134-
</div>
135-
</div>
136-
<div className="w-full h-px bg-white/[0.06]" />
137-
<div className="text-center">
138-
<div className="font-mono text-base font-bold text-gray-500">
139-
{naCount}
140-
</div>
141-
<div className="text-[10px] text-gray-500">n/a</div>
142-
</div>
143-
</div>
144-
)}
14547
</div>
14648
)
14749
}

src/components/package-inspector/ScoreCardChart.tsx

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ function getScoreLabel(score: number): string {
7676
}
7777

7878
export default function ScoreCardChart({ checks, score }: ScoreCardChartProps) {
79-
const [expanded, setExpanded] = useState(false)
79+
const [failingExpanded, setFailingExpanded] = useState(false)
80+
const [passingExpanded, setPassingExpanded] = useState(false)
8081

8182
const failing = checks.filter((c) => c.score === 0)
8283
const passing = checks
@@ -96,25 +97,19 @@ export default function ScoreCardChart({ checks, score }: ScoreCardChartProps) {
9697
Supply chain security checks
9798
</p>
9899
</div>
99-
<button
100-
onClick={() => setExpanded(!expanded)}
101-
className="rounded-lg border border-gray-700 px-4 py-2 text-sm text-gray-400 transition-colors hover:bg-white/5 hover:text-gray-200"
102-
>
103-
{expanded ? '↑ Collapse' : '↓ Show all'}
104-
</button>
105-
</div>
106-
107-
<div className="mb-5 flex justify-center">
108-
<OverallScoreGauge score={score} checks={checks} />
100+
<OverallScoreGauge score={score} />
109101
</div>
110102

111103
{/* Failing */}
112104
{failing.length > 0 && (
113105
<div className="mb-4">
114-
<div className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-500">
115-
● Failing
116-
</div>
117-
{!expanded ? (
106+
<button
107+
onClick={() => setFailingExpanded(!failingExpanded)}
108+
className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-500 transition-colors hover:text-gray-300"
109+
>
110+
{failingExpanded ? '▾' : '▸'} Failing ({failing.length})
111+
</button>
112+
{!failingExpanded ? (
118113
<div className="flex flex-wrap gap-1.5">
119114
{failing.map((check) => (
120115
<Tooltip key={check.name}>
@@ -177,10 +172,13 @@ export default function ScoreCardChart({ checks, score }: ScoreCardChartProps) {
177172

178173
{/* Passing + N/A */}
179174
<div>
180-
<div className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-500">
181-
● Passing
182-
</div>
183-
{!expanded ? (
175+
<button
176+
onClick={() => setPassingExpanded(!passingExpanded)}
177+
className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-500 transition-colors hover:text-gray-300"
178+
>
179+
{passingExpanded ? '▾' : '▸'} Passing ({passing.length + na.length})
180+
</button>
181+
{!passingExpanded ? (
184182
<div className="flex flex-wrap gap-1.5">
185183
{[...passing, ...na].map((check) => (
186184
<Tooltip key={check.name}>

src/components/package-inspector/VulnerabilityList.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ export default function VulnerabilityList({
5050
return bScore - aScore
5151
})
5252

53-
const visible = expanded ? sortedVulns : sortedVulns.slice(0, 7)
54-
const hasMore = sortedVulns.length > 7
53+
const visible = expanded ? sortedVulns : sortedVulns.slice(0, 3)
54+
const hasMore = sortedVulns.length > 3
5555

5656
return (
5757
<div>
@@ -108,7 +108,7 @@ export default function VulnerabilityList({
108108
className="mt-3 flex w-full items-center justify-center gap-1 rounded-md border border-gray-700 py-1.5 text-sm text-gray-400 hover:bg-gray-800"
109109
>
110110
<ChevronDown className="h-4 w-4" />
111-
{vulns.length - 7} more
111+
{vulns.length - 3} more
112112
</button>
113113
)}
114114
</div>

src/components/package-inspector/purlPage.tsx

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import PackageHeroCard from '@/components/package-inspector/PackageHeroCard'
99
import ScoreCardChart from '@/components/package-inspector/ScoreCardChart'
1010
import VulnerabilityList from '@/components/package-inspector/VulnerabilityList'
1111
import { Button } from '@/components/ui/button'
12+
import { Skeleton } from '@/components/ui/skeleton'
1213
import { Container } from '../ui/container'
1314

1415
export default function PurlPageComponent({ purl }: { purl?: string }) {
@@ -29,14 +30,48 @@ export default function PurlPageComponent({ purl }: { purl?: string }) {
2930

3031
if (isLoading) {
3132
return (
32-
<div className="flex min-h-screen items-center justify-center bg-gray-900">
33-
<div className="flex flex-col items-center gap-4">
34-
<div className="h-12 w-12 animate-spin rounded-full border-4 border-yellow-500 border-t-transparent"></div>
35-
<p className="text-gray-600">
36-
Loading package information...
37-
</p>
33+
<Container showTopGrid={false} showBottomGrid={false} className="py-5">
34+
<div className="mb-6">
35+
<Skeleton className="mb-1 h-4 w-32" />
3836
</div>
39-
</div>
37+
38+
{/* Hero card skeleton */}
39+
<div className="mb-4 rounded-xl border border-gray-700 bg-gray-800/50 p-6">
40+
<div className="flex items-center gap-4">
41+
<Skeleton className="h-12 w-12 rounded-full" />
42+
<div className="flex-1 space-y-2">
43+
<Skeleton className="h-6 w-48" />
44+
<Skeleton className="h-4 w-32" />
45+
</div>
46+
</div>
47+
<div className="mt-4 space-y-2">
48+
<Skeleton className="h-4 w-full" />
49+
<Skeleton className="h-4 w-3/4" />
50+
</div>
51+
</div>
52+
53+
{/* Two-column skeleton */}
54+
<div className="grid items-stretch gap-4 md:grid-cols-2">
55+
<div className="rounded-xl border border-gray-700 bg-gray-800/50 p-6">
56+
<Skeleton className="mb-4 h-5 w-24" />
57+
<Skeleton className="h-48 w-full rounded-lg" />
58+
</div>
59+
<div className="rounded-xl border border-gray-700 bg-gray-800/50 p-6">
60+
<Skeleton className="mb-4 h-5 w-40" />
61+
<div className="space-y-3">
62+
<Skeleton className="h-10 w-full" />
63+
<Skeleton className="h-10 w-full" />
64+
<Skeleton className="h-10 w-full" />
65+
</div>
66+
</div>
67+
</div>
68+
69+
{/* Bottom buttons skeleton */}
70+
<div className="mt-6 flex justify-end gap-3">
71+
<Skeleton className="h-10 w-24 rounded-md" />
72+
<Skeleton className="h-10 w-32 rounded-md" />
73+
</div>
74+
</Container>
4075
)
4176
}
4277

0 commit comments

Comments
 (0)