From 5c80402aedeef48eda5c0db6293ba1a687110a8f Mon Sep 17 00:00:00 2001 From: test Date: Sun, 30 Aug 2026 11:06:07 +0200 Subject: [PATCH] fix --- bootstrap/ssr/ssr-manifest.json | 3 + bootstrap/ssr/ssr.js | 284 +++++++++++++++++++++++++------- 2 files changed, 228 insertions(+), 59 deletions(-) diff --git a/bootstrap/ssr/ssr-manifest.json b/bootstrap/ssr/ssr-manifest.json index d8c76881..c932867e 100644 --- a/bootstrap/ssr/ssr-manifest.json +++ b/bootstrap/ssr/ssr-manifest.json @@ -2467,12 +2467,15 @@ "resources/js/hooks/upload/useVisionTags.js": [], "resources/js/hooks/useWebShare.js": [], "resources/js/lib/academyAnalytics.js": [], + "resources/js/lib/profileFormatters.js": [], "resources/js/lib/security/botFingerprint.js": [], "resources/js/lib/uploadAnalytics.js": [], "resources/js/lib/uploadEndpoints.js": [], "resources/js/lib/uploadNotices.js": [], "resources/js/lib/uploadUtils.js": [], "resources/js/lib/useDeferredSimilarAi.js": [], + "resources/js/lib/useLazyProfileFavourites.js": [], + "resources/js/lib/useLazyProfileFeaturedArtworks.js": [], "resources/js/lib/useNavContext.js": [], "resources/js/lib/worldAnalytics.js": [], "resources/js/ssr.jsx": [], diff --git a/bootstrap/ssr/ssr.js b/bootstrap/ssr/ssr.js index 8da29eef..85b38adb 100644 --- a/bootstrap/ssr/ssr.js +++ b/bootstrap/ssr/ssr.js @@ -42455,6 +42455,21 @@ function NovaConfirmDialog({ document.body ); } +const PROFILE_LOCALE = "en-US"; +const PROFILE_TIME_ZONE = "UTC"; +function formatProfileNumber(value) { + const numeric = Number(value ?? 0); + return Number.isFinite(numeric) ? new Intl.NumberFormat(PROFILE_LOCALE).format(numeric) : "0"; +} +function formatProfileDate(value, options = {}) { + if (!value) return null; + const date = value instanceof Date ? value : new Date(value); + if (Number.isNaN(date.getTime())) return null; + return new Intl.DateTimeFormat(PROFILE_LOCALE, { + ...options, + timeZone: PROFILE_TIME_ZONE + }).format(date); +} function FollowButton({ username, initialFollowing = false, @@ -42523,7 +42538,7 @@ function FollowButton({ }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid fa-fw ${loading ? "fa-circle-notch fa-spin" : following ? hovered ? "fa-user-minus" : "fa-user-check" : "fa-user-plus"}` }), /* @__PURE__ */ React.createElement("span", null, following ? hovered ? "Unfollow" : "Following" : "Follow"), - showCount ? /* @__PURE__ */ React.createElement("span", { className: "text-xs opacity-70" }, count.toLocaleString()) : null + showCount ? /* @__PURE__ */ React.createElement("span", { className: "text-xs opacity-70" }, formatProfileNumber(count)) : null ), /* @__PURE__ */ React.createElement( NovaConfirmDialog, { @@ -76057,13 +76072,7 @@ async function requestJson$p(url, { method = "GET", body: body2 } = {}) { } function formatUpdated(value) { if (!value) return "Updated recently"; - const date = new Date(value); - if (Number.isNaN(date.getTime())) return "Updated recently"; - return new Intl.DateTimeFormat(void 0, { - month: "short", - day: "numeric", - year: "numeric" - }).format(date); + return formatProfileDate(value, { month: "short", day: "numeric", year: "numeric" }) || "Updated recently"; } function StatPill$1({ icon, label, value }) { return /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-1.5 rounded-full border border-white/10 bg-white/[0.05] px-2.5 py-1 text-[11px] font-medium text-slate-300" }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${icon} text-[10px] text-slate-400` }), /* @__PURE__ */ React.createElement("span", { className: "text-white" }, value), /* @__PURE__ */ React.createElement("span", null, label)); @@ -76140,7 +76149,7 @@ function CollectionCard({ collection, isOwner, onDelete, onToggleFeature, onMove className: `group relative overflow-hidden rounded-[28px] border border-white/10 bg-white/[0.04] shadow-[0_22px_60px_rgba(2,6,23,0.22)] transition-all duration-200 hover:-translate-y-1 hover:border-sky-300/30 hover:bg-white/[0.06] ${busy ? "opacity-70" : ""} lg:max-w-[360px] lg:mx-auto` }, /* @__PURE__ */ React.createElement("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.12),transparent_34%),radial-gradient(circle_at_bottom_right,rgba(249,115,22,0.12),transparent_28%)] opacity-0 transition duration-300 group-hover:opacity-100" }), - /* @__PURE__ */ React.createElement("div", { className: "relative" }, /* @__PURE__ */ React.createElement(CoverMedia, { collection, isOwner }), /* @__PURE__ */ React.createElement("div", { className: "p-5" }, /* @__PURE__ */ React.createElement("div", { className: "mb-3 flex flex-wrap items-center gap-2" }, collection?.is_featured ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-amber-300/25 bg-amber-300/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, "Featured") : null, collection?.mode === "smart" ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-sky-300/25 bg-sky-400/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, "Smart") : null, !isOwner && collection?.program_key ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-lime-300/25 bg-lime-400/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-lime-100" }, "Program · ", collection.program_key) : null, !isOwner && collection?.partner_label ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-white/10 bg-white/[0.05] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-200" }, "Partner · ", collection.partner_label) : null, !isOwner && collection?.sponsorship_label ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-amber-300/20 bg-amber-300/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, "Sponsor · ", collection.sponsorship_label) : null, isOwner ? /* @__PURE__ */ React.createElement(CollectionVisibilityBadge, { visibility: collection?.visibility }) : null), /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("h3", { className: "truncate text-lg font-semibold tracking-[-0.03em] text-white" }, collection?.title), collection?.subtitle ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 truncate text-sm text-slate-300" }, collection.subtitle) : null, collection?.owner?.name ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 truncate text-sm text-slate-400" }, "Curated by ", collection.owner.name, collection?.owner?.username ? ` • @${collection.owner.username}` : "") : null, /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, (collection?.artworks_count ?? 0).toLocaleString(), " artworks"))), collection?.description_excerpt ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 line-clamp-2 text-sm leading-relaxed text-slate-300" }, collection.description_excerpt) : collection?.smart_summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 line-clamp-2 text-sm leading-relaxed text-slate-300" }, collection.smart_summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement(StatPill$1, { icon: "fa-heart", label: "likes", value: (collection?.likes_count ?? 0).toLocaleString() }), /* @__PURE__ */ React.createElement(StatPill$1, { icon: "fa-bell", label: "followers", value: (collection?.followers_count ?? 0).toLocaleString() }), collection?.collaborators_count > 1 ? /* @__PURE__ */ React.createElement(StatPill$1, { icon: "fa-user-group", label: "curators", value: (collection?.collaborators_count ?? 0).toLocaleString() }) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex items-center justify-between gap-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, collection?.is_featured ? "Featured" : "Updated", " ", formatUpdated(collection?.featured_at || collection?.updated_at)), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2", onClick: stop }, !isOwner && (collection?.save_url || collection?.unsave_url || collection?.login_url) ? /* @__PURE__ */ React.createElement( + /* @__PURE__ */ React.createElement("div", { className: "relative" }, /* @__PURE__ */ React.createElement(CoverMedia, { collection, isOwner }), /* @__PURE__ */ React.createElement("div", { className: "p-5" }, /* @__PURE__ */ React.createElement("div", { className: "mb-3 flex flex-wrap items-center gap-2" }, collection?.is_featured ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-amber-300/25 bg-amber-300/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, "Featured") : null, collection?.mode === "smart" ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-sky-300/25 bg-sky-400/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-sky-100" }, "Smart") : null, !isOwner && collection?.program_key ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-lime-300/25 bg-lime-400/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-lime-100" }, "Program · ", collection.program_key) : null, !isOwner && collection?.partner_label ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-white/10 bg-white/[0.05] px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-200" }, "Partner · ", collection.partner_label) : null, !isOwner && collection?.sponsorship_label ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full border border-amber-300/20 bg-amber-300/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-100" }, "Sponsor · ", collection.sponsorship_label) : null, isOwner ? /* @__PURE__ */ React.createElement(CollectionVisibilityBadge, { visibility: collection?.visibility }) : null), /* @__PURE__ */ React.createElement("div", { className: "flex items-start justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("h3", { className: "truncate text-lg font-semibold tracking-[-0.03em] text-white" }, collection?.title), collection?.subtitle ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 truncate text-sm text-slate-300" }, collection.subtitle) : null, collection?.owner?.name ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 truncate text-sm text-slate-400" }, "Curated by ", collection.owner.name, collection?.owner?.username ? ` • @${collection.owner.username}` : "") : null, /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400" }, formatProfileNumber(collection?.artworks_count ?? 0), " artworks"))), collection?.description_excerpt ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 line-clamp-2 text-sm leading-relaxed text-slate-300" }, collection.description_excerpt) : collection?.smart_summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 line-clamp-2 text-sm leading-relaxed text-slate-300" }, collection.smart_summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement(StatPill$1, { icon: "fa-heart", label: "likes", value: formatProfileNumber(collection?.likes_count ?? 0) }), /* @__PURE__ */ React.createElement(StatPill$1, { icon: "fa-bell", label: "followers", value: formatProfileNumber(collection?.followers_count ?? 0) }), collection?.collaborators_count > 1 ? /* @__PURE__ */ React.createElement(StatPill$1, { icon: "fa-user-group", label: "curators", value: formatProfileNumber(collection?.collaborators_count ?? 0) }) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex items-center justify-between gap-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, collection?.is_featured ? "Featured" : "Updated", " ", formatUpdated(collection?.featured_at || collection?.updated_at)), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2", onClick: stop }, !isOwner && (collection?.save_url || collection?.unsave_url || collection?.login_url) ? /* @__PURE__ */ React.createElement( "button", { type: "button", @@ -94913,7 +94922,7 @@ function ProfileCoverEditor({ ))))); } function formatXp(value) { - return new Intl.NumberFormat().format(Number(value || 0)); + return formatProfileNumber(value); } function clampPercent(value) { const numeric = Number(value || 0); @@ -94977,8 +94986,7 @@ function shinyFlagUrl(countryCode, filesCdnUrl = "") { return base ? `${base}${relativePath}` : relativePath; } function formatCompactNumber$1(value) { - const numeric = Number(value ?? 0); - return numeric.toLocaleString("en-US"); + return formatProfileNumber(value); } function ProfileHero({ user, profile, isOwner, viewerIsFollowing, followerCount, recentFollowers = [], followContext = null, heroBgUrl, countryName, leaderboardRank, extraActions = null }) { const { props } = X$1(); @@ -94990,7 +94998,7 @@ function ProfileHero({ user, profile, isOwner, viewerIsFollowing, followerCount, const flagUrl = shinyFlagUrl(profile?.country_code, props?.cdn?.files_url); const uname = user.username || user.name || "Unknown"; const displayName = user.name || uname; - const joinDate = user.created_at ? new Date(user.created_at).toLocaleDateString("en-US", { month: "long", year: "numeric" }) : null; + const joinDate = user.created_at ? formatProfileDate(user.created_at, { month: "long", year: "numeric" }) : null; const progressPercent = Math.round(Number(user?.progress_percent ?? 0)); const heroStats = [ { label: "Followers", value: formatCompactNumber$1(count) }, @@ -95752,7 +95760,7 @@ function slugify(value) { return String(value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""); } function formatNumber$9(value) { - return Number(value ?? 0).toLocaleString(); + return formatProfileNumber(value); } function sortByPublishedAt(items) { return [...items].sort((left, right) => { @@ -95873,7 +95881,7 @@ function FullGalleryCta({ galleryUrl, username }) { "Browse full gallery" ))); } -function TabArtworks({ artworks, featuredArtworks, username, galleryUrl }) { +function TabArtworks({ artworks, featuredArtworks, featuredLoading = false, featuredError = false, onRetryFeatured, username, galleryUrl }) { const initialItems = artworks?.data ?? artworks ?? []; const trendingItems = useArtworkPreview(username, "trending"); const popularItems = useArtworkPreview(username, "views"); @@ -95890,6 +95898,7 @@ function TabArtworks({ artworks, featuredArtworks, username, galleryUrl }) { "aria-labelledby": "tab-artworks", className: "mx-auto max-w-7xl px-4 pt-2 pb-10 md:px-6" }, + featuredLoading ? /* @__PURE__ */ React.createElement("div", { className: "mb-8 rounded-[28px] border border-white/10 bg-white/[0.03] px-5 py-4 text-sm text-slate-400", role: "status" }, "Loading featured artworks…") : featuredError ? /* @__PURE__ */ React.createElement("div", { className: "mb-8 rounded-[28px] border border-rose-400/20 bg-rose-400/10 px-5 py-4 text-sm text-rose-100", role: "alert" }, "Featured artworks could not be loaded.", onRetryFeatured ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "ml-3 underline", onClick: onRetryFeatured }, "Retry") : null) : null, /* @__PURE__ */ React.createElement(FeaturedShowcase, { featuredArtworks: featuredArtworks ?? [] }), /* @__PURE__ */ React.createElement( PreviewRail, @@ -95974,7 +95983,7 @@ function AchievementCard({ achievement, className = "" }) { style: { width: `${unlocked ? 100 : percent}%` } } )), - achievement?.unlocked_at ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-xs text-slate-500" }, "Unlocked ", new Date(achievement.unlocked_at).toLocaleDateString()) : null + achievement?.unlocked_at ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-xs text-slate-500" }, "Unlocked ", formatProfileDate(achievement.unlocked_at, { year: "numeric", month: "short", day: "numeric" })) : null ); } function AchievementsList({ unlocked = [], locked = [], limitLocked }) { @@ -96013,11 +96022,7 @@ function TabAchievements({ achievements, worldRewards, worldHistory, onTabChange function formatDate$a(value) { if (!value) return null; try { - return new Intl.DateTimeFormat("en", { - month: "short", - day: "numeric", - year: "numeric" - }).format(new Date(value)); + return formatProfileDate(value, { month: "short", day: "numeric", year: "numeric" }); } catch { return null; } @@ -96025,7 +96030,7 @@ function formatDate$a(value) { function formatYear(value) { if (!value) return null; try { - return new Intl.DateTimeFormat("en", { year: "numeric" }).format(new Date(value)); + return formatProfileDate(value, { year: "numeric" }); } catch { return null; } @@ -96156,8 +96161,8 @@ function YearlyProductivityChart({ recaps }) { className: `absolute inset-y-0 left-0 rounded-full ${colors.bar} opacity-80 transition-all duration-500 group-hover:opacity-100`, style: { width: `${(pct * 100).toFixed(1)}%` } } - ), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-y-0 left-0 flex w-full items-center px-3 opacity-0 transition-opacity group-hover:opacity-100" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold text-white drop-shadow-sm" }, uploads, " upload", uploads !== 1 ? "s" : "", (item.metrics?.views ?? 0) > 0 ? ` · ${Number(item.metrics.views).toLocaleString()} views` : "", (item.metrics?.downloads ?? 0) > 0 ? ` · ${Number(item.metrics.downloads).toLocaleString()} dl` : ""))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1.5" }, /* @__PURE__ */ React.createElement("span", { className: `text-[13px] font-bold tabular-nums ${isBest ? "text-white" : "text-slate-300"}` }, uploads), isBest && /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-400/25 bg-amber-400/10 px-1.5 py-0.5 text-[9px] font-bold uppercase tracking-wider text-amber-300" }, "best"), (item.metrics?.featured_count ?? 0) > 0 && !isBest && /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-400/20 bg-sky-400/10 px-1.5 py-0.5 text-[9px] font-bold uppercase tracking-wider text-sky-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-star text-[8px]" }), " ", item.metrics.featured_count))); - })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-4 border-t border-white/5 pt-4 text-[12px] text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, sorted.length), " active years"), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, sorted.reduce((s2, r) => s2 + r.metrics.uploads_count, 0).toLocaleString()), " total uploads"), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, Number(sorted.reduce((s2, r) => s2 + (r.metrics.views ?? 0), 0)).toLocaleString()), " total views"))); + ), /* @__PURE__ */ React.createElement("div", { className: "absolute inset-y-0 left-0 flex w-full items-center px-3 opacity-0 transition-opacity group-hover:opacity-100" }, /* @__PURE__ */ React.createElement("span", { className: "text-[11px] font-semibold text-white drop-shadow-sm" }, uploads, " upload", uploads !== 1 ? "s" : "", (item.metrics?.views ?? 0) > 0 ? ` · ${formatProfileNumber(item.metrics.views)} views` : "", (item.metrics?.downloads ?? 0) > 0 ? ` · ${formatProfileNumber(item.metrics.downloads)} dl` : ""))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1.5" }, /* @__PURE__ */ React.createElement("span", { className: `text-[13px] font-bold tabular-nums ${isBest ? "text-white" : "text-slate-300"}` }, uploads), isBest && /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-amber-400/25 bg-amber-400/10 px-1.5 py-0.5 text-[9px] font-bold uppercase tracking-wider text-amber-300" }, "best"), (item.metrics?.featured_count ?? 0) > 0 && !isBest && /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-sky-400/20 bg-sky-400/10 px-1.5 py-0.5 text-[9px] font-bold uppercase tracking-wider text-sky-300" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-star text-[8px]" }), " ", item.metrics.featured_count))); + })), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-4 border-t border-white/5 pt-4 text-[12px] text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, sorted.length), " active years"), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, formatProfileNumber(sorted.reduce((s2, r) => s2 + r.metrics.uploads_count, 0))), " total uploads"), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-white" }, formatProfileNumber(sorted.reduce((s2, r) => s2 + (r.metrics.views ?? 0), 0))), " total views"))); } const RELATION_LABELS = { remake_of: "Remake", @@ -96243,7 +96248,7 @@ function CreatorJourneySection({ journey, username }) { comeback: "bg-amber-400/12 text-amber-200", quiet: "bg-slate-700 text-slate-400" }; - return /* @__PURE__ */ React.createElement("article", { key: item.id, className: "rounded-2xl border border-white/10 bg-white/[0.04] p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, item.value), status2 && /* @__PURE__ */ React.createElement("span", { className: `rounded-full px-2 py-0.5 text-[10px] font-semibold capitalize ${statusColors[status2] ?? statusColors.steady}` }, status2)), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, item.headline)), /* @__PURE__ */ React.createElement("div", { className: "flex h-11 w-11 items-center justify-center rounded-2xl bg-amber-400/12 text-amber-200" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-column" }))), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-relaxed text-slate-300" }, item.summary), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React.createElement(StatPill, { label: "Views", value: (item.metrics?.views ?? 0).toLocaleString() }), /* @__PURE__ */ React.createElement(StatPill, { label: "Downloads", value: (item.metrics?.downloads ?? 0).toLocaleString() }), (item.metrics?.featured_count ?? 0) > 0 && /* @__PURE__ */ React.createElement(StatPill, { label: "Featured", value: item.metrics.featured_count }), item.metrics?.top_category && /* @__PURE__ */ React.createElement(StatPill, { label: "Top category", value: item.metrics.top_category }))); + return /* @__PURE__ */ React.createElement("article", { key: item.id, className: "rounded-2xl border border-white/10 bg-white/[0.04] p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold uppercase tracking-[0.2em] text-slate-400" }, item.value), status2 && /* @__PURE__ */ React.createElement("span", { className: `rounded-full px-2 py-0.5 text-[10px] font-semibold capitalize ${statusColors[status2] ?? statusColors.steady}` }, status2)), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-lg font-semibold text-white" }, item.headline)), /* @__PURE__ */ React.createElement("div", { className: "flex h-11 w-11 items-center justify-center rounded-2xl bg-amber-400/12 text-amber-200" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-column" }))), /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm leading-relaxed text-slate-300" }, item.summary), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React.createElement(StatPill, { label: "Views", value: formatProfileNumber(item.metrics?.views ?? 0) }), /* @__PURE__ */ React.createElement(StatPill, { label: "Downloads", value: formatProfileNumber(item.metrics?.downloads ?? 0) }), (item.metrics?.featured_count ?? 0) > 0 && /* @__PURE__ */ React.createElement(StatPill, { label: "Featured", value: item.metrics.featured_count }), item.metrics?.top_category && /* @__PURE__ */ React.createElement(StatPill, { label: "Top category", value: item.metrics.top_category }))); })))), /* @__PURE__ */ React.createElement(StreaksSection, { streaks }), /* @__PURE__ */ React.createElement(YearlyProductivityChart, { recaps: allRecaps }), /* @__PURE__ */ React.createElement(EvolutionSection, { evolution })); } const SOCIAL_ICONS = { @@ -96304,7 +96309,7 @@ function getContentTypeIcon(label) { return CONTENT_TYPE_ICONS[key] ?? null; } function formatNumber$8(value) { - return Number(value ?? 0).toLocaleString(); + return formatProfileNumber(value); } function formatRelativeDate(value) { if (!value) return null; @@ -96314,7 +96319,7 @@ function formatRelativeDate(value) { const now = /* @__PURE__ */ new Date(); const diffSeconds = Math.round((date.getTime() - now.getTime()) / 1e3); const absSeconds = Math.abs(diffSeconds); - const formatter = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); + const formatter = new Intl.RelativeTimeFormat("en-US", { numeric: "auto" }); if (absSeconds < 3600) { return formatter.format(Math.round(diffSeconds / 60), "minute"); } @@ -96337,7 +96342,7 @@ function formatShortDate$2(value) { try { const date = new Date(value); if (Number.isNaN(date.getTime())) return null; - return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); + return formatProfileDate(date, { month: "short", day: "numeric", year: "numeric" }); } catch { return null; } @@ -96353,7 +96358,7 @@ function formatContributionDate(value) { try { const date = new Date(value); if (Number.isNaN(date.getTime())) return null; - return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); + return formatProfileDate(date, { month: "short", day: "numeric", year: "numeric" }); } catch { return null; } @@ -96408,11 +96413,10 @@ function TabAbout({ user, profile, stats, achievements, worldRewards, artworks, const about = profile?.about; const website = profile?.website; const flagUrl = shinyFlagUrl(profile?.country_code, props?.cdn?.files_url); - const joinDate = user.created_at ? new Date(user.created_at).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" }) : null; + const joinDate = user.created_at ? formatProfileDate(user.created_at, { month: "long", day: "numeric", year: "numeric" }) : null; const lastVisit = user.last_visit_at ? (() => { try { - const d2 = new Date(user.last_visit_at); - return d2.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); + return formatProfileDate(user.last_visit_at, { month: "short", day: "numeric", year: "numeric" }); } catch { return null; } @@ -96421,7 +96425,7 @@ function TabAbout({ user, profile, stats, achievements, worldRewards, artworks, const genderLabel = genderMap[profile?.gender?.toUpperCase()] ?? null; const birthDate = profile?.birthdate ? (() => { try { - return new Date(profile.birthdate).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" }); + return formatProfileDate(profile.birthdate, { month: "long", day: "numeric", year: "numeric" }); } catch { return null; } @@ -96483,7 +96487,7 @@ function TabAbout({ user, profile, stats, achievements, worldRewards, artworks, href: entry.group?.profile_url || "#", className: "rounded-[24px] border border-white/8 bg-white/[0.03] p-4 transition-colors hover:border-white/14 hover:bg-white/[0.06]" }, - /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, entry.group?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: entry.group.avatar_url, alt: entry.group?.name, className: "h-12 w-12 rounded-2xl object-cover ring-1 ring-white/10" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.04] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-people-group" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "truncate text-sm font-semibold text-white" }, entry.group?.name), entry.role ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/20 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, String(entry.role).replaceAll("_", " ")) : null, entry.trusted_indicator ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-300/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-emerald-100" }, "Trusted") : null), entry.group?.headline ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, truncateText$1(entry.group.headline, 100)) : null, entry.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm text-slate-300" }, entry.summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-3 text-xs text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, Number(entry.counts?.credited_artworks || 0).toLocaleString(), " credited artworks"), /* @__PURE__ */ React.createElement("span", null, Number(entry.counts?.releases || 0).toLocaleString(), " releases"), /* @__PURE__ */ React.createElement("span", null, Number(entry.counts?.projects || 0).toLocaleString(), " projects"), entry.joined_at ? /* @__PURE__ */ React.createElement("span", null, "Joined ", formatContributionDate(entry.joined_at)) : null), Array.isArray(entry.role_labels) && entry.role_labels.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-2" }, entry.role_labels.map((label) => /* @__PURE__ */ React.createElement("span", { key: `${entry.group?.slug}-${label}`, className: "rounded-full border border-sky-300/15 bg-sky-400/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-sky-100" }, label))) : null, Array.isArray(entry.recent_release_titles) && entry.recent_release_titles.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-xs text-slate-400" }, "Recent releases: ", entry.recent_release_titles.join(" • ")) : null)) + /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-3" }, entry.group?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: entry.group.avatar_url, alt: entry.group?.name, className: "h-12 w-12 rounded-2xl object-cover ring-1 ring-white/10" }) : /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-2xl border border-white/10 bg-white/[0.04] text-slate-400" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-people-group" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "truncate text-sm font-semibold text-white" }, entry.group?.name), entry.role ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-white/10 bg-black/20 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-300" }, String(entry.role).replaceAll("_", " ")) : null, entry.trusted_indicator ? /* @__PURE__ */ React.createElement("span", { className: "rounded-full border border-emerald-300/20 bg-emerald-300/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-emerald-100" }, "Trusted") : null), entry.group?.headline ? /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-slate-400" }, truncateText$1(entry.group.headline, 100)) : null, entry.summary ? /* @__PURE__ */ React.createElement("p", { className: "mt-3 text-sm text-slate-300" }, entry.summary) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-3 text-xs text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, formatProfileNumber(entry.counts?.credited_artworks || 0), " credited artworks"), /* @__PURE__ */ React.createElement("span", null, formatProfileNumber(entry.counts?.releases || 0), " releases"), /* @__PURE__ */ React.createElement("span", null, formatProfileNumber(entry.counts?.projects || 0), " projects"), entry.joined_at ? /* @__PURE__ */ React.createElement("span", null, "Joined ", formatContributionDate(entry.joined_at)) : null), Array.isArray(entry.role_labels) && entry.role_labels.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 flex flex-wrap gap-2" }, entry.role_labels.map((label) => /* @__PURE__ */ React.createElement("span", { key: `${entry.group?.slug}-${label}`, className: "rounded-full border border-sky-300/15 bg-sky-400/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-sky-100" }, label))) : null, Array.isArray(entry.recent_release_titles) && entry.recent_release_titles.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-xs text-slate-400" }, "Recent releases: ", entry.recent_release_titles.join(" • ")) : null)) )))) : null, followers.length > 0 ? /* @__PURE__ */ React.createElement(SectionCard$2, { icon: "fa-solid fa-user-group", eyebrow: "Community", title: "Recent followers" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 sm:grid-cols-2" }, followers.slice(0, 6).map((follower) => /* @__PURE__ */ React.createElement( "a", { @@ -96587,7 +96591,7 @@ function TabAbout({ user, profile, stats, achievements, worldRewards, artworks, ); } function KpiCard$1({ icon, label, value, color: color2 = "text-sky-400" }) { - return /* @__PURE__ */ React.createElement("div", { className: "bg-white/4 ring-1 ring-white/10 rounded-2xl p-5 shadow-xl shadow-black/20 backdrop-blur flex items-center gap-4" }, /* @__PURE__ */ React.createElement("div", { className: `w-12 h-12 rounded-xl bg-white/5 flex items-center justify-center shrink-0 ${color2}` }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${icon} text-xl` })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-2xl font-bold text-white tabular-nums" }, Number(value ?? 0).toLocaleString()), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500 mt-0.5 uppercase tracking-wider" }, label))); + return /* @__PURE__ */ React.createElement("div", { className: "bg-white/4 ring-1 ring-white/10 rounded-2xl p-5 shadow-xl shadow-black/20 backdrop-blur flex items-center gap-4" }, /* @__PURE__ */ React.createElement("div", { className: `w-12 h-12 rounded-xl bg-white/5 flex items-center justify-center shrink-0 ${color2}` }, /* @__PURE__ */ React.createElement("i", { className: `fa-solid ${icon} text-xl` })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-2xl font-bold text-white tabular-nums" }, formatProfileNumber(value)), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-500 mt-0.5 uppercase tracking-wider" }, label))); } function TabStats({ stats, followerCount, followAnalytics }) { const medalTotals = stats?.medal_totals ?? null; @@ -96616,18 +96620,18 @@ function TabStats({ stats, followerCount, followAnalytics }) { "aria-labelledby": "tab-stats", className: "pt-6" }, - !hasStats ? /* @__PURE__ */ React.createElement("div", { className: "bg-white/4 ring-1 ring-white/10 rounded-2xl p-10 text-center shadow-xl shadow-black/20" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-bar text-3xl text-slate-600 mb-3 block" }), /* @__PURE__ */ React.createElement("p", { className: "text-slate-400 font-medium" }, "No stats available yet"), /* @__PURE__ */ React.createElement("p", { className: "text-slate-600 text-sm mt-1" }, "Stats will appear once there is activity on this profile.")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("h2", { className: "text-xs font-semibold uppercase tracking-widest text-slate-500 mb-4 flex items-center gap-2" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-bar text-green-400 fa-fw" }), "Lifetime Statistics"), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3" }, kpis.map((kpi) => /* @__PURE__ */ React.createElement(KpiCard$1, { key: kpi.label, ...kpi }))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 rounded-2xl border border-white/10 bg-white/4 p-5 shadow-xl shadow-black/20" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-xs font-semibold uppercase tracking-widest text-slate-500" }, "Medal Breakdown"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, "Real medal totals collected across all public artworks.")), /* @__PURE__ */ React.createElement("div", { className: "text-right" }, /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-widest text-slate-500" }, "Weighted Score"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-2xl font-bold text-white tabular-nums" }, Number(medalTotals?.score_total ?? 0).toLocaleString()))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid grid-cols-2 gap-3 md:grid-cols-4" }, [ + !hasStats ? /* @__PURE__ */ React.createElement("div", { className: "bg-white/4 ring-1 ring-white/10 rounded-2xl p-10 text-center shadow-xl shadow-black/20" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-bar text-3xl text-slate-600 mb-3 block" }), /* @__PURE__ */ React.createElement("p", { className: "text-slate-400 font-medium" }, "No stats available yet"), /* @__PURE__ */ React.createElement("p", { className: "text-slate-600 text-sm mt-1" }, "Stats will appear once there is activity on this profile.")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("h2", { className: "text-xs font-semibold uppercase tracking-widest text-slate-500 mb-4 flex items-center gap-2" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-chart-bar text-green-400 fa-fw" }), "Lifetime Statistics"), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3" }, kpis.map((kpi) => /* @__PURE__ */ React.createElement(KpiCard$1, { key: kpi.label, ...kpi }))), /* @__PURE__ */ React.createElement("div", { className: "mt-8 rounded-2xl border border-white/10 bg-white/4 p-5 shadow-xl shadow-black/20" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { className: "text-xs font-semibold uppercase tracking-widest text-slate-500" }, "Medal Breakdown"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm text-slate-400" }, "Real medal totals collected across all public artworks.")), /* @__PURE__ */ React.createElement("div", { className: "text-right" }, /* @__PURE__ */ React.createElement("div", { className: "text-xs uppercase tracking-widest text-slate-500" }, "Weighted Score"), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-2xl font-bold text-white tabular-nums" }, formatProfileNumber(medalTotals?.score_total ?? 0)))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 grid grid-cols-2 gap-3 md:grid-cols-4" }, [ { label: "Gold", value: medalTotals?.gold ?? 0, color: "text-amber-300" }, { label: "Silver", value: medalTotals?.silver ?? 0, color: "text-slate-300" }, { label: "Bronze", value: medalTotals?.bronze ?? 0, color: "text-orange-300" }, { label: "Total Medals", value: medalTotals?.count ?? 0, color: "text-cyan-300" } - ].map((item) => /* @__PURE__ */ React.createElement("div", { key: item.label, className: "rounded-2xl border border-white/10 bg-black/15 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-widest text-slate-500" }, item.label), /* @__PURE__ */ React.createElement("div", { className: `mt-2 text-2xl font-semibold tabular-nums ${item.color}` }, Number(item.value).toLocaleString()))))), /* @__PURE__ */ React.createElement("h3", { className: "mt-8 mb-4 text-xs font-semibold uppercase tracking-widest text-slate-500 flex items-center gap-2" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user-group text-emerald-400 fa-fw" }), "Follow Growth"), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3" }, trendCards.map((card) => /* @__PURE__ */ React.createElement(KpiCard$1, { key: card.label, ...card }))), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-600 mt-6 text-center" }, "More detailed analytics (charts, trends) coming soon.")) + ].map((item) => /* @__PURE__ */ React.createElement("div", { key: item.label, className: "rounded-2xl border border-white/10 bg-black/15 px-4 py-4" }, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-widest text-slate-500" }, item.label), /* @__PURE__ */ React.createElement("div", { className: `mt-2 text-2xl font-semibold tabular-nums ${item.color}` }, formatProfileNumber(item.value)))))), /* @__PURE__ */ React.createElement("h3", { className: "mt-8 mb-4 text-xs font-semibold uppercase tracking-widest text-slate-500 flex items-center gap-2" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user-group text-emerald-400 fa-fw" }), "Follow Growth"), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3" }, trendCards.map((card) => /* @__PURE__ */ React.createElement(KpiCard$1, { key: card.label, ...card }))), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-slate-600 mt-6 text-center" }, "More detailed analytics (charts, trends) coming soon.")) ); } function FavSkeleton() { return /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl overflow-hidden bg-white/5 animate-pulse" }, /* @__PURE__ */ React.createElement("div", { className: "aspect-square bg-white/8" })); } -function TabFavourites({ favourites, isOwner, username }) { +function TabFavourites({ favourites, isOwner, username, loading = false, error = false, onRetry }) { const initialItems = Array.isArray(favourites) ? favourites : favourites?.data ?? []; const [items, setItems] = reactExports.useState(initialItems); const [nextCursor, setNextCursor] = reactExports.useState(favourites?.next_cursor ?? null); @@ -96681,7 +96685,9 @@ function TabFavourites({ favourites, isOwner, username }) { className: "pt-6" }, /* @__PURE__ */ React.createElement("h2", { className: "text-xs font-semibold uppercase tracking-widest text-slate-500 mb-4 flex items-center gap-2" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-heart text-pink-400 fa-fw" }), isOwner ? "Your Favourites" : "Favourites"), - items.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-center justify-center py-20 text-center" }, /* @__PURE__ */ React.createElement("div", { className: "w-20 h-20 rounded-2xl bg-white/5 flex items-center justify-center mb-5 text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-heart text-3xl" })), /* @__PURE__ */ React.createElement("p", { className: "text-slate-400 font-medium" }, "No favourites yet"), /* @__PURE__ */ React.createElement("p", { className: "text-slate-600 text-sm mt-1" }, "Artworks added to favourites will appear here.")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement( + loading && items.length === 0 && /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-4" }, Array.from({ length: 4 }).map((_2, i) => /* @__PURE__ */ React.createElement(FavSkeleton, { key: `initial-sk-${i}` }))), + error && items.length === 0 && /* @__PURE__ */ React.createElement("div", { className: "py-12 text-center text-slate-400" }, /* @__PURE__ */ React.createElement("p", null, "Favourites could not be loaded."), onRetry ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: onRetry, className: "mt-4 rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-sm text-slate-200" }, "Retry") : null), + !loading && !error && items.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-center justify-center py-20 text-center" }, /* @__PURE__ */ React.createElement("div", { className: "w-20 h-20 rounded-2xl bg-white/5 flex items-center justify-center mb-5 text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-heart text-3xl" })), /* @__PURE__ */ React.createElement("p", { className: "text-slate-400 font-medium" }, "No favourites yet"), /* @__PURE__ */ React.createElement("p", { className: "text-slate-600 text-sm mt-1" }, "Artworks added to favourites will appear here.")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement( ArtworkGallery, { items, @@ -98197,9 +98203,7 @@ function ProfileWorldRecognitionBadge({ recognition, isPrimary = false }) { function formatDate$9(value) { if (!value) return null; try { - const date = new Date(value); - if (Number.isNaN(date.getTime())) return null; - return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); + return formatProfileDate(value, { month: "short", day: "numeric", year: "numeric" }); } catch { return null; } @@ -98221,9 +98225,7 @@ function ProfileWorldHistorySummary({ history: history2 }) { function formatDate$8(value) { if (!value) return null; try { - const date = new Date(value); - if (Number.isNaN(date.getTime())) return null; - return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); + return formatProfileDate(value, { month: "short", day: "numeric", year: "numeric" }); } catch { return null; } @@ -98313,13 +98315,7 @@ function TabWorlds({ worldHistory, isOwner }) { function formatFollowedAt(value) { if (!value) return null; try { - const date = new Date(value); - if (Number.isNaN(date.getTime())) return null; - return date.toLocaleDateString(void 0, { - year: "numeric", - month: "short", - day: "numeric" - }); + return formatProfileDate(value, { year: "numeric", month: "short", day: "numeric" }); } catch { return null; } @@ -98339,7 +98335,7 @@ function TabFollowers({ listing, username, followerCount }) { "aria-labelledby": "tab-followers", className: "pt-6" }, - /* @__PURE__ */ React.createElement("h2", { className: "mb-4 flex items-center gap-2 text-xs font-semibold uppercase tracking-widest text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-users text-sky-400 fa-fw" }), "Followers", /* @__PURE__ */ React.createElement("span", { className: "rounded bg-white/5 px-1.5 py-0.5 text-[10px] font-medium normal-case tracking-normal text-slate-400" }, total.toLocaleString())), + /* @__PURE__ */ React.createElement("h2", { className: "mb-4 flex items-center gap-2 text-xs font-semibold uppercase tracking-widest text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-users text-sky-400 fa-fw" }), "Followers", /* @__PURE__ */ React.createElement("span", { className: "rounded bg-white/5 px-1.5 py-0.5 text-[10px] font-medium normal-case tracking-normal text-slate-400" }, formatProfileNumber(total))), items.length === 0 ? /* @__PURE__ */ React.createElement("p", { className: "rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-8 text-center text-sm text-slate-400" }, username ? `${username} has no public followers yet.` : "No public followers yet.") : /* @__PURE__ */ React.createElement("ul", { className: "grid gap-3 sm:grid-cols-2" }, items.map((follower) => { const handle = follower.username || follower.display_name || follower.name; const followedAt = formatFollowedAt(follower.followed_at); @@ -98363,10 +98359,164 @@ function TabFollowers({ listing, username, followerCount }) { lastPage > 1 ? /* @__PURE__ */ React.createElement("nav", { className: "mt-6 flex items-center justify-center gap-3 text-sm", "aria-label": "Followers pagination" }, prevUrl ? /* @__PURE__ */ React.createElement("a", { href: prevUrl, className: "rounded-xl border border-white/10 px-3 py-2 text-slate-300 hover:bg-white/5" }, "Previous") : /* @__PURE__ */ React.createElement("span", { className: "rounded-xl border border-white/5 px-3 py-2 text-slate-600" }, "Previous"), /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "Page ", currentPage, " of ", lastPage), nextUrl ? /* @__PURE__ */ React.createElement("a", { href: nextUrl, className: "rounded-xl border border-white/10 px-3 py-2 text-slate-300 hover:bg-white/5" }, "Next") : /* @__PURE__ */ React.createElement("span", { className: "rounded-xl border border-white/5 px-3 py-2 text-slate-600" }, "Next")) : null ); } +function normalizeGroupContribution(entry = {}) { + const counts = entry?.counts || {}; + const role = entry?.role; + return { + ...entry, + roleLabel: typeof role === "string" ? role : role?.label || null, + trusted: entry?.trusted_indicator ?? entry?.trusted ?? false, + counts: { + ...counts, + credited_artworks: counts.credited_artworks ?? counts.artworks ?? 0, + releases: counts.releases ?? 0, + projects: counts.projects ?? 0, + review_actions: counts.review_actions ?? 0 + }, + role_labels: Array.isArray(entry?.role_labels) ? entry.role_labels : [], + recent_release_titles: Array.isArray(entry?.recent_release_titles) ? entry.recent_release_titles : [] + }; +} function GroupProfileSummary({ contributions = [], href = null }) { if (!Array.isArray(contributions) || contributions.length === 0) return null; - const featured = contributions.slice(0, 3); - return /* @__PURE__ */ React.createElement("section", { className: "mx-auto mt-8 max-w-6xl px-4 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[30px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(14,165,233,0.12),transparent_34%),linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] p-5 shadow-[0_18px_55px_rgba(2,6,23,0.28)] backdrop-blur-xl sm:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80" }, "Group footprint"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Collaborative work across public groups"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-6 text-slate-400" }, "See the groups this creator contributes to through releases, credited artworks, and shared publishing activity.")), href ? /* @__PURE__ */ React.createElement("a", { href, className: "text-sm font-semibold text-sky-200 transition hover:text-white" }, "View full contribution history") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 lg:grid-cols-3" }, featured.map((entry) => /* @__PURE__ */ React.createElement("a", { key: entry.group?.slug || entry.group?.id, href: entry.group?.profile_url || "/groups", className: "rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20 hover:bg-black/30" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-2xl border border-white/10 bg-white/[0.04]" }, entry.group?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: entry.group.avatar_url, alt: entry.group?.name, className: "h-full w-full object-cover", loading: "lazy" }) : /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-people-group text-slate-300" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "truncate text-base font-semibold text-white" }, entry.group?.name), entry.role?.label ? /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm text-slate-400" }, entry.role.label) : null, entry.summary ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-300" }, entry.summary) : null)), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, entry.trusted ? /* @__PURE__ */ React.createElement(GroupBadgePill, { label: "Trusted contributor", tone: "sky" }) : null, entry.recent_release_titles?.length ? /* @__PURE__ */ React.createElement(GroupBadgePill, { label: "Recent releases", tone: "amber" }) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-4 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, Number(entry.counts?.artworks || 0).toLocaleString("en-US"), " artworks"), /* @__PURE__ */ React.createElement("span", null, Number(entry.counts?.releases || 0).toLocaleString("en-US"), " releases"), /* @__PURE__ */ React.createElement("span", null, Number(entry.counts?.projects || 0).toLocaleString("en-US"), " projects"))))))); + const featured = contributions.slice(0, 3).map(normalizeGroupContribution); + return /* @__PURE__ */ React.createElement("section", { className: "mx-auto mt-8 max-w-6xl px-4 sm:px-6 lg:px-8" }, /* @__PURE__ */ React.createElement("div", { className: "rounded-[30px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(14,165,233,0.12),transparent_34%),linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] p-5 shadow-[0_18px_55px_rgba(2,6,23,0.28)] backdrop-blur-xl sm:p-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80" }, "Group footprint"), /* @__PURE__ */ React.createElement("h2", { className: "mt-2 text-2xl font-semibold text-white" }, "Collaborative work across public groups"), /* @__PURE__ */ React.createElement("p", { className: "mt-2 max-w-3xl text-sm leading-6 text-slate-400" }, "See the groups this creator contributes to through releases, credited artworks, and shared publishing activity.")), href ? /* @__PURE__ */ React.createElement("a", { href, className: "text-sm font-semibold text-sky-200 transition hover:text-white" }, "View full contribution history") : null), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid gap-4 lg:grid-cols-3" }, featured.map((entry) => /* @__PURE__ */ React.createElement("a", { key: entry.group?.slug || entry.group?.id, href: entry.group?.profile_url || "/groups", className: "rounded-[24px] border border-white/10 bg-black/20 p-4 transition hover:border-white/20 hover:bg-black/30" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-2xl border border-white/10 bg-white/[0.04]" }, entry.group?.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: entry.group.avatar_url, alt: entry.group?.name, className: "h-full w-full object-cover", loading: "lazy" }) : /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-people-group text-slate-300" })), /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "truncate text-base font-semibold text-white" }, entry.group?.name), entry.roleLabel ? /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm text-slate-400" }, entry.roleLabel) : null, entry.summary ? /* @__PURE__ */ React.createElement("div", { className: "mt-2 text-sm text-slate-300" }, entry.summary) : null)), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, entry.trusted ? /* @__PURE__ */ React.createElement(GroupBadgePill, { label: "Trusted contributor", tone: "sky" }) : null, entry.recent_release_titles?.length ? /* @__PURE__ */ React.createElement(GroupBadgePill, { label: "Recent releases", tone: "amber" }) : null, entry.role_labels.map((label, index2) => /* @__PURE__ */ React.createElement(GroupBadgePill, { key: `${label}-${index2}`, label, tone: "slate" }))), entry.recent_release_titles.length ? /* @__PURE__ */ React.createElement("div", { className: "mt-3 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", { className: "font-semibold text-slate-300" }, "Recent releases:"), " ", entry.recent_release_titles.join(", ")) : null, /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-4 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("span", null, formatProfileNumber(entry.counts.credited_artworks), " artworks"), /* @__PURE__ */ React.createElement("span", null, formatProfileNumber(entry.counts.releases), " releases"), /* @__PURE__ */ React.createElement("span", null, formatProfileNumber(entry.counts.projects), " projects"), /* @__PURE__ */ React.createElement("span", null, formatProfileNumber(entry.counts.review_actions), " reviews"))))))); +} +function useLazyProfileFeaturedArtworks({ username, initialFeaturedArtworks, enabled }) { + const hasInitialValue = Array.isArray(initialFeaturedArtworks); + const [items, setItems] = reactExports.useState(() => hasInitialValue ? initialFeaturedArtworks : []); + const [loaded, setLoaded] = reactExports.useState(hasInitialValue); + const [loading, setLoading] = reactExports.useState(false); + const [error, setError] = reactExports.useState(false); + const requestKeyRef = reactExports.useRef(username); + const requestedRef = reactExports.useRef(hasInitialValue); + const abortRef = reactExports.useRef(null); + reactExports.useEffect(() => { + if (requestKeyRef.current === username) return void 0; + abortRef.current?.abort(); + abortRef.current = null; + requestKeyRef.current = username; + requestedRef.current = false; + setItems([]); + setLoaded(false); + setLoading(false); + setError(false); + return void 0; + }, [username]); + reactExports.useEffect(() => { + if (!hasInitialValue || requestKeyRef.current !== username) return; + abortRef.current?.abort(); + abortRef.current = null; + requestedRef.current = true; + setItems(initialFeaturedArtworks); + setLoaded(true); + setLoading(false); + setError(false); + }, [hasInitialValue, initialFeaturedArtworks, username]); + const load = reactExports.useCallback(async () => { + if (!enabled || hasInitialValue || requestedRef.current || !username) return; + requestedRef.current = true; + const controller = new AbortController(); + const requestKey = requestKeyRef.current; + abortRef.current = controller; + setLoading(true); + setError(false); + try { + const response = await fetch(`/api/profile/${encodeURIComponent(username)}/featured-artworks`, { + headers: { Accept: "application/json" }, + credentials: "same-origin", + signal: controller.signal + }); + if (!response.ok) throw new Error("Featured artworks could not be loaded."); + const payload = await response.json(); + if (requestKey !== requestKeyRef.current || controller.signal.aborted) return; + setItems(Array.isArray(payload?.data) ? payload.data : []); + setLoaded(true); + } catch (caught) { + if (caught?.name === "AbortError" || requestKey !== requestKeyRef.current) return; + requestedRef.current = false; + setError(true); + } finally { + if (requestKey === requestKeyRef.current) { + abortRef.current = null; + setLoading(false); + } + } + }, [enabled, hasInitialValue, username]); + reactExports.useEffect(() => { + if (enabled) load(); + }, [enabled, load]); + reactExports.useEffect(() => () => abortRef.current?.abort(), []); + return { items, loaded, loading, error, retry: load }; +} +function useLazyProfileFavourites({ username, initialFavourites, enabled }) { + const hasInitialValue = initialFavourites !== null && initialFavourites !== void 0; + const initialItems = Array.isArray(initialFavourites) ? initialFavourites : initialFavourites?.data ?? []; + const [items, setItems] = reactExports.useState(() => initialItems); + const [nextCursor, setNextCursor] = reactExports.useState(() => initialFavourites?.next_cursor ?? null); + const [loading, setLoading] = reactExports.useState(false); + const [error, setError] = reactExports.useState(false); + const requestKeyRef = reactExports.useRef(username); + const requestedRef = reactExports.useRef(hasInitialValue); + const abortRef = reactExports.useRef(null); + reactExports.useEffect(() => { + if (requestKeyRef.current === username) return void 0; + abortRef.current?.abort(); + abortRef.current = null; + requestKeyRef.current = username; + requestedRef.current = false; + setItems([]); + setNextCursor(null); + setLoading(false); + setError(false); + return void 0; + }, [username]); + reactExports.useEffect(() => { + if (requestKeyRef.current !== username || !hasInitialValue) return void 0; + abortRef.current?.abort(); + abortRef.current = null; + requestedRef.current = true; + setItems(initialItems); + setNextCursor(initialFavourites?.next_cursor ?? null); + setLoading(false); + setError(false); + return void 0; + }, [hasInitialValue, initialFavourites, initialItems, username]); + const load = reactExports.useCallback(async () => { + if (!enabled || hasInitialValue || requestedRef.current || !username) return; + requestedRef.current = true; + const controller = new AbortController(); + const requestKey = requestKeyRef.current; + abortRef.current = controller; + setLoading(true); + setError(false); + try { + const response = await fetch(`/api/profile/${encodeURIComponent(username)}/favourites`, { + headers: { Accept: "application/json" }, + credentials: "same-origin", + signal: controller.signal + }); + if (!response.ok) throw new Error("Favourites could not be loaded."); + const payload = await response.json(); + if (requestKey !== requestKeyRef.current || controller.signal.aborted) return; + setItems(Array.isArray(payload?.data) ? payload.data : []); + setNextCursor(payload?.next_cursor ?? null); + } catch (caught) { + if (caught?.name === "AbortError" || requestKey !== requestKeyRef.current) return; + requestedRef.current = false; + setError(true); + } finally { + if (requestKey === requestKeyRef.current) { + abortRef.current = null; + setLoading(false); + } + } + }, [enabled, hasInitialValue, username]); + reactExports.useEffect(() => { + if (enabled) load(); + }, [enabled, load]); + reactExports.useEffect(() => () => abortRef.current?.abort(), []); + return { items, nextCursor, loading, error, retry: load }; } const VALID_TABS = ["posts", "artworks", "stories", "achievements", "worlds", "collections", "about", "stats", "favourites", "activity", "followers"]; function getInitialTab(initialTab = "posts") { @@ -98426,6 +98576,16 @@ function ProfileShow() { seo } = props; const [activeTab, setActiveTab] = reactExports.useState(() => getInitialTab(initialTab)); + const featuredState = useLazyProfileFeaturedArtworks({ + username: user?.username || user?.name || "", + initialFeaturedArtworks: featuredArtworks, + enabled: activeTab === "artworks" + }); + const favouritesState = useLazyProfileFavourites({ + username: user?.username || user?.name || "", + initialFavourites: favourites, + enabled: activeTab === "favourites" + }); const handleTabChange = reactExports.useCallback((tab2) => { if (!VALID_TABS.includes(tab2)) return; if (tab2 === "followers") { @@ -98454,8 +98614,8 @@ function ProfileShow() { const isLoggedIn = !!auth?.user; const artworkList = Array.isArray(artworks) ? artworks : artworks?.data ?? []; const artworkNextCursor = artworks?.next_cursor ?? null; - const favouriteList = Array.isArray(favourites) ? favourites : favourites?.data ?? []; - const favouriteNextCursor = favourites?.next_cursor ?? null; + const favouriteList = favouritesState.items; + const favouriteNextCursor = favouritesState.nextCursor; const socialLinksObj = Array.isArray(socialLinks) ? socialLinks.reduce((acc, l3) => { acc[l3.platform] = l3; return acc; @@ -98510,7 +98670,10 @@ function ProfileShow() { TabArtworks, { artworks: { data: artworkList, next_cursor: artworkNextCursor }, - featuredArtworks, + featuredArtworks: featuredState.items, + featuredLoading: featuredState.loading, + featuredError: featuredState.error, + onRetryFeatured: featuredState.retry, username: user.username || user.name, galleryUrl, isActive: true @@ -98580,7 +98743,10 @@ function ProfileShow() { { favourites: { data: favouriteList, next_cursor: favouriteNextCursor }, isOwner, - username: user.username || user.name + username: user.username || user.name, + loading: favouritesState.loading, + error: favouritesState.error, + onRetry: favouritesState.retry } ), activeTab === "activity" && /* @__PURE__ */ React.createElement( TabActivity,