)
}
export default function DashboardPage({ username, isCreator, level, rank, receivedCommentsCount, overview = {}, preferences = {} }) {
const persistedPinnedSpaces = sanitizePinnedDashboardSpaces(Array.isArray(preferences.pinned_spaces) ? preferences.pinned_spaces : [])
const persistedPinnedSpacesKey = persistedPinnedSpaces.join('|')
const overviewStats = {
artworks: Number(overview.artworks || 0),
stories: Number(overview.stories || 0),
followers: Number(overview.followers || 0),
following: Number(overview.following || 0),
favorites: Number(overview.favorites || 0),
notifications: Number(overview.notifications || 0),
receivedComments: Number(overview.received_comments || receivedCommentsCount || 0),
}
const [pinnedOrder, setPinnedOrder] = useState(persistedPinnedSpaces)
const [recentVisits, setRecentVisits] = useState([])
const [shortcutNotice, setShortcutNotice] = useState(null)
const latestShortcutSaveRequestRef = useRef(0)
useEffect(() => {
const nextPinnedOrder = sanitizePinnedDashboardSpaces(persistedPinnedSpaces)
const visits = orderDashboardVisits(loadRecentDashboardVisits(), nextPinnedOrder).filter(
(item) => item && item.href && item.href !== '/dashboard'
)
persistRecentDashboardVisits(visits)
setPinnedOrder(nextPinnedOrder)
setRecentVisits(buildDashboardVisitEntries(visits))
saveRecentDashboardVisit({ href: '/dashboard', label: 'Dashboard Home' })
}, [persistedPinnedSpacesKey])
useEffect(() => {
if (!shortcutNotice) {
return undefined
}
const timeoutId = window.setTimeout(() => {
setShortcutNotice(null)
}, 2400)
return () => {
window.clearTimeout(timeoutId)
}
}, [shortcutNotice])
function applyPinnedOrder(nextPinnedOrder, sourceVisits) {
const sanitizedPinnedOrder = sanitizePinnedDashboardSpaces(nextPinnedOrder)
const visits = orderDashboardVisits(sourceVisits, sanitizedPinnedOrder).filter((item) => item.href !== '/dashboard')
const requestId = latestShortcutSaveRequestRef.current + 1
persistRecentDashboardVisits(visits)
setPinnedOrder(sanitizedPinnedOrder)
setRecentVisits(buildDashboardVisitEntries(visits))
latestShortcutSaveRequestRef.current = requestId
setShortcutNotice({
tone: 'info',
message: 'Saving dashboard shortcuts...',
})
persistPinnedDashboardSpaces(sanitizedPinnedOrder).then((saved) => {
if (latestShortcutSaveRequestRef.current !== requestId) {
return
}
setShortcutNotice(
saved
? {
tone: 'success',
message: sanitizedPinnedOrder.length === 0 ? 'Pinned shortcuts cleared.' : 'Dashboard shortcuts saved.',
}
: {
tone: 'error',
message: 'Could not save dashboard shortcuts. Refresh and try again.',
}
)
})
}
function handleNavigate(href, label) {
const next = orderDashboardVisits(saveRecentDashboardVisit({ href, label }), pinnedOrder).filter((item) => item.href !== '/dashboard')
persistRecentDashboardVisits(next)
setRecentVisits(buildDashboardVisitEntries(next))
}
function handleTogglePin(href) {
const nextPinnedOrder = pinnedOrder.includes(href)
? pinnedOrder.filter((item) => item !== href)
: [...pinnedOrder, href]
applyPinnedOrder(nextPinnedOrder, loadRecentDashboardVisits())
}
function handleTogglePinFromCard(href, label) {
const isPinned = pinnedOrder.includes(href)
const sourceVisits = upsertPinnedDashboardVisit(href, label, false)
const nextPinnedOrder = isPinned
? pinnedOrder.filter((item) => item !== href)
: [...pinnedOrder, href]
applyPinnedOrder(nextPinnedOrder, sourceVisits)
}
function handleMovePinnedSpace(href, direction) {
const nextPinnedOrder = movePinnedDashboardSpace(pinnedOrder, href, direction)
if (nextPinnedOrder === pinnedOrder) {
return
}
applyPinnedOrder(nextPinnedOrder, loadRecentDashboardVisits())
}
const pinnedHrefs = new Set(pinnedOrder)
const pinnedSpaces = recentVisits
.filter((item) => item.pinned)
.map((item) => ({
...item,
preview: previewLabelForRoute(item.href, overviewStats),
}))
const dashboardSections = [
{
eyebrow: 'Account Hub',
title: 'Profile, alerts, and feedback',
description: 'Keep your public identity sharp and stay on top of everything that needs a response.',
items: [
{
label: 'Profile Settings',
href: '/dashboard/profile',
icon: 'fa-solid fa-user-pen',
description: 'Update your bio, location, links, avatar, and account preferences.',
meta: 'Dashboard Profile',
preview: 'Bio, avatar, links, and account details',
},
{
label: 'Notifications',
href: '/dashboard/notifications',
icon: 'fa-solid fa-bell',
description: 'Review mentions, system updates, and activity that needs your attention.',
meta: 'Dashboard Notifications',
badge: overviewStats.notifications > 0 ? `${overviewStats.notifications} new` : null,
preview: previewLabelForRoute('/dashboard/notifications', overviewStats),
},
{
label: 'Received Comments',
href: '/dashboard/comments/received',
icon: 'fa-solid fa-inbox',
description: 'Catch up on feedback left on your artworks and clear your inbox quickly.',
meta: 'Dashboard Comments',
badge: overviewStats.receivedComments > 0 ? `${overviewStats.receivedComments} new` : 'Clear',
preview: previewLabelForRoute('/dashboard/comments/received', overviewStats),
},
],
},
{
eyebrow: 'Community',
title: 'Audience, network, and saved work',
description: 'Stay close to the people around your account, from new followers to the creators shaping your feed.',
items: [
{
label: 'Followers',
href: '/dashboard/followers',
icon: 'fa-solid fa-user-group',
description: 'See who joined your audience and discover the people supporting your work.',
meta: 'Dashboard Followers',
badge: overviewStats.followers > 0 ? String(overviewStats.followers) : null,
preview: previewLabelForRoute('/dashboard/followers', overviewStats),
accent: 'sky',
},
{
label: 'Following',
href: '/dashboard/following',
icon: 'fa-solid fa-users-viewfinder',
description: 'Jump back into creators, artists, and brands you already follow.',
meta: 'Dashboard Following',
badge: overviewStats.following > 0 ? String(overviewStats.following) : null,
preview: previewLabelForRoute('/dashboard/following', overviewStats),
accent: 'emerald',
},
{
label: 'Favorites',
href: '/dashboard/favorites',
icon: 'fa-solid fa-bookmark',
description: 'Revisit the artworks you saved so inspiration is always one click away.',
meta: 'Dashboard Favorites',
badge: overviewStats.favorites > 0 ? String(overviewStats.favorites) : null,
preview: previewLabelForRoute('/dashboard/favorites', overviewStats),
accent: 'rose',
},
],
},
{
eyebrow: 'Creator Space',
title: 'Portfolio management and recognition',
description: 'Everything tied to your published work, gallery presentation, and achievements lives here.',
items: [
{
label: 'My Artworks',
href: '/dashboard/artworks',
icon: 'fa-solid fa-layer-group',
description: 'Manage your uploaded artworks, edit details, and keep your portfolio organized.',
meta: 'Dashboard Artworks',
badge: overviewStats.artworks > 0 ? String(overviewStats.artworks) : null,
preview: previewLabelForRoute('/dashboard/artworks', overviewStats),
},
{
label: 'Gallery',
href: '/dashboard/gallery',
icon: 'fa-solid fa-images',
description: 'Review your gallery layout and browse your work the way visitors see it.',
meta: 'Dashboard Gallery',
badge: overviewStats.artworks > 0 ? `${overviewStats.artworks} items` : null,
preview: previewLabelForRoute('/dashboard/gallery', overviewStats),
},
{
label: 'Awards',
href: '/dashboard/awards',
icon: 'fa-solid fa-trophy',
description: 'Track badges, awards, and milestones that showcase your growth on Skinbase.',
meta: 'Dashboard Awards',
},
],
},
]
const dashboardLinksCount = dashboardSections.reduce((total, section) => total + section.items.length, 0)
const isBrandNewMember =
overviewStats.artworks === 0 &&
overviewStats.stories === 0 &&
overviewStats.followers === 0 &&
overviewStats.following === 0 &&
overviewStats.favorites === 0 &&
overviewStats.notifications === 0 &&
overviewStats.receivedComments === 0
const needsCreatorMomentum = !isBrandNewMember && isCreator && overviewStats.receivedComments === 0 && overviewStats.followers < 3
const overviewCards = [
{
label: 'Unread notifications',
value: overviewStats.notifications,
href: '/dashboard/notifications',
icon: 'fa-solid fa-bell',
accent: overviewStats.notifications > 0 ? 'amber' : 'slate',
caption: overviewStats.notifications > 0 ? 'Needs review' : 'All clear',
},
{
label: 'Followers',
value: overviewStats.followers,
href: '/dashboard/followers',
icon: 'fa-solid fa-user-group',
accent: 'sky',
caption: overviewStats.followers > 0 ? 'Audience' : 'Build audience',
},
{
label: 'Following',
value: overviewStats.following,
href: '/dashboard/following',
icon: 'fa-solid fa-users',
accent: 'emerald',
caption: overviewStats.following > 0 ? 'Network' : 'Find creators',
},
{
label: 'Saved favorites',
value: overviewStats.favorites,
href: '/dashboard/favorites',
icon: 'fa-solid fa-bookmark',
accent: 'rose',
caption: overviewStats.favorites > 0 ? 'Inspiration' : 'Nothing saved',
},
{
label: 'Artworks',
value: overviewStats.artworks,
href: '/dashboard/artworks',
icon: 'fa-solid fa-layer-group',
accent: 'emerald',
caption: overviewStats.artworks > 0 ? 'Portfolio' : 'Start uploading',
},
{
label: 'Stories',
value: overviewStats.stories,
href: isCreator ? '/creator/stories' : '/creator/stories/create',
icon: 'fa-solid fa-pen-nib',
accent: 'amber',
caption: overviewStats.stories > 0 ? 'Creator voice' : 'Tell your story',
},
]
const suggestions = [
{
label: overviewStats.receivedComments > 0 ? 'Review new feedback' : 'Open comment inbox',
href: '/dashboard/comments/received',
icon: 'fa-solid fa-comments',
highlight: overviewStats.receivedComments > 0,
},
{
label: overviewStats.notifications > 0 ? 'Clear your alerts' : 'Refine your profile',
href: overviewStats.notifications > 0 ? '/dashboard/notifications' : '/dashboard/profile',
icon: overviewStats.notifications > 0 ? 'fa-solid fa-bell' : 'fa-solid fa-user-gear',
},
{
label: overviewStats.followers > 0 ? 'Check your audience' : 'Find creators to follow',
href: overviewStats.followers > 0 ? '/dashboard/followers' : '/creators/top',
icon: overviewStats.followers > 0 ? 'fa-solid fa-user-group' : 'fa-solid fa-compass',
},
isCreator
? {
label: 'Manage artworks',
href: '/dashboard/artworks',
icon: 'fa-solid fa-pen-ruler',
}
: {
label: 'Start your gallery',
href: '/upload',
icon: 'fa-solid fa-cloud-arrow-up',
},
]
const guidance = isBrandNewMember
? {
title: 'Build your account in three clean steps',
description: 'New members need a little direction more than they need dense analytics. These actions create a better profile, feed, and portfolio foundation quickly.',
tone: 'sky',
steps: [
{
title: 'Finish your profile',
description: 'Add a stronger bio, links, location, and avatar so people have a reason to follow you back.',
href: '/dashboard/profile',
icon: 'fa-solid fa-user-pen',
emphasis: true,
},
{
title: 'Follow great creators',
description: 'Shape your taste graph and make the rest of the dashboard more useful by following a few standout accounts.',
href: '/creators/top',
icon: 'fa-solid fa-user-group',
},
{
title: 'Upload your first artwork',
description: 'Unlock creator-focused dashboard tools, comment feedback, and gallery visibility.',
href: '/upload',
icon: 'fa-solid fa-cloud-arrow-up',
},
],
}
: needsCreatorMomentum
? {
title: 'Give your creator profile more momentum',
description: 'Your dashboard is set up, but a few focused moves will make it feel more alive: publish more, expand reach, and create something worth revisiting.',
tone: 'amber',
steps: [
{
title: 'Polish your gallery',
description: 'Tighten artwork titles, thumbnails, and presentation so the work lands better for new visitors.',
href: '/dashboard/gallery',
icon: 'fa-solid fa-images',
emphasis: true,
},
{
title: 'Publish a creator story',
description: 'Stories give followers more context and help new visitors understand your process.',
href: '/creator/stories/create',
icon: 'fa-solid fa-pen-nib',
},
{
title: 'Discover new audiences',
description: 'Browse rising creators and trending work to find collaborations, inspiration, and follow-back opportunities.',
href: '/discover/rising',
icon: 'fa-solid fa-rocket',
},
],
}
: null
return (
Skinbase Nova Dashboard
Welcome back, {username}
This page is now your dashboard home base: every dashboard section is grouped below, the most urgent tasks are surfaced first, and your progress stays visible without forcing you to hunt through menus.