537 lines
27 KiB
JavaScript
537 lines
27 KiB
JavaScript
import React from 'react'
|
|
import { usePage } from '@inertiajs/react'
|
|
import StudioLayout from '../../Layouts/StudioLayout'
|
|
import { studioSurface, trackStudioEvent } from '../../utils/studioEvents'
|
|
|
|
const kpiConfig = [
|
|
{ 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 }) {
|
|
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-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500">{config.label}</span>
|
|
</div>
|
|
<p className="mt-4 text-3xl font-semibold text-white tabular-nums">{typeof value === 'number' ? value.toLocaleString() : value}</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function QuickCreateCard({ item }) {
|
|
return (
|
|
<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="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 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="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>
|
|
|
|
<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>
|
|
</section>
|
|
</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>
|
|
)
|
|
}
|