400 lines
27 KiB
JavaScript
400 lines
27 KiB
JavaScript
import React from 'react'
|
|
import { usePage } from '@inertiajs/react'
|
|
import StudioLayout from '../../Layouts/StudioLayout'
|
|
|
|
function StatCard({ label, value, icon }) {
|
|
return (
|
|
<div className="rounded-[24px] border border-white/10 bg-white/[0.03] p-5">
|
|
<div className="flex items-center gap-3 text-slate-300"><i className={icon} /><span>{label}</span></div>
|
|
<div className="mt-3 text-3xl font-semibold text-white">{Number(value || 0).toLocaleString()}</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function ContentCard({ item, fallbackLabel }) {
|
|
return (
|
|
<a href={item.manage_url || item.urls?.edit || item.edit_url || item.preview_url || item.view_url || item.url} className="overflow-hidden rounded-[24px] border border-white/10 bg-black/20 transition hover:border-white/20">
|
|
{item.image_url ? <img src={item.image_url} alt={item.title} className="aspect-[4/3] w-full object-cover" /> : <div className="flex aspect-[4/3] items-center justify-center bg-white/[0.03] text-slate-500"><i className="fa-solid fa-image text-2xl" /></div>}
|
|
<div className="p-4">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<h3 className="text-base font-semibold text-white">{item.title}</h3>
|
|
<span className="rounded-full border border-white/10 bg-white/[0.04] px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300">{item.status || item.event_type || fallbackLabel}</span>
|
|
</div>
|
|
<p className="mt-1 text-sm text-slate-400">{item.subtitle || item.description || item.summary || fallbackLabel}</p>
|
|
</div>
|
|
</a>
|
|
)
|
|
}
|
|
|
|
function EmptyCard({ title, description }) {
|
|
return (
|
|
<div className="rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-5 text-sm text-slate-400">
|
|
<p className="font-semibold text-white">{title}</p>
|
|
<p className="mt-2 leading-6">{description}</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function ActivityCard({ item }) {
|
|
return (
|
|
<div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<div className="text-sm font-semibold text-white">{item.headline}</div>
|
|
{item.is_pinned ? <span className="rounded-full border border-amber-300/20 bg-amber-300/10 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-amber-100">Pinned</span> : null}
|
|
</div>
|
|
{item.summary ? <p className="mt-2 text-sm text-slate-400">{item.summary}</p> : null}
|
|
<div className="mt-2 text-xs text-slate-500">{item.actor?.name || item.actor?.username || 'System'} • {item.occurred_at ? new Date(item.occurred_at).toLocaleString() : 'Recently'}</div>
|
|
{item.subject?.url ? <a href={item.subject.url} className="mt-3 inline-flex text-sm font-semibold text-sky-200">Open subject</a> : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function StudioGroupDashboard() {
|
|
const { props } = usePage()
|
|
const group = props.studioGroup
|
|
const members = Array.isArray(props.members) ? props.members : []
|
|
const dashboard = props.dashboard || {}
|
|
const draftsPendingAction = Array.isArray(props.draftsPendingAction) ? props.draftsPendingAction : []
|
|
const recentArtworks = Array.isArray(props.recentArtworks) ? props.recentArtworks : []
|
|
const recentCollections = Array.isArray(props.recentCollections) ? props.recentCollections : []
|
|
const recentPosts = Array.isArray(props.recentPosts) ? props.recentPosts : []
|
|
const recentProjects = Array.isArray(props.recentProjects) ? props.recentProjects : []
|
|
const recentReleases = Array.isArray(props.recentReleases) ? props.recentReleases : []
|
|
const recentChallenges = Array.isArray(props.recentChallenges) ? props.recentChallenges : []
|
|
const recentEvents = Array.isArray(props.recentEvents) ? props.recentEvents : []
|
|
const recentActivity = Array.isArray(props.recentActivity) ? props.recentActivity : []
|
|
const trustSignals = Array.isArray(props.trustSignals) ? props.trustSignals : []
|
|
const reputationSummary = props.reputationSummary || {}
|
|
const pendingJoinRequests = Array.isArray(props.pendingJoinRequests) ? props.pendingJoinRequests : []
|
|
const reviewQueuePreview = Array.isArray(props.reviewQueuePreview) ? props.reviewQueuePreview : []
|
|
const recruitment = props.recruitment || null
|
|
const recentHistory = Array.isArray(props.recentHistory) ? props.recentHistory : []
|
|
|
|
const roleSummary = members.reduce((summary, member) => {
|
|
const role = String(member.role || 'member')
|
|
summary[role] = (summary[role] || 0) + 1
|
|
return summary
|
|
}, {})
|
|
|
|
const quickActions = [
|
|
{ label: 'Upload artwork', href: group?.urls?.upload, icon: 'fa-solid fa-cloud-arrow-up', tone: 'sky', detail: 'Start a new group-published artwork.' },
|
|
{ label: 'Invite member', href: group?.urls?.studio_invitations, icon: 'fa-solid fa-user-plus', tone: 'emerald', detail: `Manage invites${Number(dashboard.pending_invites_count || 0) > 0 ? ` (${Number(dashboard.pending_invites_count)})` : ''}.` },
|
|
{ label: 'Review queue', href: group?.urls?.studio_review, icon: 'fa-solid fa-list-check', tone: 'amber', detail: `${Number(dashboard.pending_reviews_count || 0)} submissions waiting.` },
|
|
{ label: 'Posts', href: group?.urls?.studio_posts, icon: 'fa-solid fa-bullhorn', tone: 'violet', detail: `${Number(dashboard.published_posts_count || 0)} published posts.` },
|
|
{ label: 'Projects', href: group?.urls?.studio_projects, icon: 'fa-solid fa-diagram-project', tone: 'sky', detail: `${Number(dashboard.projects_count || 0)} total projects.` },
|
|
{ label: 'Releases', href: group?.urls?.studio_releases, icon: 'fa-solid fa-rocket', tone: 'amber', detail: `${Number(dashboard.published_releases_count || 0)} published releases.` },
|
|
{ label: 'Challenges', href: group?.urls?.studio_challenges, icon: 'fa-solid fa-trophy', tone: 'amber', detail: `${Number(dashboard.active_challenges_count || 0)} active or published.` },
|
|
{ label: 'Events', href: group?.urls?.studio_events, icon: 'fa-solid fa-calendar-day', tone: 'emerald', detail: `${Number(dashboard.events_count || 0)} scheduled or archived.` },
|
|
{ label: 'Assets', href: group?.urls?.studio_assets, icon: 'fa-solid fa-box-archive', tone: 'violet', detail: `${Number(dashboard.assets_count || 0)} shared files.` },
|
|
{ label: 'Reputation', href: group?.urls?.studio_reputation, icon: 'fa-solid fa-shield-heart', tone: 'sky', detail: `${Number(reputationSummary?.counts?.contributors || 0)} contributors tracked.` },
|
|
{ label: 'Activity', href: group?.urls?.studio_activity, icon: 'fa-solid fa-wave-square', tone: 'sky', detail: `${Number(dashboard.activity_count || 0)} feed items recorded.` },
|
|
{ label: 'Edit profile', href: group?.urls?.studio_settings, icon: 'fa-solid fa-pen-to-square', tone: 'amber', detail: 'Update headline, visuals, and links.' },
|
|
{ label: 'Recruitment', href: group?.urls?.studio_recruitment, icon: 'fa-solid fa-user-plus', tone: 'emerald', detail: recruitment?.is_recruiting ? 'Recruitment is live.' : 'Configure recruiting status.' },
|
|
{ label: 'Create collection', href: group?.urls?.collection_create, icon: 'fa-solid fa-layer-group', tone: 'violet', detail: 'Publish a new collection under this group.' },
|
|
].filter((item) => Boolean(item.href))
|
|
|
|
const toneClasses = {
|
|
sky: 'border-sky-300/20 bg-sky-300/10 text-sky-100',
|
|
emerald: 'border-emerald-300/20 bg-emerald-300/10 text-emerald-100',
|
|
amber: 'border-amber-300/20 bg-amber-300/10 text-amber-100',
|
|
violet: 'border-violet-300/20 bg-violet-300/10 text-violet-100',
|
|
}
|
|
|
|
return (
|
|
<StudioLayout title={props.title} subtitle={props.description}>
|
|
<div className="grid gap-4 md:grid-cols-3 xl:grid-cols-6">
|
|
<StatCard label="Artworks" value={group?.counts?.artworks} icon="fa-solid fa-images" />
|
|
<StatCard label="Collections" value={group?.counts?.collections} icon="fa-solid fa-layer-group" />
|
|
<StatCard label="Followers" value={group?.counts?.followers} icon="fa-solid fa-user-group" />
|
|
<StatCard label="Active members" value={dashboard?.active_members_count || group?.counts?.members} icon="fa-solid fa-people-group" />
|
|
<StatCard label="Projects" value={dashboard?.projects_count} icon="fa-solid fa-diagram-project" />
|
|
<StatCard label="Releases" value={dashboard?.published_releases_count || dashboard?.releases_count} icon="fa-solid fa-rocket" />
|
|
<StatCard label="Assets" value={dashboard?.assets_count} icon="fa-solid fa-box-archive" />
|
|
</div>
|
|
|
|
<div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]">
|
|
<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">Quick actions</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Run the most common group tasks without leaving the dashboard.</p>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{quickActions.map((action) => (
|
|
<a key={action.label} href={action.href} className={`rounded-[24px] border px-4 py-4 transition hover:translate-y-[-1px] hover:border-white/20 ${toneClasses[action.tone] || toneClasses.sky}`}>
|
|
<div className="flex items-center gap-3">
|
|
<span className="inline-flex h-11 w-11 items-center justify-center rounded-2xl border border-current/20 bg-black/10"><i className={action.icon} /></span>
|
|
<div>
|
|
<div className="text-sm font-semibold">{action.label}</div>
|
|
<div className="mt-1 text-xs opacity-80">{action.detail}</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-6 rounded-[24px] border border-white/10 bg-black/20 p-4">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-white">Pending action</h3>
|
|
<p className="mt-1 text-sm text-slate-400">Drafts and scheduled items that still need a publishing decision.</p>
|
|
</div>
|
|
<div className="text-right text-sm text-slate-300">
|
|
<div>{Number(dashboard?.draft_artworks_count || 0)} drafts</div>
|
|
<div>{Number(dashboard?.scheduled_artworks_count || 0)} scheduled</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{draftsPendingAction.length > 0 ? draftsPendingAction.map((artwork) => (
|
|
<ContentCard key={artwork.id} item={artwork} fallbackLabel="Draft" />
|
|
)) : <EmptyCard title="No drafts waiting" description="This group has no draft artworks waiting for review or completion right now." />}
|
|
</div>
|
|
</div>
|
|
|
|
{pendingJoinRequests.length > 0 ? (
|
|
<div className="mt-6 rounded-[24px] border border-white/10 bg-black/20 p-4">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-white">Pending join requests</h3>
|
|
<p className="mt-1 text-sm text-slate-400">Applicants waiting for a review decision.</p>
|
|
</div>
|
|
{group?.urls?.studio_join_requests ? <a href={group.urls.studio_join_requests} className="text-sm font-semibold text-sky-200">Open queue</a> : null}
|
|
</div>
|
|
<div className="mt-4 space-y-3">
|
|
{pendingJoinRequests.map((item) => (
|
|
<div key={item.id} className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
|
|
<div className="font-semibold text-white">{item.user?.name || item.user?.username}</div>
|
|
<div className="mt-1 text-sm text-slate-400">{item.desired_role_label || item.desired_role || 'Contributor'} • {item.created_at ? new Date(item.created_at).toLocaleDateString() : 'New'}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</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">
|
|
<h2 className="text-xl font-semibold text-white">Members</h2>
|
|
<a href={group?.urls?.studio_members} className="text-sm font-semibold text-sky-200">Manage</a>
|
|
</div>
|
|
<div className="mt-4 grid gap-2 sm:grid-cols-2">
|
|
{Object.entries(roleSummary).map(([role, count]) => (
|
|
<div key={role} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-slate-300">
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">{role}</div>
|
|
<div className="mt-1 text-xl font-semibold text-white">{Number(count)}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="mt-4 space-y-3">
|
|
{members.slice(0, 6).map((member) => (
|
|
<div key={member.id} className="flex items-center gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
|
|
{member.user?.avatar_url ? <img src={member.user.avatar_url} alt={member.user.name || member.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="truncate font-semibold text-white">{member.user?.name || member.user?.username}</div>
|
|
<div className="text-xs uppercase tracking-[0.16em] text-slate-400">{member.role}</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-6 rounded-[24px] border border-white/10 bg-black/20 p-4">
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Recruitment</div>
|
|
<div className="mt-2 text-lg font-semibold text-white">{recruitment?.is_recruiting ? (recruitment.headline || 'Recruiting is active') : 'Recruitment is off'}</div>
|
|
<p className="mt-2 text-sm text-slate-400">{recruitment?.description || 'Set open roles, skills, and contact instructions from the recruitment page.'}</p>
|
|
</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">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-white">Releases</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Track featured drops and current release pipelines.</p>
|
|
</div>
|
|
{group?.urls?.studio_releases ? <a href={group.urls.studio_releases} className="text-sm font-semibold text-sky-200">Manage</a> : null}
|
|
</div>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{recentReleases.length > 0 ? recentReleases.map((release) => (
|
|
<ContentCard key={release.id} item={release} fallbackLabel="Release" />
|
|
)) : <EmptyCard title="No releases yet" description="Create a release to track milestones, contributors, and publication status." />}
|
|
</div>
|
|
</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">Projects</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Recent structured releases and collaboration hubs.</p>
|
|
</div>
|
|
{group?.urls?.studio_projects ? <a href={group.urls.studio_projects} className="text-sm font-semibold text-sky-200">Manage</a> : null}
|
|
</div>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{recentProjects.length > 0 ? recentProjects.map((project) => (
|
|
<ContentCard key={project.id} item={project} fallbackLabel="Project" />
|
|
)) : <EmptyCard title="No projects yet" description="Create a project to bundle shared assets, linked artworks, and a release state." />}
|
|
</div>
|
|
</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">Challenges</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Current creative prompts and challenge arcs.</p>
|
|
</div>
|
|
{group?.urls?.studio_challenges ? <a href={group.urls.studio_challenges} className="text-sm font-semibold text-sky-200">Manage</a> : null}
|
|
</div>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{recentChallenges.length > 0 ? recentChallenges.map((challenge) => (
|
|
<ContentCard key={challenge.id} item={challenge} fallbackLabel="Challenge" />
|
|
)) : <EmptyCard title="No challenges yet" description="Launch a challenge to keep the group active between major releases." />}
|
|
</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">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-white">Trust summary</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Public-facing trust labels and internal contributor health snapshot.</p>
|
|
</div>
|
|
{group?.urls?.studio_reputation ? <a href={group.urls.studio_reputation} className="text-sm font-semibold text-sky-200">Open dashboard</a> : null}
|
|
</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 text-sm text-slate-300"><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(reputationSummary?.counts?.contributors || 0)}</div></div>
|
|
<div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-4 text-sm text-slate-300"><div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Group badges</div><div className="mt-2 text-2xl font-semibold text-white">{Number(reputationSummary?.counts?.group_badges || 0)}</div></div>
|
|
</div>
|
|
</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">Contributor highlights</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Recent high-trust contributors and badge unlocks.</p>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 space-y-3">{Array.isArray(reputationSummary?.top_contributors) && reputationSummary.top_contributors.length > 0 ? reputationSummary.top_contributors.slice(0, 4).map((entry) => <div key={entry.user?.id} 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-slate-400">{entry.summary || 'Contributor'}</div><div className="mt-2 text-xs text-slate-500">{entry.counts?.releases || 0} releases • {entry.counts?.credited_artworks || 0} artworks</div></div>) : <EmptyCard title="No contributor signals yet" description="Release and milestone activity will populate contributor reputation here." />}</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">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-white">Recent artworks</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Latest published work released under this group identity.</p>
|
|
</div>
|
|
<a href={group?.urls?.studio_artworks} className="text-sm font-semibold text-sky-200">View all</a>
|
|
</div>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{recentArtworks.length > 0 ? recentArtworks.map((artwork) => (
|
|
<ContentCard key={artwork.id} item={artwork} fallbackLabel="Published" />
|
|
)) : <EmptyCard title="No published artworks yet" description="Publish the first group artwork to start building this feed." />}
|
|
</div>
|
|
</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">Events</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Upcoming or recently updated moments on the group timeline.</p>
|
|
</div>
|
|
{group?.urls?.studio_events ? <a href={group.urls.studio_events} className="text-sm font-semibold text-sky-200">Manage</a> : null}
|
|
</div>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{recentEvents.length > 0 ? recentEvents.map((event) => (
|
|
<ContentCard key={event.id} item={event} fallbackLabel="Event" />
|
|
)) : <EmptyCard title="No events yet" description="Schedule a launch, stream, or milestone to start the group timeline." />}
|
|
</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">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-white">Recent collections</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Collections most recently updated in this group workspace.</p>
|
|
</div>
|
|
<a href={group?.urls?.studio_collections} className="text-sm font-semibold text-sky-200">View all</a>
|
|
</div>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{recentCollections.length > 0 ? recentCollections.map((collection) => (
|
|
<ContentCard key={collection.id} item={collection} fallbackLabel="Collection" />
|
|
)) : <EmptyCard title="No collections yet" description="Create a collection to organize group work into campaigns, series, or themed sets." />}
|
|
</div>
|
|
</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">Activity feed</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Pinned and recent internal or public timeline items.</p>
|
|
</div>
|
|
{group?.urls?.studio_activity ? <a href={group.urls.studio_activity} className="text-sm font-semibold text-sky-200">Open feed</a> : null}
|
|
</div>
|
|
<div className="mt-4 space-y-3">
|
|
{recentActivity.length > 0 ? recentActivity.map((item) => (
|
|
<ActivityCard key={item.id} item={item} />
|
|
)) : <EmptyCard title="No activity items yet" description="Publishing projects, events, posts, and member milestones will populate this feed." />}
|
|
</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">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-white">Review queue</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Latest artwork submissions waiting for moderation.</p>
|
|
</div>
|
|
{group?.urls?.studio_review ? <a href={group.urls.studio_review} className="text-sm font-semibold text-sky-200">Open queue</a> : null}
|
|
</div>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{reviewQueuePreview.length > 0 ? reviewQueuePreview.map((item) => (
|
|
<a key={item.id} href={item.urls?.edit} className="rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
|
|
<div className="text-sm font-semibold text-white">{item.title}</div>
|
|
<div className="mt-1 text-xs uppercase tracking-[0.16em] text-slate-500">{item.group_review_status}</div>
|
|
</a>
|
|
)) : <EmptyCard title="No pending reviews" description="Contributor submissions will appear here when they are sent for review." />}
|
|
</div>
|
|
</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">Recent posts</h2>
|
|
<p className="mt-1 text-sm text-slate-400">Announcements and updates published from the group.</p>
|
|
</div>
|
|
{group?.urls?.studio_posts ? <a href={group.urls.studio_posts} className="text-sm font-semibold text-sky-200">Manage posts</a> : null}
|
|
</div>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
|
{recentPosts.length > 0 ? recentPosts.map((post) => (
|
|
<a key={post.id} href={post.url} className="rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20">
|
|
<div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">{post.type}</div>
|
|
<div className="mt-2 text-base font-semibold text-white">{post.title}</div>
|
|
<p className="mt-2 text-sm text-slate-400">{post.excerpt || 'Open post'}</p>
|
|
</a>
|
|
)) : <EmptyCard title="No posts yet" description="Create the first group announcement to add a public news feed." />}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<section className="mt-6 rounded-[28px] border border-white/10 bg-white/[0.03] p-5">
|
|
<h2 className="text-xl font-semibold text-white">Recent history</h2>
|
|
<div className="mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
{recentHistory.length > 0 ? recentHistory.map((item) => (
|
|
<div key={item.id} className="rounded-[24px] border border-white/10 bg-black/20 p-4">
|
|
<div className="text-sm font-semibold text-white">{item.summary || item.action_type}</div>
|
|
<div className="mt-2 text-xs text-slate-400">{item.actor?.name || item.actor?.username || 'System'} • {item.created_at ? new Date(item.created_at).toLocaleString() : 'Recently'}</div>
|
|
</div>
|
|
)) : <EmptyCard title="No history yet" description="Audit events will appear here as members review requests, posts, and submissions." />}
|
|
</div>
|
|
</section>
|
|
</StudioLayout>
|
|
)
|
|
} |