Implement creator studio and upload updates

This commit is contained in:
2026-04-04 10:12:02 +02:00
parent 1da7d3bf88
commit 0b216b7ecd
15107 changed files with 31206 additions and 626514 deletions

View File

@@ -1,141 +1,536 @@
import React from 'react'
import { usePage, Link } from '@inertiajs/react'
import { usePage } from '@inertiajs/react'
import StudioLayout from '../../Layouts/StudioLayout'
import { studioSurface, trackStudioEvent } from '../../utils/studioEvents'
const kpiConfig = [
{ key: 'total_artworks', label: 'Total Artworks', icon: 'fa-images', color: 'text-blue-400', link: '/studio/artworks' },
{ key: 'views_30d', label: 'Views (30d)', icon: 'fa-eye', color: 'text-emerald-400', link: null },
{ key: 'favourites_30d', label: 'Favourites (30d)', icon: 'fa-heart', color: 'text-pink-400', link: null },
{ key: 'shares_30d', label: 'Shares (30d)', icon: 'fa-share-nodes', color: 'text-amber-400', link: null },
{ key: 'followers', label: 'Followers', icon: 'fa-user-group', color: 'text-purple-400', link: null },
{ key: 'total_content', label: 'Total content', icon: 'fa-solid fa-table-cells-large' },
{ key: 'views_30d', label: 'Views', icon: 'fa-solid fa-eye' },
{ key: 'appreciation_30d', label: 'Reactions', icon: 'fa-solid fa-heart' },
{ key: 'shares_30d', label: 'Shares / Saves', icon: 'fa-solid fa-share-nodes' },
{ key: 'comments_30d', label: 'Comments', icon: 'fa-solid fa-comments' },
{ key: 'followers', label: 'Followers', icon: 'fa-solid fa-user-group' },
]
function KpiCard({ config, value }) {
const content = (
<div className="bg-nova-900/60 border border-white/10 rounded-2xl p-5 hover:border-white/20 hover:shadow-lg hover:shadow-accent/5 transition-all duration-300 cursor-pointer group">
<div className="flex items-center gap-3 mb-3">
<div className={`w-10 h-10 rounded-xl bg-white/5 flex items-center justify-center ${config.color} group-hover:scale-110 transition-transform`}>
<i className={`fa-solid ${config.icon}`} />
return (
<div className="rounded-[26px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_18px_40px_rgba(2,6,23,0.18)]">
<div className="flex items-center gap-3 text-slate-300">
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-sky-300/10 text-sky-100">
<i className={config.icon} />
</div>
<span className="text-xs font-medium text-slate-400 uppercase tracking-wider">{config.label}</span>
<span className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500">{config.label}</span>
</div>
<p className="text-3xl font-bold text-white tabular-nums">
{typeof value === 'number' ? value.toLocaleString() : value}
</p>
<p className="mt-4 text-3xl font-semibold text-white tabular-nums">{typeof value === 'number' ? value.toLocaleString() : value}</p>
</div>
)
if (config.link) {
return <Link href={config.link}>{content}</Link>
}
return content
}
function TopPerformerCard({ artwork }) {
function QuickCreateCard({ item }) {
return (
<div className="bg-nova-900/60 border border-white/10 rounded-2xl p-4 hover:border-white/20 hover:shadow-lg hover:shadow-accent/5 transition-all duration-300 group">
<div className="flex items-start gap-3">
{artwork.thumb_url && (
<img
src={artwork.thumb_url}
alt={artwork.title}
className="w-16 h-16 rounded-xl object-cover bg-nova-800 flex-shrink-0 group-hover:scale-105 transition-transform"
loading="lazy"
/>
)}
<div className="min-w-0 flex-1">
<h4 className="text-sm font-semibold text-white truncate" title={artwork.title}>
{artwork.title}
</h4>
<div className="flex flex-wrap items-center gap-3 mt-1.5">
<span className="text-xs text-slate-400">
{artwork.favourites?.toLocaleString()}
</span>
<span className="text-xs text-slate-400">
🔗 {artwork.shares?.toLocaleString()}
</span>
</div>
{artwork.heat_score > 5 && (
<span className="inline-flex items-center gap-1 mt-2 px-2 py-0.5 rounded-md text-[10px] font-medium bg-orange-500/20 text-orange-400 border border-orange-500/30">
<i className="fa-solid fa-fire" /> Rising
</span>
)}
<a
href={item.url}
onClick={() => trackStudioEvent('studio_quick_create_used', {
surface: studioSurface(),
module: item.key,
meta: {
href: item.url,
label: item.label,
},
})}
className="rounded-[24px] border border-sky-300/20 bg-sky-300/10 p-4 text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15"
>
<div className="flex items-center gap-3">
<i className={item.icon} />
<span className="text-sm font-semibold">New {item.label}</span>
</div>
<p className="mt-3 text-sm leading-6 text-sky-100/80">Jump straight into the dedicated {item.label.toLowerCase()} creation workflow.</p>
</a>
)
}
function RecentPublishCard({ item }) {
return (
<a href={item.edit_url || item.manage_url} className="block rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/70">{item.module_label}</p>
<h3 className="mt-2 text-base font-semibold text-white">{item.title}</h3>
<p className="mt-1 text-sm text-slate-400">Published {new Date(item.published_at || item.updated_at).toLocaleDateString()}</p>
</a>
)
}
function ContinueWorkingCard({ item }) {
return (
<a href={item.edit_url || item.manage_url} className="block rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/70">{item.module_label}</p>
<h3 className="mt-2 text-base font-semibold text-white">{item.title}</h3>
<p className="mt-1 text-sm text-slate-400">Updated {new Date(item.updated_at || item.created_at).toLocaleDateString()}</p>
</a>
)
}
function ScheduledItemCard({ item }) {
return (
<a href={item.edit_url || item.manage_url} className="block rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/70">{item.module_label}</p>
<h3 className="mt-2 text-base font-semibold text-white">{item.title}</h3>
<p className="mt-1 text-sm text-slate-400">Scheduled {new Date(item.scheduled_at || item.published_at).toLocaleString()}</p>
</a>
)
}
function ActivityRow({ item }) {
return (
<a href={item.url} className="block rounded-[20px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
<div className="flex items-center justify-between gap-3">
<p className="text-sm font-semibold text-white">{item.title}</p>
<span className="text-[11px] uppercase tracking-[0.18em] text-slate-500">{item.module_label}</span>
</div>
<p className="mt-2 text-sm text-slate-400 line-clamp-2">{item.body}</p>
</a>
)
}
function GrowthHint({ item }) {
return (
<a href={item.url} className="block rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
<h3 className="text-base font-semibold text-white">{item.title}</h3>
<p className="mt-2 text-sm leading-6 text-slate-400">{item.body}</p>
<span className="mt-4 inline-flex items-center gap-2 text-sm font-medium text-sky-100">
{item.label}
<i className="fa-solid fa-arrow-right" />
</span>
</a>
)
}
function ChallengeWidget({ challenge }) {
return (
<a href={challenge.url} className="block rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/70">{challenge.official ? 'Official challenge' : 'Community challenge'}</p>
<h3 className="mt-2 text-base font-semibold text-white">{challenge.title}</h3>
</div>
<span className="text-[10px] uppercase tracking-[0.16em] text-slate-500">{challenge.status}</span>
</div>
<p className="mt-2 text-sm leading-6 text-slate-400 line-clamp-3">{challenge.prompt || challenge.description}</p>
<div className="mt-3 flex flex-wrap gap-3 text-xs text-slate-500">
<span>{Number(challenge.entries_count || 0).toLocaleString()} entries</span>
<span>{challenge.is_joined ? `${challenge.submission_count} submitted` : 'Not joined'}</span>
</div>
</a>
)
}
function FeaturedStatusCard({ item }) {
return (
<a href={item.edit_url || item.manage_url || item.view_url} className="block rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/70">{item.module_label}</p>
<h3 className="mt-2 text-base font-semibold text-white">{item.title}</h3>
<p className="mt-1 text-sm text-slate-400">{item.subtitle || item.visibility || 'Selected for profile presentation'}</p>
</a>
)
}
function CommandCenterColumn({ title, items = [], empty, renderItem }) {
return (
<div>
<h3 className="text-sm font-semibold uppercase tracking-[0.18em] text-slate-500">{title}</h3>
<div className="mt-3 space-y-3">
{items.length > 0 ? items.map(renderItem) : <div className="rounded-2xl border border-dashed border-white/10 px-4 py-6 text-sm text-slate-500">{empty}</div>}
</div>
</div>
)
}
function InsightBlock({ item }) {
const toneClasses = {
positive: 'border-emerald-400/20 bg-emerald-400/10 text-emerald-100',
warning: 'border-amber-400/20 bg-amber-400/10 text-amber-100',
action: 'border-sky-400/20 bg-sky-400/10 text-sky-100',
neutral: 'border-white/10 bg-white/[0.03] text-slate-200',
}
return (
<a
href={item.href}
onClick={() => trackStudioEvent('studio_insight_clicked', {
surface: studioSurface(),
module: 'overview',
meta: {
insight_key: item.key,
href: item.href,
},
})}
className={`block rounded-[24px] border p-4 transition hover:border-white/20 ${toneClasses[item.tone] || toneClasses.neutral}`}
>
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-2xl border border-white/10 bg-black/20">
<i className={item.icon} />
</div>
<div className="min-w-0">
<h3 className="text-base font-semibold text-white">{item.title}</h3>
<p className="mt-2 text-sm leading-6 text-slate-300">{item.body}</p>
<span className="mt-3 inline-flex items-center gap-2 text-sm font-medium text-inherit">
{item.cta}
<i className="fa-solid fa-arrow-right" />
</span>
</div>
</div>
</a>
)
}
function TopPerformerCard({ item }) {
return (
<article className="rounded-[26px] border border-white/10 bg-white/[0.03] p-4 transition hover:border-white/20">
<div className="flex items-start gap-3">
{item.image_url && (
<img src={item.image_url} alt={item.title} className="h-16 w-16 flex-shrink-0 rounded-2xl object-cover" loading="lazy" />
)}
<div className="min-w-0 flex-1">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/70">{item.module_label}</p>
<h4 className="mt-1 truncate text-base font-semibold text-white" title={item.title}>{item.title}</h4>
<p className="mt-1 text-sm text-slate-400">{item.subtitle || item.visibility}</p>
<div className="mt-3 flex flex-wrap items-center gap-3 text-xs text-slate-400">
<span>{Number(item.metrics?.views || 0).toLocaleString()} views</span>
<span>{Number(item.metrics?.appreciation || 0).toLocaleString()} reactions</span>
<span>{Number(item.metrics?.comments || 0).toLocaleString()} comments</span>
</div>
</div>
</div>
<div className="mt-4 flex gap-2">
<a href={item.edit_url || item.manage_url} className="inline-flex items-center gap-2 rounded-full border border-white/10 px-3 py-1.5 text-xs text-slate-100">Edit</a>
<a href={item.analytics_url} className="inline-flex items-center gap-2 rounded-full border border-white/10 px-3 py-1.5 text-xs text-slate-100">Analytics</a>
</div>
</article>
)
}
function RecentComment({ comment }) {
return (
<div className="flex items-start gap-3 py-3 border-b border-white/5 last:border-0">
<div className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center text-xs text-slate-400 flex-shrink-0">
<i className="fa-solid fa-comment" />
</div>
<div className="min-w-0 flex-1">
<p className="text-sm text-white">
<span className="font-medium text-accent">{comment.author_name}</span>
{' '}on{' '}
<span className="text-slate-300">{comment.artwork_title}</span>
</p>
<p className="text-xs text-slate-500 mt-0.5 line-clamp-2">{comment.body}</p>
<p className="text-[10px] text-slate-600 mt-1">
{new Date(comment.created_at).toLocaleDateString()}
</p>
</div>
<div className="border-b border-white/5 py-3 last:border-0">
<p className="text-sm text-white">
<span className="font-medium text-sky-100">{comment.author_name}</span>
{' '}on{' '}
<span className="text-slate-300">{comment.item_title}</span>
</p>
<p className="mt-1 text-xs text-slate-500 line-clamp-2">{comment.body}</p>
<p className="mt-1 text-[10px] text-slate-600">{new Date(comment.created_at).toLocaleDateString()}</p>
</div>
)
}
export default function StudioDashboard() {
const { props } = usePage()
const { kpis, topPerformers, recentComments } = props
const overview = props.overview || {}
const analytics = props.analytics || {}
const kpis = overview.kpis || {}
const widgetVisibility = overview.preferences?.widget_visibility || {}
const showWidget = (key) => widgetVisibility[key] !== false
return (
<StudioLayout title="Studio Overview">
{/* KPI Cards */}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4 mb-8">
<StudioLayout title="Overview" subtitle="Create, manage, and grow across artworks, cards, collections, and stories from one shared creator operating surface.">
<div className="grid grid-cols-2 gap-4 lg:grid-cols-5">
{kpiConfig.map((config) => (
<KpiCard key={config.key} config={config} value={kpis?.[config.key] ?? 0} />
))}
</div>
{/* Top Performers */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold text-white">Your Top Performers</h2>
<span className="text-xs text-slate-500">Last 7 days</span>
</div>
{topPerformers?.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{topPerformers.map((art) => (
<TopPerformerCard key={art.id} artwork={art} />
<div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
<section className="rounded-[30px] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.14),_transparent_35%),linear-gradient(135deg,_rgba(15,23,42,0.86),_rgba(2,6,23,0.96))] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Command center</h2>
<a href="/studio/calendar" className="text-sm font-medium text-sky-100">Open calendar</a>
</div>
<div className="mt-5 grid gap-5 lg:grid-cols-3">
<CommandCenterColumn
title="Publishing today"
items={overview.command_center?.publishing_today || []}
empty="Nothing is scheduled today."
renderItem={(item) => <ScheduledItemCard key={item.id} item={item} />}
/>
<CommandCenterColumn
title="Attention now"
items={overview.command_center?.attention_now || []}
empty="Inbox is quiet right now."
renderItem={(item) => <ActivityRow key={item.id} item={item} />}
/>
<CommandCenterColumn
title="Ready to schedule"
items={overview.workflow_focus?.ready_to_schedule || []}
empty="No ready drafts yet."
renderItem={(item) => <ContinueWorkingCard key={item.id} item={item} />}
/>
</div>
</section>
<section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<h2 className="text-lg font-semibold text-white">Readable insights</h2>
<div className="mt-4 space-y-3">
{(overview.insight_blocks || []).map((item) => (
<InsightBlock key={item.key} item={item} />
))}
</div>
) : (
<div className="bg-nova-900/40 border border-white/10 rounded-2xl p-8 text-center">
<p className="text-slate-500 text-sm">No artworks yet. Upload your first creation!</p>
<Link
href="/upload"
className="inline-flex items-center gap-2 mt-4 px-5 py-2.5 rounded-xl bg-accent hover:bg-accent/90 text-white text-sm font-semibold transition-all shadow-lg shadow-accent/25"
>
<i className="fa-solid fa-cloud-arrow-up" /> Upload
</Link>
</div>
)}
</section>
</div>
{/* Recent Comments */}
<div>
<h2 className="text-lg font-bold text-white mb-4">Recent Comments</h2>
<div className="bg-nova-900/40 border border-white/10 rounded-2xl p-4">
{recentComments?.length > 0 ? (
recentComments.map((c) => <RecentComment key={c.id} comment={c} />)
) : (
<p className="text-slate-500 text-sm text-center py-4">No comments yet</p>
)}
</div>
<div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
{showWidget('module_summaries') && <section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Module health</h2>
<a href="/studio/content" className="text-sm font-medium text-sky-100">Open content queue</a>
</div>
<div className="mt-5 grid gap-4 md:grid-cols-2">
{(overview.module_summaries || []).map((item) => (
<div key={item.key} className="rounded-[24px] border border-white/10 bg-black/20 p-4">
<div className="flex items-center gap-3 text-slate-200">
<i className={item.icon} />
<span className="text-base font-semibold text-white">{item.label}</span>
</div>
<div className="mt-4 flex items-end justify-between gap-4">
<div>
<div className="text-3xl font-semibold text-white">{Number(item.count || 0).toLocaleString()}</div>
<div className="mt-2 text-sm text-slate-400">{Number(item.published_count || 0).toLocaleString()} published</div>
<div className="mt-1 text-xs uppercase tracking-[0.18em] text-sky-200/70">{Number(item.trend_value || 0).toLocaleString()} {item.trend_label || 'recent'}</div>
</div>
<div className="text-right text-sm text-slate-400">
<div>{Number(item.draft_count || 0).toLocaleString()} drafts</div>
<div>{Number(item.archived_count || 0).toLocaleString()} archived</div>
</div>
</div>
<div className="mt-4 flex flex-wrap gap-2">
<a href={item.index_url} className="inline-flex items-center gap-2 rounded-full border border-white/10 px-3 py-1.5 text-xs text-slate-100">Manage</a>
<a href={item.create_url} className="inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1.5 text-xs text-sky-100">{item.quick_action_label || 'Create new'}</a>
</div>
</div>
))}
</div>
</section>}
<section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Quick create</h2>
<span className="text-sm text-slate-500">Start with any module</span>
</div>
<div className="mt-4 grid gap-3">
{(overview.quick_create || []).map((item) => (
<QuickCreateCard key={item.key} item={item} />
))}
</div>
</section>
</div>
<div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px_360px]">
{showWidget('active_challenges') && <section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Active challenges</h2>
<a href="/studio/challenges" onClick={() => trackStudioEvent('studio_challenge_action_taken', { surface: studioSurface(), module: 'overview', meta: { action: 'open_challenges' } })} className="text-sm font-medium text-sky-100">Open challenges</a>
</div>
<div className="mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-1">
{(overview.active_challenges?.items || []).map((item) => <ChallengeWidget key={item.id} challenge={item} />)}
</div>
</section>}
{showWidget('featured_status') && <section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Featured status</h2>
<a href="/studio/featured" className="text-sm font-medium text-sky-100">Manage featured</a>
</div>
<div className="mt-4 rounded-[24px] border border-white/10 bg-black/20 p-4">
<div className="flex items-end justify-between gap-3">
<div>
<div className="text-3xl font-semibold text-white">{overview.featured_status?.selected_count || 0}/{overview.featured_status?.target_count || 4}</div>
<div className="mt-1 text-sm text-slate-400">modules have a selected featured item</div>
</div>
<div className="text-right text-xs uppercase tracking-[0.16em] text-slate-500">{(overview.featured_status?.missing_modules || []).length} missing</div>
</div>
</div>
<div className="mt-4 space-y-3">
{(overview.featured_status?.items || []).slice(0, 3).map((item) => <FeaturedStatusCard key={item.id} item={item} />)}
</div>
</section>}
{showWidget('creator_health') && <section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Creator health</h2>
<a href="/studio/growth" className="text-sm font-medium text-sky-100">Open growth</a>
</div>
<div className="mt-4 rounded-[24px] border border-white/10 bg-black/20 p-4">
<div className="text-3xl font-semibold text-white">{overview.creator_health?.score || 0}</div>
<div className="mt-1 text-sm text-slate-400">blended workflow health score</div>
</div>
<div className="mt-4 space-y-3">
{(overview.creator_health?.checkpoints || []).map((item) => (
<a key={item.key} href={item.href} className="block rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
<div className="flex items-start justify-between gap-3">
<div>
<h3 className="text-sm font-semibold text-white">{item.label}</h3>
<p className="mt-2 text-sm leading-6 text-slate-400">{item.detail}</p>
</div>
<span className="text-xl font-semibold text-white">{item.score}</span>
</div>
</a>
))}
</div>
</section>}
</div>
<div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
{showWidget('continue_working') && <section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Continue working</h2>
<a href="/studio/drafts" className="text-sm font-medium text-sky-100">Open drafts</a>
</div>
<div className="mt-4 grid gap-3 md:grid-cols-3">
{(overview.continue_working || []).map((item) => <ContinueWorkingCard key={item.id} item={item} />)}
</div>
</section>}
{showWidget('scheduled_items') && <section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Upcoming schedule</h2>
<a href="/studio/scheduled" className="text-sm font-medium text-sky-100">Open calendar</a>
</div>
<div className="mt-4 space-y-3">
{(overview.scheduled_items || []).slice(0, 4).map((item) => <ScheduledItemCard key={item.id} item={item} />)}
</div>
</section>}
</div>
<div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
<section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Top performers</h2>
<a href="/studio/analytics" className="text-sm font-medium text-sky-100">Open insights</a>
</div>
{overview.top_performers?.length > 0 ? (
<div className="mt-5 grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
{overview.top_performers.map((item) => (
<TopPerformerCard key={item.id} item={item} />
))}
</div>
) : (
<div className="mt-5 rounded-[24px] border border-dashed border-white/15 px-6 py-12 text-center text-slate-400">Nothing has enough activity yet to rank here.</div>
)}
</section>
{showWidget('draft_reminders') && <section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<h2 className="text-lg font-semibold text-white">Draft reminders</h2>
<div className="mt-4 space-y-3">
{(overview.draft_reminders || []).map((item) => (
<a key={item.id} href={item.edit_url || item.manage_url} className="block rounded-2xl border border-white/10 bg-black/20 p-4">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-200/70">{item.module_label}</p>
<h3 className="mt-2 text-base font-semibold text-white">{item.title}</h3>
<p className="mt-1 text-sm text-slate-400">Updated {new Date(item.updated_at).toLocaleDateString()}</p>
</a>
))}
</div>
</section>}
</div>
<div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
{showWidget('recent_activity') && <section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Recent activity</h2>
<a href="/studio/activity" className="text-sm font-medium text-sky-100">Open inbox</a>
</div>
<div className="mt-5 grid gap-4 lg:grid-cols-2">
<div>
<h3 className="text-sm font-semibold uppercase tracking-[0.18em] text-slate-500">Recent publishes</h3>
<div className="mt-3 space-y-3">
{(overview.recent_publishes || []).slice(0, 4).map((item) => (
<RecentPublishCard key={item.id} item={item} />
))}
</div>
</div>
<div>
<h3 className="text-sm font-semibold uppercase tracking-[0.18em] text-slate-500">Recent followers</h3>
<div className="mt-3 space-y-3">
{(overview.recent_followers || []).map((follower) => (
<a key={follower.id} href={follower.profile_url} className="flex items-center gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
{follower.avatar_url ? (
<img src={follower.avatar_url} alt={follower.username} className="h-11 w-11 rounded-2xl object-cover" />
) : (
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-white/5 text-slate-400">
<i className="fa-solid fa-user" />
</div>
)}
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-white">{follower.name}</div>
<div className="text-xs text-slate-400">@{follower.username}</div>
</div>
</a>
))}
</div>
</div>
<div>
<h3 className="text-sm font-semibold uppercase tracking-[0.18em] text-slate-500">Inbox feed</h3>
<div className="mt-3 space-y-3">
{(overview.recent_activity || []).slice(0, 4).map((item) => (
<ActivityRow key={item.id} item={item} />
))}
</div>
</div>
</div>
</section>}
<section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<h2 className="text-lg font-semibold text-white">Growth hints</h2>
<div className="mt-4 space-y-3">
{(overview.growth_hints || []).map((item) => (
<GrowthHint key={item.title} item={item} />
))}
</div>
</section>
</div>
<div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
<section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Recent comments</h2>
<a href="/studio/comments" className="text-sm font-medium text-sky-100">View all</a>
</div>
<div className="mt-4">
{(overview.recent_comments || []).length > 0 ? (
overview.recent_comments.map((comment) => <RecentComment key={comment.id} comment={comment} />)
) : (
<p className="py-6 text-center text-sm text-slate-500">No comments yet</p>
)}
</div>
</section>
<section className="rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<h2 className="text-lg font-semibold text-white">Momentum</h2>
<div className="mt-4 space-y-4">
{[
['Views', analytics.totals?.views],
['Reactions', analytics.totals?.appreciation],
['Shares', analytics.totals?.shares],
['Comments', analytics.totals?.comments],
].map(([label, value]) => (
<div key={label} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
<div className="flex items-center justify-between text-sm text-slate-300">
<span>{label}</span>
<span className="font-semibold text-white">{Number(value || 0).toLocaleString()}</span>
</div>
</div>
))}
</div>
</section>
</div>
{showWidget('stale_drafts') && <div className="mt-6 rounded-[30px] border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Stale drafts</h2>
<a href="/studio/content?bucket=drafts&stale=only&module=stories" className="text-sm font-medium text-sky-100">Filter stale work</a>
</div>
<div className="mt-4 grid gap-3 md:grid-cols-4">
{(overview.stale_drafts || []).map((item) => <ContinueWorkingCard key={item.id} item={item} />)}
</div>
</div>}
</StudioLayout>
)
}