import React, { useEffect, useState } from 'react' import { Link, usePage } from '@inertiajs/react' import NovaSelect from '../components/ui/NovaSelect' import { studioModule, studioSurface, trackStudioEvent } from '../utils/studioEvents' const baseNavGroups = [ { label: 'Creator Studio', items: [ { label: 'Overview', href: '/studio', icon: 'fa-solid fa-chart-line' }, { label: 'Search', href: '/studio/search', icon: 'fa-solid fa-magnifying-glass' }, { label: 'Groups', href: '/studio/groups', icon: 'fa-solid fa-people-group' }, ], }, { label: 'Create', items: [ { label: 'New Artwork', href: '/upload', icon: 'fa-solid fa-cloud-arrow-up' }, { label: 'Upload Queue', href: '/studio/upload-queue', icon: 'fa-solid fa-layer-group' }, { label: 'New Card', href: '/studio/cards/create', icon: 'fa-solid fa-id-card' }, { label: 'New Story', href: '/studio/stories/create', icon: 'fa-solid fa-feather-pointed', fullLoad: true }, { label: 'New Collection', href: '/settings/collections/create', icon: 'fa-solid fa-layer-group' }, ], }, { label: 'Content', items: [ { label: 'All Content', href: '/studio/content', icon: 'fa-solid fa-table-cells-large' }, { label: 'Artworks', href: '/studio/artworks', icon: 'fa-solid fa-images' }, { label: 'Cards', href: '/studio/cards', icon: 'fa-solid fa-id-card' }, { label: 'Collections', href: '/studio/collections', icon: 'fa-solid fa-layer-group' }, { label: 'Stories', href: '/studio/stories', icon: 'fa-solid fa-feather-pointed' }, ], }, { label: 'Library', items: [ { label: 'Drafts', href: '/studio/drafts', icon: 'fa-solid fa-file-pen' }, { label: 'Upload Queue', href: '/studio/upload-queue', icon: 'fa-solid fa-list-check' }, { label: 'Scheduled', href: '/studio/scheduled', icon: 'fa-solid fa-calendar-days' }, { label: 'Calendar', href: '/studio/calendar', icon: 'fa-solid fa-calendar-days' }, { label: 'Archived', href: '/studio/archived', icon: 'fa-solid fa-box-archive' }, { label: 'Assets', href: '/studio/assets', icon: 'fa-solid fa-photo-film' }, ], }, { label: 'Engagement', items: [ { label: 'Inbox', href: '/studio/inbox', icon: 'fa-solid fa-inbox' }, { label: 'Activity', href: '/studio/activity', icon: 'fa-solid fa-bell' }, { label: 'Comments', href: '/studio/comments', icon: 'fa-solid fa-comments' }, { label: 'Followers', href: '/studio/followers', icon: 'fa-solid fa-user-group' }, { label: 'Challenges', href: '/studio/challenges', icon: 'fa-solid fa-trophy' }, ], }, { label: 'Insights', items: [ { label: 'Analytics', href: '/studio/analytics', icon: 'fa-solid fa-chart-pie' }, { label: 'Growth', href: '/studio/growth', icon: 'fa-solid fa-chart-line' }, ], }, { label: 'Creator', items: [ { label: 'Profile', href: '/studio/profile', icon: 'fa-solid fa-id-card' }, { label: 'Featured Content', href: '/studio/featured', icon: 'fa-solid fa-wand-magic-sparkles' }, { label: 'Preferences', href: '/studio/preferences', icon: 'fa-solid fa-sliders' }, { label: 'Studio Settings', href: '/studio/settings', icon: 'fa-solid fa-gear' }, ], }, ] const baseQuickCreateItems = [ { label: 'Artwork', href: '/upload', icon: 'fa-solid fa-cloud-arrow-up' }, { label: 'Card', href: '/studio/cards/create', icon: 'fa-solid fa-id-card' }, { label: 'Story', href: '/studio/stories/create', icon: 'fa-solid fa-feather-pointed', fullLoad: true }, { label: 'Collection', href: '/settings/collections/create', icon: 'fa-solid fa-layer-group' }, ] const STUDIO_CONTEXT_STORAGE_KEY = 'sb.studio.last-context' const RESTORABLE_STUDIO_PATHS = ['/studio', '/studio/artworks', '/studio/collections', '/studio/settings'] function supportsStudioContextRestore(pathname) { return RESTORABLE_STUDIO_PATHS.includes(pathname) } function studioRouteKeyForPath(pathname) { if (pathname === '/studio/artworks' || pathname.endsWith('/artworks')) return 'studio_artworks_url' if (pathname === '/studio/collections' || pathname.endsWith('/collections')) return 'studio_collections_url' if (pathname === '/studio/settings' || pathname.endsWith('/settings')) return 'studio_settings_url' if (pathname.endsWith('/members')) return 'studio_members_url' if (pathname.endsWith('/invitations')) return 'studio_invitations_url' return 'studio_url' } function nestedRouteKeyFor(topLevelRouteKey) { return topLevelRouteKey.replace(/_url$/, '') } function groupStudioUrlForPath(group, pathname) { if (!group) return '/studio' const routeKey = studioRouteKeyForPath(pathname) const nestedRouteKey = nestedRouteKeyFor(routeKey) return group[routeKey] || group.urls?.[nestedRouteKey] || group.studio_url || group.urls?.studio || '/studio' } function personalStudioUrlForPath(pathname) { if (pathname === '/studio/artworks' || pathname.endsWith('/artworks')) return '/studio/artworks' if (pathname === '/studio/collections' || pathname.endsWith('/collections')) return '/studio/collections' if (pathname === '/studio/settings' || pathname.endsWith('/settings')) return '/studio/settings' return '/studio' } function persistStudioContext(slug) { if (typeof window === 'undefined') return try { window.sessionStorage.setItem(STUDIO_CONTEXT_STORAGE_KEY, slug || '') } catch { // Ignore storage failures so Studio navigation keeps working. } } function readPersistedStudioContext() { if (typeof window === 'undefined') return null try { return window.sessionStorage.getItem(STUDIO_CONTEXT_STORAGE_KEY) } catch { return null } } function navigateToStudioUrl(targetUrl) { if (typeof window === 'undefined' || !targetUrl) return if (typeof window.location?.assign === 'function') { window.location.assign(targetUrl) return } window.location.href = targetUrl } function NavLink({ item, active }) { const className = `flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ${ active ? 'bg-accent/20 text-accent shadow-sm shadow-accent/10' : 'text-slate-400 hover:text-white hover:bg-white/5' }` if (item.fullLoad) { return ( {item.label} ) } return ( {item.label} ) } function studioContextSummary(option) { if (!option) return '' if (option.kind === 'personal') return option.description || 'Your creator workspace' return [option.roleLabel, option.description].filter(Boolean).join(' · ') } function studioContextInitials(label) { const initials = String(label || '') .split(/\s+/) .filter(Boolean) .slice(0, 2) .map((segment) => segment.charAt(0).toUpperCase()) .join('') return initials || 'SB' } function StudioContextAvatar({ option, size = 'md' }) { const sizeClass = size === 'lg' ? 'h-11 w-11 text-sm' : 'h-9 w-9 text-xs' if (option?.avatarUrl) { return ( {option.label} ) } const toneClass = option?.kind === 'personal' ? 'border-sky-300/25 bg-sky-300/12 text-sky-100' : 'border-amber-300/20 bg-amber-300/10 text-amber-100' return ( {option?.kind === 'personal' ? : studioContextInitials(option?.label)} ) } function StudioContextOptionContent({ option, expanded = false }) { return (
{option.label}
{studioContextSummary(option)}
{expanded && option.kind === 'group' && option.status ? ( {String(option.status).replace(/_/g, ' ')} ) : null}
) } function buildStudioContextOptions(currentGroup, studioGroups, userLabel, userAvatarUrl) { return [ { value: '', label: 'Personal studio', description: userLabel || 'Your creator workspace', avatarUrl: userAvatarUrl || null, group: 'Workspace', kind: 'personal', }, ...studioGroups.map((group) => ({ value: group.slug, label: group.name, description: Number(group.artworks_count || 0) > 0 ? `${Number(group.artworks_count || 0).toLocaleString()} artwork${Number(group.artworks_count || 0) === 1 ? '' : 's'}` : 'Group workspace', roleLabel: group.role_label || 'Member', status: group.status, avatarUrl: group.avatar_url || null, group: 'Groups', kind: 'group', isCurrent: currentGroup?.slug === group.slug, })), ] } export default function StudioLayout({ children, title, subtitle, actions }) { const { url, props } = usePage() const [mobileOpen, setMobileOpen] = useState(false) const [createOpen, setCreateOpen] = useState(false) const pathname = url.split('?')[0] const studioGroups = Array.isArray(props.studio_groups) ? props.studio_groups : [] const currentGroup = props.studioGroup || null const userLabel = props.auth?.user?.name || props.auth?.user?.username || 'Your creator workspace' const userAvatarUrl = props.auth?.user?.avatar_url || null const canManageNews = Boolean(props.auth?.user?.is_admin || props.auth?.user?.is_moderator) const canManageWorlds = canManageNews const isStaff = Boolean(props.auth?.user?.is_staff) const studioContextOptions = buildStudioContextOptions(currentGroup, studioGroups, userLabel, userAvatarUrl) const navGroups = baseNavGroups.map((group) => { if ((!canManageNews && !canManageWorlds) || group.label !== 'Content') { return group } const extraItems = [] if (canManageNews) { extraItems.push({ label: 'News', href: '/studio/news', icon: 'fa-solid fa-newspaper' }) } if (canManageWorlds) { extraItems.push({ label: 'Worlds', href: '/studio/worlds', icon: 'fa-solid fa-globe' }) } return { ...group, items: [...group.items, ...extraItems], } }) const quickCreatePool = [...baseQuickCreateItems] if (canManageNews) { quickCreatePool.push({ label: 'News Article', href: '/studio/news/create', icon: 'fa-solid fa-newspaper' }) } if (canManageWorlds) { quickCreatePool.push({ label: 'World', href: '/studio/worlds/create', icon: 'fa-solid fa-globe' }) } const quickCreateItems = quickCreatePool.map((item) => { if (currentGroup?.urls && item.label === 'Artwork') { return { ...item, href: currentGroup.urls?.studio_artworks ? `/upload?group=${currentGroup.slug}` : item.href } } if (currentGroup?.urls && item.label === 'Collection') { return { ...item, href: `/settings/collections/create?group=${currentGroup.slug}` } } return item }) useEffect(() => { const moduleKey = studioModule(pathname) const surface = studioSurface(pathname) trackStudioEvent('studio_opened', { surface, module: moduleKey, }) trackStudioEvent('studio_module_opened', { surface, module: moduleKey, }) }, [pathname]) useEffect(() => { if (!currentGroup?.slug) return persistStudioContext(currentGroup.slug) }, [currentGroup?.slug]) useEffect(() => { if (currentGroup || !supportsStudioContextRestore(pathname)) return const storedSlug = readPersistedStudioContext() if (!storedSlug) return const nextGroup = studioGroups.find((group) => group.slug === storedSlug) if (!nextGroup) { persistStudioContext('') return } const targetUrl = groupStudioUrlForPath(nextGroup, pathname) if (targetUrl && targetUrl !== pathname) { navigateToStudioUrl(targetUrl) } }, [currentGroup, pathname, studioGroups]) const isActive = (href) => { if (href === '/studio') return pathname === '/studio' return pathname.startsWith(href) } const handleQuickCreateClick = (item) => { trackStudioEvent('studio_quick_create_used', { surface: studioSurface(pathname), module: item.label.toLowerCase(), meta: { href: item.href, label: item.label, }, }) } const handleContextChange = (nextSlug) => { persistStudioContext(nextSlug) const nextGroup = studioGroups.find((group) => group.slug === nextSlug) const targetUrl = nextGroup ? groupStudioUrlForPath(nextGroup, pathname) : personalStudioUrlForPath(pathname) if (targetUrl !== pathname) { navigateToStudioUrl(targetUrl) } } return (

Creator Studio

{title || 'Creator Studio'}

{mobileOpen && (
setMobileOpen(false)}>
)}

Creator Studio

{title &&

{title}

} {subtitle &&

{subtitle}

} {currentGroup ?

Group context: {currentGroup.name}

: null}
{studioGroups.length > 0 ? : null} {actions}
{createOpen && ( )}
{children}
) } function ContextSwitcher({ currentGroup, studioContextOptions, onContextChange }) { return (
onContextChange?.(value)} options={studioContextOptions} searchable={true} searchPlaceholder="Search studios or groups…" renderOption={(option) => } renderValue={(option) => } className="min-w-[280px] border-white/10 bg-white/[0.03] shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] hover:border-white/20" />
) } function StudioSidebarContent({ currentGroup, studioContextOptions, navGroups, quickCreateItems, isActive, isStaff, onNavigate, onQuickCreate, onContextChange }) { return ( <>

Skinbase Nova

Creator Studio

Create, manage, and grow from one modular workspace built for every creator surface.

{studioContextOptions.length > 1 ? (

Context

onContextChange?.(value)} className="mt-2" options={studioContextOptions} searchable={true} searchPlaceholder="Search studios or groups…" renderOption={(option) => } renderValue={(option) => } />
) : null}

Quick create

) }