user(); $prefs = $this->preferences->forUser($user); if (! $request->boolean('overview') && $prefs['default_landing_page'] !== 'overview') { return redirect()->route($this->landingPageRoute($prefs['default_landing_page']), $request->query(), 302); } return Inertia::render('Studio/StudioDashboard', [ 'overview' => $this->overview->build($user), 'analytics' => $this->analytics->overview($user, $prefs['analytics_range_days']), ]); } public function content(Request $request): Response { $prefs = $this->preferences->forUser($request->user()); $listing = $this->content->list($request->user(), $request->only(['module', 'bucket', 'q', 'sort', 'page', 'per_page', 'category', 'tag', 'visibility', 'activity_state', 'stale'])); $listing['default_view'] = $prefs['default_content_view']; return Inertia::render('Studio/StudioContentIndex', [ 'title' => 'Content', 'description' => 'Manage every artwork, card, collection, and story from one queue.', 'listing' => $listing, 'quickCreate' => $this->content->quickCreate(), ]); } /** * Artwork Manager (/studio/artworks) */ public function artworks(Request $request): Response { $provider = $this->content->provider('artworks'); $prefs = $this->preferences->forUser($request->user()); $listing = $this->content->list($request->user(), $request->only(['q', 'sort', 'bucket', 'page', 'per_page', 'category', 'tag']), null, 'artworks'); $listing['default_view'] = $prefs['default_content_view']; return Inertia::render('Studio/StudioArtworks', [ 'title' => 'Artworks', 'description' => 'Upload, manage, and review long-form visual work from the shared Creator Studio workflow.', 'summary' => $provider?->summary($request->user()), 'listing' => $listing, 'quickCreate' => $this->content->quickCreate(), ]); } /** * Drafts (/studio/drafts) */ public function drafts(Request $request): Response { $prefs = $this->preferences->forUser($request->user()); $listing = $this->content->list($request->user(), $request->only(['module', 'q', 'sort', 'page', 'per_page', 'stale']), 'drafts'); $listing['default_view'] = $prefs['default_content_view']; return Inertia::render('Studio/StudioDrafts', [ 'title' => 'Drafts', 'description' => 'Resume unfinished work across every creator module.', 'listing' => $listing, 'quickCreate' => $this->content->quickCreate(), ]); } /** * Archived (/studio/archived) */ public function archived(Request $request): Response { $prefs = $this->preferences->forUser($request->user()); $listing = $this->content->list($request->user(), $request->only(['module', 'q', 'sort', 'page', 'per_page']), 'archived'); $listing['default_view'] = $prefs['default_content_view']; return Inertia::render('Studio/StudioArchived', [ 'title' => 'Archived', 'description' => 'Review hidden, rejected, and archived content in one place.', 'listing' => $listing, 'quickCreate' => $this->content->quickCreate(), ]); } public function scheduled(Request $request): Response { $listing = $this->scheduled->list($request->user(), $request->only(['module', 'q', 'page', 'per_page', 'range', 'start_date', 'end_date'])); return Inertia::render('Studio/StudioScheduled', [ 'title' => 'Scheduled', 'description' => 'Keep track of upcoming publishes across artworks, cards, collections, and stories.', 'listing' => $listing, 'endpoints' => [ 'publishNowPattern' => route('api.studio.schedule.publishNow', ['module' => '__MODULE__', 'id' => '__ID__']), 'unschedulePattern' => route('api.studio.schedule.unschedule', ['module' => '__MODULE__', 'id' => '__ID__']), ], ]); } public function calendar(Request $request): Response { return Inertia::render('Studio/StudioCalendar', [ 'title' => 'Calendar', 'description' => 'Plan publishing cadence, spot overloaded days, and move quickly between scheduled work and the unscheduled queue.', 'calendar' => $this->calendar->build($request->user(), $request->only(['view', 'module', 'status', 'q', 'focus_date'])), 'endpoints' => [ 'publishNowPattern' => route('api.studio.schedule.publishNow', ['module' => '__MODULE__', 'id' => '__ID__']), 'unschedulePattern' => route('api.studio.schedule.unschedule', ['module' => '__MODULE__', 'id' => '__ID__']), ], ]); } public function collections(Request $request): Response { $provider = $this->content->provider('collections'); $prefs = $this->preferences->forUser($request->user()); $listing = $this->content->list($request->user(), $request->only(['q', 'sort', 'page', 'per_page', 'visibility']), null, 'collections'); $listing['default_view'] = $prefs['default_content_view']; return Inertia::render('Studio/StudioCollections', [ 'title' => 'Collections', 'description' => 'Curate sets, track collection performance, and keep editorial surfaces organised.', 'summary' => $provider?->summary($request->user()), 'listing' => $listing, 'quickCreate' => $this->content->quickCreate(), 'dashboardUrl' => route('settings.collections.dashboard'), ]); } public function stories(Request $request): Response { $provider = $this->content->provider('stories'); $prefs = $this->preferences->forUser($request->user()); $listing = $this->content->list($request->user(), $request->only(['q', 'sort', 'page', 'per_page', 'activity_state']), null, 'stories'); $listing['default_view'] = $prefs['default_content_view']; return Inertia::render('Studio/StudioStories', [ 'title' => 'Stories', 'description' => 'Track drafts, jump into the editor, and monitor story reach from Studio.', 'summary' => $provider?->summary($request->user()), 'listing' => $listing, 'quickCreate' => $this->content->quickCreate(), 'dashboardUrl' => route('creator.stories.index'), ]); } public function assets(Request $request): Response { return Inertia::render('Studio/StudioAssets', [ 'title' => 'Assets', 'description' => 'A reusable creator asset library for card backgrounds, story covers, collection covers, artwork previews, and profile branding.', 'assets' => $this->assets->library($request->user(), $request->only(['type', 'source', 'sort', 'q', 'page', 'per_page'])), ]); } public function comments(Request $request): Response { $listing = $this->comments->list($request->user(), $request->only(['module', 'q', 'page', 'per_page'])); return Inertia::render('Studio/StudioComments', [ 'title' => 'Comments', 'description' => 'View context, reply in place, remove unsafe comments, and report issues across all of your content.', 'listing' => $listing, 'endpoints' => [ 'replyPattern' => route('api.studio.comments.reply', ['module' => '__MODULE__', 'commentId' => '__COMMENT__']), 'moderatePattern' => route('api.studio.comments.moderate', ['module' => '__MODULE__', 'commentId' => '__COMMENT__']), 'reportPattern' => route('api.studio.comments.report', ['module' => '__MODULE__', 'commentId' => '__COMMENT__']), ], ]); } public function followers(Request $request): Response { return Inertia::render('Studio/StudioFollowers', [ 'title' => 'Followers', 'description' => 'See who is following your work, who follows back, and which supporters are most established.', 'listing' => $this->followers->list($request->user(), $request->only(['q', 'sort', 'relationship', 'page'])), ]); } public function activity(Request $request): Response { return Inertia::render('Studio/StudioActivity', [ 'title' => 'Activity', 'description' => 'One creator-facing inbox for notifications, comments, and follower activity.', 'listing' => $this->activity->list($request->user(), $request->only(['type', 'module', 'q', 'page', 'per_page'])), 'endpoints' => [ 'markAllRead' => route('api.studio.activity.readAll'), ], ]); } public function inbox(Request $request): Response { return Inertia::render('Studio/StudioInbox', [ 'title' => 'Inbox', 'description' => 'A creator-first response surface for comments, notifications, followers, reminders, and what needs attention now.', 'inbox' => $this->inbox->build($request->user(), $request->only(['type', 'module', 'q', 'page', 'per_page', 'read_state', 'priority'])), 'endpoints' => [ 'markAllRead' => route('api.studio.activity.readAll'), ], ]); } public function search(Request $request): Response { return Inertia::render('Studio/StudioSearch', [ 'title' => 'Search', 'description' => 'Search across content, comments, inbox signals, and reusable assets without leaving Creator Studio.', 'search' => $this->search->build($request->user(), $request->only(['q', 'module', 'type'])), 'quickCreate' => $this->content->quickCreate(), ]); } public function challenges(Request $request): Response { $data = $this->challenges->build($request->user()); return Inertia::render('Studio/StudioChallenges', [ 'title' => 'Challenges', 'description' => 'Track active Nova Cards challenge runs, review your submissions, and keep challenge-ready cards close to hand.', 'summary' => $data['summary'], 'spotlight' => $data['spotlight'], 'activeChallenges' => $data['active_challenges'], 'recentEntries' => $data['recent_entries'], 'cardLeaders' => $data['card_leaders'], 'reminders' => $data['reminders'], ]); } public function growth(Request $request): Response { $prefs = $this->preferences->forUser($request->user()); $rangeDays = in_array((int) $request->query('range_days', 0), [7, 14, 30, 60, 90], true) ? (int) $request->query('range_days') : $prefs['analytics_range_days']; $data = $this->growth->build($request->user(), $rangeDays); return Inertia::render('Studio/StudioGrowth', [ 'title' => 'Growth', 'description' => 'A creator-readable view of profile readiness, publishing cadence, engagement momentum, and challenge participation.', 'summary' => $data['summary'], 'moduleFocus' => $data['module_focus'], 'checkpoints' => $data['checkpoints'], 'opportunities' => $data['opportunities'], 'milestones' => $data['milestones'], 'momentum' => $data['momentum'], 'topContent' => $data['top_content'], 'rangeDays' => $data['range_days'], ]); } public function profile(Request $request): Response { $user = $request->user()->loadMissing(['profile', 'statistics']); $prefs = $this->preferences->forUser($user); $socialLinks = DB::table('user_social_links') ->where('user_id', $user->id) ->orderBy('platform') ->get(['platform', 'url']) ->map(fn ($row): array => [ 'platform' => (string) $row->platform, 'url' => (string) $row->url, ]) ->values() ->all(); return Inertia::render('Studio/StudioProfile', [ 'title' => 'Profile', 'description' => 'Keep your public creator presence aligned with the work you are publishing.', 'profile' => [ 'name' => $user->name, 'username' => $user->username, 'bio' => $user->profile?->about, 'tagline' => $user->profile?->description, 'location' => $user->profile?->country, 'website' => $user->profile?->website, 'avatar_url' => $user->profile?->avatar_url, 'cover_url' => $user->cover_hash && $user->cover_ext ? CoverUrl::forUser($user->cover_hash, $user->cover_ext, time()) : null, 'cover_position' => (int) ($user->cover_position ?? 50), 'followers' => (int) ($user->statistics?->followers_count ?? 0), 'profile_url' => '/@' . strtolower((string) $user->username), 'social_links' => $socialLinks, ], 'moduleSummaries' => $this->content->moduleSummaries($user), 'featuredModules' => $prefs['featured_modules'], 'featuredContent' => $this->content->selectedItems($user, $prefs['featured_content']), 'endpoints' => [ 'profile' => route('api.studio.preferences.profile'), 'avatarUpload' => route('avatar.upload'), 'coverUpload' => route('api.profile.cover.upload'), 'coverPosition' => route('api.profile.cover.position'), 'coverDelete' => route('api.profile.cover.destroy'), ], ]); } public function featured(Request $request): Response { $prefs = $this->preferences->forUser($request->user()); return Inertia::render('Studio/StudioFeatured', [ 'title' => 'Featured', 'description' => 'Choose the artwork, card, collection, and story that should represent each module on your public profile.', 'items' => $this->content->featuredCandidates($request->user(), 12), 'selected' => $prefs['featured_content'], 'featuredModules' => $prefs['featured_modules'], 'endpoints' => [ 'save' => route('api.studio.preferences.featured'), ], ]); } public function settings(Request $request): Response { return Inertia::render('Studio/StudioSettings', [ 'title' => 'Settings', 'description' => 'Keep system handoff links, legacy dashboards, and future Studio control surfaces organized in one place.', 'links' => [ ['label' => 'Profile settings', 'url' => route('settings.profile'), 'icon' => 'fa-solid fa-user-gear'], ['label' => 'Collection dashboard', 'url' => route('settings.collections.dashboard'), 'icon' => 'fa-solid fa-layer-group'], ['label' => 'Story dashboard', 'url' => route('creator.stories.index'), 'icon' => 'fa-solid fa-feather-pointed'], ['label' => 'Followers', 'url' => route('dashboard.followers'), 'icon' => 'fa-solid fa-user-group'], ['label' => 'Received comments', 'url' => route('dashboard.comments.received'), 'icon' => 'fa-solid fa-comments'], ], 'sections' => [ [ 'title' => 'Studio preferences moved into their own surface', 'body' => 'Use the dedicated Preferences page for layout, landing page, analytics window, widget order, and shortcut controls.', 'href' => route('studio.preferences'), 'cta' => 'Open preferences', ], [ 'title' => 'Future-ready control points', 'body' => 'Notification routing, automation defaults, and collaboration hooks can plug into this settings surface without overloading creator workflow pages.', 'href' => route('studio.growth'), 'cta' => 'Review growth', ], ], ]); } public function preferences(Request $request): Response { $prefs = $this->preferences->forUser($request->user()); return Inertia::render('Studio/StudioPreferences', [ 'title' => 'Preferences', 'description' => 'Control how Creator Studio opens, which widgets stay visible, and where your daily workflow starts.', 'preferences' => $prefs, 'links' => [ ['label' => 'Profile settings', 'url' => route('settings.profile'), 'icon' => 'fa-solid fa-user-gear'], ['label' => 'Featured content', 'url' => route('studio.featured'), 'icon' => 'fa-solid fa-wand-magic-sparkles'], ['label' => 'Growth overview', 'url' => route('studio.growth'), 'icon' => 'fa-solid fa-chart-line'], ['label' => 'Studio settings', 'url' => route('studio.settings'), 'icon' => 'fa-solid fa-sliders'], ], 'endpoints' => [ 'save' => route('api.studio.preferences.settings'), ], ]); } /** * Edit artwork (/studio/artworks/:id/edit) */ public function edit(Request $request, int $id): Response { $artwork = $request->user()->artworks() ->with(['stats', 'categories.contentType', 'tags', 'artworkAiAssist']) ->findOrFail($id); $primaryCategory = $artwork->categories->first(); return Inertia::render('Studio/StudioArtworkEdit', [ 'artwork' => [ 'id' => $artwork->id, 'title' => $artwork->title, 'slug' => $artwork->slug, 'description' => $artwork->description, 'is_public' => (bool) $artwork->is_public, 'visibility' => $artwork->visibility ?: ((bool) $artwork->is_public ? 'public' : 'private'), 'is_approved' => (bool) $artwork->is_approved, 'publish_mode' => $artwork->artwork_status === 'scheduled' ? 'schedule' : 'now', 'publish_at' => $artwork->publish_at?->toIso8601String(), 'artwork_status' => $artwork->artwork_status, 'artwork_timezone' => $artwork->artwork_timezone, 'thumb_url' => $artwork->thumbUrl('md'), 'thumb_url_lg' => $artwork->thumbUrl('lg'), 'file_name' => $artwork->file_name, 'file_size' => $artwork->file_size, 'width' => $artwork->width, 'height' => $artwork->height, 'mime_type' => $artwork->mime_type, 'content_type_id' => $primaryCategory?->contentType?->id, 'category_id' => $primaryCategory?->id, 'parent_category_id' => $primaryCategory?->parent_id ? $primaryCategory->parent_id : $primaryCategory?->id, 'sub_category_id' => $primaryCategory?->parent_id ? $primaryCategory->id : null, 'categories' => $artwork->categories->map(fn ($c) => ['id' => $c->id, 'name' => $c->name, 'slug' => $c->slug])->values()->all(), 'tags' => $artwork->tags->map(fn ($t) => ['id' => $t->id, 'name' => $t->name, 'slug' => $t->slug])->values()->all(), 'ai_status' => $artwork->ai_status, 'title_source' => $artwork->title_source ?: 'manual', 'description_source' => $artwork->description_source ?: 'manual', 'tags_source' => $artwork->tags_source ?: 'manual', 'category_source' => $artwork->category_source ?: 'manual', // Versioning 'version_count' => (int) ($artwork->version_count ?? 1), 'requires_reapproval' => (bool) $artwork->requires_reapproval, ], 'contentTypes' => $this->getCategories(), ]); } /** * Analytics v1 (/studio/artworks/:id/analytics) */ public function analytics(Request $request, int $id): Response { $artwork = $request->user()->artworks() ->with(['stats', 'awardStat']) ->findOrFail($id); $stats = $artwork->stats; return Inertia::render('Studio/StudioArtworkAnalytics', [ 'artwork' => [ 'id' => $artwork->id, 'title' => $artwork->title, 'slug' => $artwork->slug, 'thumb_url' => $artwork->thumbUrl('md'), ], 'analytics' => [ 'views' => (int) ($stats?->views ?? 0), 'favourites' => (int) ($stats?->favorites ?? 0), 'shares' => (int) ($stats?->shares_count ?? 0), 'comments' => (int) ($stats?->comments_count ?? 0), 'downloads' => (int) ($stats?->downloads ?? 0), 'ranking_score' => (float) ($stats?->ranking_score ?? 0), 'heat_score' => (float) ($stats?->heat_score ?? 0), 'engagement_velocity' => (float) ($stats?->engagement_velocity ?? 0), ], ]); } /** * Studio-wide Analytics (/studio/analytics) */ public function analyticsOverview(Request $request): Response { $user = $request->user(); $prefs = $this->preferences->forUser($user); $rangeDays = in_array((int) $request->query('range_days', 0), [7, 14, 30, 60, 90], true) ? (int) $request->query('range_days') : $prefs['analytics_range_days']; $data = $this->analytics->overview($user, $rangeDays); return Inertia::render('Studio/StudioAnalytics', [ 'totals' => $data['totals'], 'topContent' => $data['top_content'], 'moduleBreakdown' => $data['module_breakdown'], 'viewsTrend' => $data['views_trend'], 'engagementTrend' => $data['engagement_trend'], 'publishingTimeline' => $data['publishing_timeline'], 'comparison' => $data['comparison'], 'insightBlocks' => $data['insight_blocks'], 'rangeDays' => $data['range_days'], 'recentComments' => $this->overview->recentComments($user, 8), ]); } private function getCategories(): array { return ContentType::with(['rootCategories.children'])->ordered()->get()->map(function ($ct) { return [ 'id' => $ct->id, 'name' => $ct->name, 'slug' => $ct->slug, 'categories' => $ct->rootCategories->map(function ($c) { return [ 'id' => $c->id, 'name' => $c->name, 'slug' => $c->slug, 'children' => $c->children->map(fn ($ch) => [ 'id' => $ch->id, 'name' => $ch->name, 'slug' => $ch->slug, ])->values()->all(), ]; })->values()->all(), ]; })->values()->all(); } private function landingPageRoute(string $page): string { return match ($page) { 'content' => 'studio.content', 'drafts' => 'studio.drafts', 'scheduled' => 'studio.scheduled', 'analytics' => 'studio.analytics', 'activity' => 'studio.activity', 'calendar' => 'studio.calendar', 'inbox' => 'studio.inbox', 'search' => 'studio.search', 'growth' => 'studio.growth', 'challenges' => 'studio.challenges', 'preferences' => 'studio.preferences', default => 'studio.index', }; } }