|
|
|
@@ -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,
|
|
|
|
|