import React, { useEffect, useRef, useState } from 'react' import QuickActions from './components/QuickActions' import ActivityFeed from './components/ActivityFeed' import CreatorAnalytics from './components/CreatorAnalytics' import TrendingArtworks from './components/TrendingArtworks' import RecommendedCreators from './components/RecommendedCreators' import LevelBadge from '../components/xp/LevelBadge' import RecentAchievements from './components/RecentAchievements' import TopCreatorsWidget from './components/TopCreatorsWidget' import XPProgressWidget from './components/XPProgressWidget' const RECENT_DASHBOARD_VISITS_KEY = 'skinbase.dashboard.recent-visits' const MAX_PINNED_DASHBOARD_SPACES = 8 const routeDirectory = { '/dashboard': { label: 'Dashboard Home', icon: 'fa-solid fa-house', description: 'Return to the main dashboard hub and overview panels.', }, '/dashboard/profile': { label: 'Profile Settings', icon: 'fa-solid fa-user-pen', description: 'Update your account presentation and profile settings.', }, '/dashboard/notifications': { label: 'Notifications', icon: 'fa-solid fa-bell', description: 'Review unread alerts and recent system updates.', }, '/dashboard/comments/received': { label: 'Received Comments', icon: 'fa-solid fa-inbox', description: 'Catch up on feedback left on your artworks.', }, '/dashboard/followers': { label: 'Followers', icon: 'fa-solid fa-user-group', description: 'See who is following your work.', }, '/dashboard/following': { label: 'Following', icon: 'fa-solid fa-users-viewfinder', description: 'Jump back into the creators you follow.', }, '/dashboard/favorites': { label: 'Favorites', icon: 'fa-solid fa-bookmark', description: 'Revisit the work you saved.', }, '/dashboard/artworks': { label: 'My Artworks', icon: 'fa-solid fa-layer-group', description: 'Manage your uploaded portfolio pieces.', }, '/dashboard/gallery': { label: 'Gallery', icon: 'fa-solid fa-images', description: 'Review the presentation of your gallery.', }, '/dashboard/awards': { label: 'Awards', icon: 'fa-solid fa-trophy', description: 'Track recognition and milestones.', }, '/creator/stories': { label: 'Story Dashboard', icon: 'fa-solid fa-newspaper', description: 'Review creator stories and drafts.', }, '/studio': { label: 'Studio', icon: 'fa-solid fa-compass-drafting', description: 'Open the broader creator workspace.', }, } function loadRecentDashboardVisits() { if (typeof window === 'undefined') { return [] } try { const raw = window.localStorage.getItem(RECENT_DASHBOARD_VISITS_KEY) if (!raw) { return [] } const parsed = JSON.parse(raw) return Array.isArray(parsed) ? parsed : [] } catch { return [] } } function persistRecentDashboardVisits(items) { if (typeof window === 'undefined') { return [] } window.localStorage.setItem(RECENT_DASHBOARD_VISITS_KEY, JSON.stringify(items)) return items } function normalizeRecentVisit(item) { if (!item || typeof item !== 'object' || !item.href) { return null } return { href: item.href, label: item.label || routeDirectory[item.href]?.label || 'Dashboard Space', pinned: Boolean(item.pinned), lastVisitedAt: item.lastVisitedAt || null, } } function prepareRecentVisits(items) { return items .map(normalizeRecentVisit) .filter(Boolean) .sort((left, right) => { if (left.pinned !== right.pinned) { return left.pinned ? -1 : 1 } const leftTime = left.lastVisitedAt ? new Date(left.lastVisitedAt).getTime() : 0 const rightTime = right.lastVisitedAt ? new Date(right.lastVisitedAt).getTime() : 0 return rightTime - leftTime }) } function saveRecentDashboardVisit(entry) { if (typeof window === 'undefined') { return [] } const normalizedEntry = normalizeRecentVisit(entry) const existingMatch = loadRecentDashboardVisits().map(normalizeRecentVisit).find((item) => item && item.href === normalizedEntry.href) const existing = loadRecentDashboardVisits().map(normalizeRecentVisit).filter((item) => item && item.href !== normalizedEntry.href) const next = [ { ...normalizedEntry, pinned: existingMatch?.pinned ?? normalizedEntry.pinned ?? false, lastVisitedAt: new Date().toISOString(), }, ...existing, ].slice(0, 8) return persistRecentDashboardVisits(next) } function upsertPinnedDashboardVisit(href, label, pinned) { if (typeof window === 'undefined') { return [] } const existing = loadRecentDashboardVisits().map(normalizeRecentVisit).filter(Boolean) const match = existing.find((item) => item.href === href) const next = match ? existing.map((item) => item.href === href ? { ...item, label: item.label || label, pinned, lastVisitedAt: item.lastVisitedAt || new Date().toISOString(), } : item ) : [ { href, label, pinned, lastVisitedAt: new Date().toISOString(), }, ...existing, ].slice(0, 8) return persistRecentDashboardVisits(next) } function buildDashboardVisitEntries(items) { return items .map((item) => ({ ...item, ...(routeDirectory[item.href] || {}), })) .filter((item) => item.href && routeDirectory[item.href]) } function persistPinnedDashboardSpaces(hrefs) { if (typeof window === 'undefined' || !window.axios) { return Promise.resolve(null) } return window.axios .put('/api/dashboard/preferences/shortcuts', { pinned_spaces: hrefs.slice(0, MAX_PINNED_DASHBOARD_SPACES), }) .catch(() => null) } function syncRecentVisitsWithPinnedOrder(items, pinnedSpaces = []) { const pinnedSet = new Set(pinnedSpaces) const merged = new Map() items .map(normalizeRecentVisit) .filter(Boolean) .forEach((item) => { merged.set(item.href, { ...item, pinned: pinnedSet.has(item.href), }) }) pinnedSpaces.forEach((href) => { const route = routeDirectory[href] if (!route) { return } const existing = merged.get(href) merged.set(href, { href, label: existing?.label || route.label, pinned: true, lastVisitedAt: existing?.lastVisitedAt || null, }) }) return Array.from(merged.values()) } function orderDashboardVisits(items, pinnedSpaces = []) { const synced = syncRecentVisitsWithPinnedOrder(items, pinnedSpaces) const byHref = new Map(synced.map((item) => [item.href, item])) const pinned = pinnedSpaces.map((href) => byHref.get(href)).filter(Boolean) const pinnedSet = new Set(pinnedSpaces) const unpinned = synced .filter((item) => !pinnedSet.has(item.href)) .sort((left, right) => { const leftTime = left.lastVisitedAt ? new Date(left.lastVisitedAt).getTime() : 0 const rightTime = right.lastVisitedAt ? new Date(right.lastVisitedAt).getTime() : 0 return rightTime - leftTime }) return [...pinned, ...unpinned].slice(0, 8) } function sanitizePinnedDashboardSpaces(hrefs = []) { return hrefs .filter((href, index) => routeDirectory[href] && href !== '/dashboard' && hrefs.indexOf(href) === index) .slice(0, MAX_PINNED_DASHBOARD_SPACES) } function movePinnedDashboardSpace(hrefs, href, direction) { const currentIndex = hrefs.indexOf(href) if (currentIndex === -1) { return hrefs } const targetIndex = direction === 'left' ? currentIndex - 1 : currentIndex + 1 if (targetIndex < 0 || targetIndex >= hrefs.length) { return hrefs } const next = [...hrefs] const [item] = next.splice(currentIndex, 1) next.splice(targetIndex, 0, item) return next } function formatRelativeTime(value) { if (!value) { return 'just now' } const timestamp = new Date(value).getTime() if (Number.isNaN(timestamp)) { return 'just now' } const deltaSeconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000)) if (deltaSeconds < 60) { return 'just now' } const deltaMinutes = Math.round(deltaSeconds / 60) if (deltaMinutes < 60) { return `${deltaMinutes} min ago` } const deltaHours = Math.round(deltaMinutes / 60) if (deltaHours < 24) { return `${deltaHours} hr ago` } const deltaDays = Math.round(deltaHours / 24) return `${deltaDays} day${deltaDays === 1 ? '' : 's'} ago` } function previewLabelForRoute(href, stats) { switch (href) { case '/dashboard/notifications': return `${stats.notifications} unread alert${stats.notifications === 1 ? '' : 's'}` case '/dashboard/comments/received': return `${stats.receivedComments} comment${stats.receivedComments === 1 ? '' : 's'} waiting` case '/dashboard/followers': return `${stats.followers} follower${stats.followers === 1 ? '' : 's'}` case '/dashboard/following': return `${stats.following} account${stats.following === 1 ? '' : 's'} followed` case '/dashboard/favorites': return `${stats.favorites} saved favorite${stats.favorites === 1 ? '' : 's'}` case '/dashboard/artworks': return `${stats.artworks} artwork${stats.artworks === 1 ? '' : 's'} in portfolio` case '/dashboard/gallery': return `${stats.artworks} gallery item${stats.artworks === 1 ? '' : 's'}` case '/creator/stories': return `${stats.stories} stor${stats.stories === 1 ? 'y' : 'ies'} published` default: return null } } function HeroStat({ label, value, tone = 'sky' }) { const tones = { sky: 'border-sky-400/20 bg-sky-400/10 text-sky-100', amber: 'border-amber-400/20 bg-amber-400/10 text-amber-100', emerald: 'border-emerald-400/20 bg-emerald-400/10 text-emerald-100', slate: 'border-white/10 bg-white/5 text-white', } return (

{label}

{value}

) } function SuggestionChip({ href, label, icon, highlight = false, onNavigate }) { return ( onNavigate?.(href, label)} className={[ 'inline-flex items-center gap-2 rounded-full border px-4 py-2 text-sm font-medium transition', highlight ? 'border-sky-300/30 bg-sky-400/15 text-sky-100 hover:border-sky-200/50 hover:bg-sky-400/20' : 'border-white/10 bg-white/5 text-slate-200 hover:border-white/20 hover:bg-white/10', ].join(' ')} >