105 lines
7.4 KiB
JavaScript
105 lines
7.4 KiB
JavaScript
import React from 'react'
|
|
import { usePage } from '@inertiajs/react'
|
|
import StudioLayout from '../../Layouts/StudioLayout'
|
|
|
|
function MetricCard({ label, value }) {
|
|
return (
|
|
<div className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-4">
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">{label}</div>
|
|
<div className="mt-2 text-2xl font-semibold text-white">{Number(value || 0).toFixed(1)}</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function StudioGroupReputation() {
|
|
const { props } = usePage()
|
|
const reputation = props.reputation || {}
|
|
const trustSignals = Array.isArray(props.trustSignals) ? props.trustSignals : []
|
|
const metrics = props.metrics || {}
|
|
const topContributors = Array.isArray(reputation.top_contributors) ? reputation.top_contributors : []
|
|
const recentBadges = Array.isArray(reputation.recent_badges) ? reputation.recent_badges : []
|
|
const memberBadgeUnlocks = Array.isArray(reputation.member_badge_unlocks) ? reputation.member_badge_unlocks : []
|
|
|
|
return (
|
|
<StudioLayout title={props.title} subtitle={props.description}>
|
|
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-5">
|
|
<MetricCard label="Freshness" value={metrics.freshness_score} />
|
|
<MetricCard label="Activity" value={metrics.activity_score} />
|
|
<MetricCard label="Release" value={metrics.release_score} />
|
|
<MetricCard label="Trust" value={metrics.trust_score} />
|
|
<MetricCard label="Collaboration" value={metrics.collaboration_score} />
|
|
</div>
|
|
|
|
<div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]">
|
|
<section className="rounded-[28px] border border-white/10 bg-white/[0.03] p-5">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-white">Trust signals</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Public-safe labels that shape discovery and confidence.</p>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 flex flex-wrap gap-2">{trustSignals.map((signal) => <span key={signal.key} className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-2 text-sm font-semibold text-white">{signal.label}</span>)}</div>
|
|
<div className="mt-5 grid gap-3 md:grid-cols-2">
|
|
<div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-4"><div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Contributors</div><div className="mt-2 text-2xl font-semibold text-white">{Number(reputation.counts?.contributors || 0)}</div></div>
|
|
<div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-4"><div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Member badges</div><div className="mt-2 text-2xl font-semibold text-white">{Number(reputation.counts?.member_badges || 0)}</div></div>
|
|
</div>
|
|
{metrics.last_calculated_at ? <div className="mt-4 text-xs text-slate-500">Last calculated {new Date(metrics.last_calculated_at).toLocaleString()}</div> : null}
|
|
</section>
|
|
|
|
<section className="rounded-[28px] border border-white/10 bg-white/[0.03] p-5">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-white">Top contributors</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Reputation summaries derived from visible collaboration history.</p>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 space-y-3">
|
|
{topContributors.length > 0 ? topContributors.map((entry) => (
|
|
<div key={entry.user?.id} className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-4">
|
|
<div className="flex items-center gap-3">
|
|
{entry.user?.avatar_url ? <img src={entry.user.avatar_url} alt={entry.user?.name || entry.user?.username} className="h-11 w-11 rounded-2xl object-cover" /> : <div className="flex h-11 w-11 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.03] text-slate-400"><i className="fa-solid fa-user" /></div>}
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<div className="truncate font-semibold text-white">{entry.user?.name || entry.user?.username}</div>
|
|
{entry.trusted_indicator ? <span className="rounded-full border border-emerald-300/20 bg-emerald-300/10 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-emerald-100">Trusted</span> : null}
|
|
</div>
|
|
<div className="mt-1 text-sm text-slate-400">{entry.summary || 'Contributor'}</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-3 text-xs text-slate-500">{entry.counts?.releases || 0} releases • {entry.counts?.projects || 0} projects • {entry.counts?.credited_artworks || 0} artworks • {entry.counts?.review_actions || 0} reviews</div>
|
|
{Array.isArray(entry.badges) && entry.badges.length > 0 ? <div className="mt-3 flex flex-wrap gap-2">{entry.badges.map((badge) => <span key={`${entry.user?.id}-${badge.key}`} className="rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300">{badge.label}</span>)}</div> : null}
|
|
</div>
|
|
)) : <div className="rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400">No contributor reputation signals yet.</div>}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div className="mt-6 grid gap-6 xl:grid-cols-2">
|
|
<section className="rounded-[28px] border border-white/10 bg-white/[0.03] p-5">
|
|
<h2 className="text-xl font-semibold text-white">Group badges</h2>
|
|
<div className="mt-4 space-y-3">
|
|
{recentBadges.length > 0 ? recentBadges.map((badge) => (
|
|
<div key={badge.key} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-4">
|
|
<div className="font-semibold text-white">{badge.label}</div>
|
|
<div className="mt-2 text-sm text-slate-400">{badge.reason}</div>
|
|
</div>
|
|
)) : <div className="rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400">No group badges awarded yet.</div>}
|
|
</div>
|
|
</section>
|
|
|
|
<section className="rounded-[28px] border border-white/10 bg-white/[0.03] p-5">
|
|
<h2 className="text-xl font-semibold text-white">Recent member badge unlocks</h2>
|
|
<div className="mt-4 space-y-3">
|
|
{memberBadgeUnlocks.length > 0 ? memberBadgeUnlocks.map((entry) => (
|
|
<div key={`${entry.user?.id}-${entry.badge?.key}`} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-4">
|
|
<div className="font-semibold text-white">{entry.user?.name || entry.user?.username}</div>
|
|
<div className="mt-1 text-sm text-sky-200">{entry.badge?.label}</div>
|
|
<div className="mt-2 text-sm text-slate-400">{entry.badge?.reason}</div>
|
|
</div>
|
|
)) : <div className="rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400">No member badge unlocks yet.</div>}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</StudioLayout>
|
|
)
|
|
} |