108 lines
4.4 KiB
JavaScript
108 lines
4.4 KiB
JavaScript
import React, { useEffect, useState } from 'react'
|
||
import ArtworkFormatBadges from './ArtworkFormatBadges'
|
||
|
||
const NUMBER_FORMATTER = new Intl.NumberFormat('en-US')
|
||
const ABSOLUTE_DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
|
||
year: 'numeric',
|
||
month: 'short',
|
||
day: 'numeric',
|
||
timeZone: 'UTC',
|
||
})
|
||
|
||
function formatCount(value) {
|
||
const n = Number(value || 0)
|
||
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
|
||
if (n >= 1_000) return `${(n / 1_000).toFixed(1).replace(/\.0$/, '')}k`
|
||
return NUMBER_FORMATTER.format(n)
|
||
}
|
||
|
||
function formatDate(value, useRelative = true) {
|
||
if (!value) return '—'
|
||
try {
|
||
const d = new Date(value)
|
||
if (!useRelative) return ABSOLUTE_DATE_FORMATTER.format(d)
|
||
const now = Date.now()
|
||
const diff = now - d.getTime()
|
||
const days = Math.floor(diff / 86_400_000)
|
||
if (days === 0) return 'Today'
|
||
if (days === 1) return 'Yesterday'
|
||
if (days < 30) return `${days} days ago`
|
||
return ABSOLUTE_DATE_FORMATTER.format(d)
|
||
} catch {
|
||
return '—'
|
||
}
|
||
}
|
||
|
||
/* ── Stat tile shown in the 2-col grid ─────────────────────────────────── */
|
||
function StatTile({ icon, label, value }) {
|
||
return (
|
||
<div className="flex flex-col items-center gap-1.5 rounded-xl bg-white/[0.03] px-3 py-3.5">
|
||
<span className="text-white/30">{icon}</span>
|
||
<span className="text-base font-semibold tabular-nums text-white/90">{value}</span>
|
||
<span className="text-[11px] uppercase tracking-wider text-white/35">{label}</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/* ── Key-value row ─────────────────────────────────────────────────────── */
|
||
function InfoRow({ label, value }) {
|
||
return (
|
||
<div className="flex items-center justify-between py-2">
|
||
<span className="text-xs uppercase tracking-wider text-white/35">{label}</span>
|
||
<span className="text-sm font-medium text-white/80">{value}</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function ArtworkDetailsPanel({ artwork, stats }) {
|
||
const [hydrated, setHydrated] = useState(false)
|
||
const width = artwork?.dimensions?.width || artwork?.width || 0
|
||
const height = artwork?.dimensions?.height || artwork?.height || 0
|
||
const resolution = width > 0 && height > 0 ? `${NUMBER_FORMATTER.format(width)} × ${NUMBER_FORMATTER.format(height)}` : null
|
||
|
||
useEffect(() => {
|
||
setHydrated(true)
|
||
}, [])
|
||
|
||
return (
|
||
<section className="rounded-2xl border border-white/[0.06] bg-white/[0.03] p-5">
|
||
{/* Stats grid */}
|
||
<div className="grid grid-cols-2 gap-2.5">
|
||
<StatTile
|
||
icon={
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="h-4.5 w-4.5">
|
||
<path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
|
||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||
</svg>
|
||
}
|
||
label="Views"
|
||
value={formatCount(stats?.views)}
|
||
/>
|
||
<StatTile
|
||
icon={
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="h-4.5 w-4.5">
|
||
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||
</svg>
|
||
}
|
||
label="Downloads"
|
||
value={formatCount(stats?.downloads)}
|
||
/>
|
||
</div>
|
||
|
||
{/* Info rows */}
|
||
<div className="mt-4 divide-y divide-white/[0.05]">
|
||
{resolution ? (
|
||
<div className="py-2">
|
||
<div className="flex items-center justify-between gap-4">
|
||
<span className="text-xs uppercase tracking-wider text-white/35">Resolution</span>
|
||
<span className="text-sm font-medium text-white/80">{resolution}</span>
|
||
</div>
|
||
<ArtworkFormatBadges width={width} height={height} className="mt-2" />
|
||
</div>
|
||
) : null}
|
||
<InfoRow label="Uploaded" value={formatDate(artwork?.published_at, hydrated)} />
|
||
</div>
|
||
</section>
|
||
)
|
||
}
|