Rebuild Inertia SSR artifacts after deferred artwork UI.
Keep bootstrap/ssr in sync with the client Vite build shipped in this stack.
This commit is contained in:
@@ -2354,6 +2354,7 @@
|
||||
"resources/js/components/profile/tabs/TabArtworks.jsx": [],
|
||||
"resources/js/components/profile/tabs/TabCollections.jsx": [],
|
||||
"resources/js/components/profile/tabs/TabFavourites.jsx": [],
|
||||
"resources/js/components/profile/tabs/TabFollowers.jsx": [],
|
||||
"resources/js/components/profile/tabs/TabPosts.jsx": [],
|
||||
"resources/js/components/profile/tabs/TabStats.jsx": [],
|
||||
"resources/js/components/profile/tabs/TabStories.jsx": [],
|
||||
@@ -2471,6 +2472,7 @@
|
||||
"resources/js/lib/uploadEndpoints.js": [],
|
||||
"resources/js/lib/uploadNotices.js": [],
|
||||
"resources/js/lib/uploadUtils.js": [],
|
||||
"resources/js/lib/useDeferredSimilarAi.js": [],
|
||||
"resources/js/lib/useNavContext.js": [],
|
||||
"resources/js/lib/worldAnalytics.js": [],
|
||||
"resources/js/ssr.jsx": [],
|
||||
|
||||
+339
-83
@@ -41436,49 +41436,91 @@ function Skeleton$1() {
|
||||
}
|
||||
function ArtworkComments({
|
||||
artworkId,
|
||||
comments: initialComments = [],
|
||||
isLoggedIn = false,
|
||||
loginUrl = "/login"
|
||||
}) {
|
||||
const [comments, setComments] = reactExports.useState(initialComments);
|
||||
const [comments, setComments2] = reactExports.useState([]);
|
||||
const [loading, setLoading] = reactExports.useState(false);
|
||||
const [error, setError] = reactExports.useState(false);
|
||||
const [page, setPage] = reactExports.useState(1);
|
||||
const [lastPage, setLastPage] = reactExports.useState(1);
|
||||
const [total, setTotal] = reactExports.useState(initialComments.length);
|
||||
const initialized = reactExports.useRef(false);
|
||||
const [total, setTotal] = reactExports.useState(0);
|
||||
const sectionRef = reactExports.useRef(null);
|
||||
const requestRef = reactExports.useRef(null);
|
||||
const generationRef = reactExports.useRef(0);
|
||||
const loadComments = reactExports.useCallback(
|
||||
async (p = 1) => {
|
||||
if (!artworkId) return;
|
||||
if (!artworkId || requestRef.current) return;
|
||||
const generation = generationRef.current;
|
||||
const controller = new AbortController();
|
||||
requestRef.current = controller;
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
try {
|
||||
const { data } = await axios.get(`/api/artworks/${artworkId}/comments?page=${p}`);
|
||||
const { data } = await axios.get(`/api/artworks/${artworkId}/comments?page=${p}`, { signal: controller.signal });
|
||||
if (generation !== generationRef.current) return;
|
||||
if (p === 1) {
|
||||
setComments(data.data ?? []);
|
||||
setComments2(data.data ?? []);
|
||||
} else {
|
||||
setComments((prev) => [...prev, ...data.data ?? []]);
|
||||
setComments2((prev) => [...prev, ...data.data ?? []]);
|
||||
}
|
||||
setPage(data.meta?.current_page ?? p);
|
||||
setLastPage(data.meta?.last_page ?? 1);
|
||||
setTotal(data.meta?.total ?? 0);
|
||||
} catch {
|
||||
if (generation === generationRef.current && !controller.signal.aborted) setError(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (requestRef.current === controller) {
|
||||
requestRef.current = null;
|
||||
if (generation === generationRef.current) setLoading(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[artworkId]
|
||||
);
|
||||
reactExports.useEffect(() => {
|
||||
if (initialized.current) return;
|
||||
initialized.current = true;
|
||||
if (artworkId && initialComments.length === 0) {
|
||||
loadComments(1);
|
||||
} else {
|
||||
setTotal(initialComments.length);
|
||||
generationRef.current += 1;
|
||||
requestRef.current?.abort();
|
||||
requestRef.current = null;
|
||||
setComments2([]);
|
||||
setLoading(false);
|
||||
setError(false);
|
||||
setPage(1);
|
||||
setLastPage(1);
|
||||
setTotal(0);
|
||||
const section = sectionRef.current;
|
||||
if (!section || !artworkId) return void 0;
|
||||
let fallbackTimer = null;
|
||||
let observer = null;
|
||||
let established = false;
|
||||
const trigger = () => {
|
||||
observer?.disconnect();
|
||||
if (!requestRef.current) loadComments(1);
|
||||
};
|
||||
if (typeof window !== "undefined" && typeof window.IntersectionObserver === "function") {
|
||||
try {
|
||||
observer = new window.IntersectionObserver((entries) => {
|
||||
if (entries.some((entry) => entry.isIntersecting)) trigger();
|
||||
}, { rootMargin: "1000px 0px" });
|
||||
observer.observe(section);
|
||||
established = true;
|
||||
} catch {
|
||||
observer?.disconnect();
|
||||
}
|
||||
}, [artworkId, initialComments.length, loadComments]);
|
||||
}
|
||||
if (!established && typeof window !== "undefined") {
|
||||
fallbackTimer = window.setTimeout(trigger, 1200);
|
||||
}
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
if (fallbackTimer !== null && typeof window !== "undefined") window.clearTimeout(fallbackTimer);
|
||||
requestRef.current?.abort();
|
||||
requestRef.current = null;
|
||||
};
|
||||
}, [artworkId, loadComments]);
|
||||
const handlePosted = reactExports.useCallback((newComment) => {
|
||||
const comment = { ...newComment, replies: newComment.replies || [] };
|
||||
setComments((prev) => [comment, ...prev]);
|
||||
setComments2((prev) => [comment, ...prev]);
|
||||
setTotal((t) => t + 1);
|
||||
}, []);
|
||||
const handleReplyPosted = reactExports.useCallback((parentId, newReply) => {
|
||||
@@ -41491,10 +41533,10 @@ function ArtworkComments({
|
||||
}
|
||||
return c;
|
||||
});
|
||||
setComments((prev) => insertReply(prev));
|
||||
setComments2((prev) => insertReply(prev));
|
||||
setTotal((t) => t + 1);
|
||||
}, []);
|
||||
return /* @__PURE__ */ React.createElement("section", { "aria-label": "Comments", className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold tracking-tight text-white sm:text-xl" }, "Comments"), total > 0 && /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full bg-white/[0.06] px-2.5 py-0.5 text-xs font-medium tabular-nums text-white/50" }, total)), loading && comments.length === 0 ? /* @__PURE__ */ React.createElement(Skeleton$1, null) : comments.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-center gap-3 rounded-2xl border border-dashed border-white/[0.08] bg-white/[0.015] px-6 py-10 text-center" }, /* @__PURE__ */ React.createElement(ChatBubbleIcon$1, null), /* @__PURE__ */ React.createElement("p", { className: "text-sm font-medium text-white/40" }, "No comments yet"), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-white/25" }, "Be the first to share your thoughts.")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("ul", { className: "space-y-3 sm:space-y-4" }, comments.map((comment) => /* @__PURE__ */ React.createElement(
|
||||
return /* @__PURE__ */ React.createElement("section", { ref: sectionRef, "aria-label": "Comments", className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement("h2", { className: "text-lg font-semibold tracking-tight text-white sm:text-xl" }, "Comments"), total > 0 && /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center rounded-full bg-white/[0.06] px-2.5 py-0.5 text-xs font-medium tabular-nums text-white/50" }, total)), loading && comments.length === 0 ? /* @__PURE__ */ React.createElement(Skeleton$1, null) : error && comments.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "rounded-2xl border border-dashed border-white/[0.08] bg-white/[0.015] px-6 py-8 text-center" }, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-medium text-white/45" }, "Comments could not be loaded."), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => loadComments(1), className: "mt-3 rounded-full border border-white/[0.1] px-4 py-2 text-xs font-medium text-white/65 hover:bg-white/[0.06]" }, "Try again")) : comments.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-col items-center gap-3 rounded-2xl border border-dashed border-white/[0.08] bg-white/[0.015] px-6 py-10 text-center" }, /* @__PURE__ */ React.createElement(ChatBubbleIcon$1, null), /* @__PURE__ */ React.createElement("p", { className: "text-sm font-medium text-white/40" }, "No comments yet"), /* @__PURE__ */ React.createElement("p", { className: "text-xs text-white/25" }, "Be the first to share your thoughts.")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("ul", { className: "space-y-3 sm:space-y-4" }, comments.map((comment) => /* @__PURE__ */ React.createElement(
|
||||
CommentItem$3,
|
||||
{
|
||||
key: comment.id,
|
||||
@@ -42906,6 +42948,20 @@ function ArtworkRecommendationsRails({ artwork, related = [], similarApiData = [
|
||||
return /* @__PURE__ */ React.createElement("div", { className: "space-y-14" }, /* @__PURE__ */ React.createElement(Rail, { title: "Similar Artworks", emoji: "✨", items: similarItems, seeAllHref: similarHref }), /* @__PURE__ */ React.createElement(Rail, { title: trendingLabel, emoji: "🔥", items: trendingRailItems, seeAllHref: trendingHref }));
|
||||
}
|
||||
const fallbackCache = /* @__PURE__ */ new Map();
|
||||
function normalizeNavigation(data) {
|
||||
if (!data || typeof data !== "object") return null;
|
||||
const hasPrev = data.prev_id == null || data.prev_url && data.prev_slug;
|
||||
const hasNext = data.next_id == null || data.next_url && data.next_slug;
|
||||
if (!hasPrev || !hasNext) return null;
|
||||
return {
|
||||
prevId: data.prev_id ?? null,
|
||||
nextId: data.next_id ?? null,
|
||||
prevUrl: data.prev_url ?? null,
|
||||
nextUrl: data.next_url ?? null,
|
||||
prevPreview: data.prev_preview ?? null,
|
||||
nextPreview: data.next_preview ?? null
|
||||
};
|
||||
}
|
||||
async function fetchFallback(artworkId) {
|
||||
const key = String(artworkId);
|
||||
if (fallbackCache.has(key)) return fallbackCache.get(key);
|
||||
@@ -42913,27 +42969,25 @@ async function fetchFallback(artworkId) {
|
||||
const res = await fetch(`/api/artworks/navigation/${artworkId}`, {
|
||||
headers: { Accept: "application/json" }
|
||||
});
|
||||
if (!res.ok) return { prevId: null, nextId: null, prevUrl: null, nextUrl: null };
|
||||
if (!res.ok) return { prevId: null, nextId: null, prevUrl: null, nextUrl: null, prevPreview: null, nextPreview: null };
|
||||
const data = await res.json();
|
||||
const result = {
|
||||
prevId: data.prev_id ?? null,
|
||||
nextId: data.next_id ?? null,
|
||||
prevUrl: data.prev_url ?? null,
|
||||
nextUrl: data.next_url ?? null
|
||||
};
|
||||
const result = normalizeNavigation(data) || { prevId: null, nextId: null, prevUrl: null, nextUrl: null, prevPreview: null, nextPreview: null };
|
||||
fallbackCache.set(key, result);
|
||||
return result;
|
||||
} catch {
|
||||
return { prevId: null, nextId: null, prevUrl: null, nextUrl: null };
|
||||
return { prevId: null, nextId: null, prevUrl: null, nextUrl: null, prevPreview: null, nextPreview: null };
|
||||
}
|
||||
}
|
||||
function useNavContext(currentArtworkId) {
|
||||
function useNavContext(currentArtworkId, embeddedNavigation = null) {
|
||||
const getNeighbors = reactExports.useCallback(async () => {
|
||||
const embedded = normalizeNavigation(embeddedNavigation);
|
||||
if (embedded) return embedded;
|
||||
return fetchFallback(currentArtworkId);
|
||||
}, [currentArtworkId]);
|
||||
}, [currentArtworkId, embeddedNavigation]);
|
||||
return { getNeighbors };
|
||||
}
|
||||
const preloadCache = /* @__PURE__ */ new Set();
|
||||
const prefetchCache = /* @__PURE__ */ new Set();
|
||||
function scheduleIdleTask(callback, delay = 1200) {
|
||||
if (typeof window === "undefined") {
|
||||
callback();
|
||||
@@ -42953,8 +43007,8 @@ function preloadImage(src2) {
|
||||
const img = new Image();
|
||||
img.src = src2;
|
||||
}
|
||||
function ArtworkNavigator({ artworkId, onNavigate, onOpenViewer, onReady }) {
|
||||
const { getNeighbors } = useNavContext(artworkId);
|
||||
function ArtworkNavigator({ artworkId, navigation: navigation2 = null, onNavigate, onOpenViewer, onReady }) {
|
||||
const { getNeighbors } = useNavContext(artworkId, navigation2);
|
||||
const [neighbors, setNeighbors] = reactExports.useState({ prevId: null, nextId: null, prevUrl: null, nextUrl: null });
|
||||
const navigatingRef = reactExports.useRef(false);
|
||||
const neighborsRef = reactExports.useRef(neighbors);
|
||||
@@ -42989,22 +43043,45 @@ function ArtworkNavigator({ artworkId, onNavigate, onOpenViewer, onReady }) {
|
||||
reactExports.useEffect(() => {
|
||||
const ids = [neighbors.prevId, neighbors.nextId].filter(Boolean);
|
||||
if (ids.length === 0) return void 0;
|
||||
const previews = /* @__PURE__ */ new Map([
|
||||
[neighbors.prevId, neighbors.prevPreview],
|
||||
[neighbors.nextId, neighbors.nextPreview]
|
||||
]);
|
||||
let cancelled = false;
|
||||
const cancelIdleTask = scheduleIdleTask(() => {
|
||||
ids.forEach((id) => {
|
||||
fetch(`/api/artworks/${id}/page`, { headers: { Accept: "application/json" } }).then((r) => r.ok ? r.json() : null).then((data) => {
|
||||
if (cancelled || !data) return;
|
||||
if (prefetchCache.has(id)) return;
|
||||
prefetchCache.add(id);
|
||||
const preview = previews.get(id);
|
||||
if (preview) {
|
||||
if (!cancelled) {
|
||||
const imgUrl = preview.thumbs?.lg?.url || preview.thumbs?.md?.url;
|
||||
if (imgUrl) preloadImage(imgUrl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
fetch(`/api/artworks/${id}/page?prefetch=1`, { headers: { Accept: "application/json" } }).then((r) => {
|
||||
if (!r.ok) {
|
||||
prefetchCache.delete(id);
|
||||
return null;
|
||||
}
|
||||
return r.json();
|
||||
}).then((data) => {
|
||||
if (!data) {
|
||||
prefetchCache.delete(id);
|
||||
return;
|
||||
}
|
||||
if (cancelled) return;
|
||||
const imgUrl = data.thumbs?.lg?.url || data.thumbs?.md?.url;
|
||||
if (imgUrl) preloadImage(imgUrl);
|
||||
}).catch(() => {
|
||||
});
|
||||
}).catch(() => prefetchCache.delete(id));
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
cancelIdleTask();
|
||||
};
|
||||
}, [neighbors.prevId, neighbors.nextId]);
|
||||
}, [neighbors.prevId, neighbors.nextId, neighbors.prevPreview, neighbors.nextPreview]);
|
||||
const navigate = reactExports.useCallback(async (targetId, targetUrl) => {
|
||||
if (!targetId && !targetUrl) return;
|
||||
if (navigatingRef.current) return;
|
||||
@@ -43194,6 +43271,140 @@ function GroupSummaryPanel({ group, artwork }) {
|
||||
if (!group) return null;
|
||||
return /* @__PURE__ */ React.createElement("section", { className: "overflow-hidden rounded-[28px] border border-white/[0.08] bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_36%),linear-gradient(180deg,rgba(255,255,255,0.06),rgba(255,255,255,0.02))] px-5 py-5 shadow-[0_22px_55px_rgba(0,0,0,0.26)] backdrop-blur-xl sm:px-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-4" }, /* @__PURE__ */ React.createElement("a", { href: group.urls?.public || "/groups", className: "flex h-16 w-16 shrink-0 items-center justify-center overflow-hidden rounded-2xl border border-white/10 bg-white/[0.04]" }, group.avatar_url ? /* @__PURE__ */ React.createElement("img", { src: group.avatar_url, alt: 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: "text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80" }, "Published as a group"), /* @__PURE__ */ React.createElement("a", { href: group.urls?.public || "/groups", className: "mt-2 block text-xl font-semibold tracking-[-0.02em] text-white transition hover:text-sky-300" }, group.name), /* @__PURE__ */ React.createElement("p", { className: "mt-2 text-sm leading-6 text-white/65" }, group.headline || group.bio_excerpt || `${artwork?.title || "This artwork"} is credited to a collaborative group identity on Skinbase.`))), /* @__PURE__ */ React.createElement("div", { className: "mt-4 flex flex-wrap gap-2" }, (Array.isArray(group.trust_signals) ? group.trust_signals : []).slice(0, 3).map((signal) => /* @__PURE__ */ React.createElement(GroupBadgePill, { key: signal.key, label: signal.label, tone: signal.tone })), group.is_recruiting ? /* @__PURE__ */ React.createElement(GroupBadgePill, { label: "Recruiting", tone: "emerald" }) : null), /* @__PURE__ */ React.createElement("div", { className: "mt-5 grid grid-cols-3 gap-2 rounded-2xl border border-white/10 bg-black/20 p-3 text-center" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-lg font-semibold text-white" }, NUMBER_FORMATTER.format(Number(group.counts?.artworks || 0))), /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-[0.16em] text-slate-500" }, "Artworks")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-lg font-semibold text-white" }, NUMBER_FORMATTER.format(Number(group.counts?.members || 0))), /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-[0.16em] text-slate-500" }, "Members")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-lg font-semibold text-white" }, NUMBER_FORMATTER.format(Number(group.counts?.followers || 0))), /* @__PURE__ */ React.createElement("div", { className: "text-[11px] uppercase tracking-[0.16em] text-slate-500" }, "Followers"))), /* @__PURE__ */ React.createElement("div", { className: "mt-5 flex flex-wrap gap-2" }, /* @__PURE__ */ React.createElement("a", { href: group.urls?.public || "/groups", className: "rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-sm font-semibold text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15" }, "Open group page"), group.urls?.releases ? /* @__PURE__ */ React.createElement("a", { href: group.urls.releases, className: "rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.07]" }, "Browse releases") : null));
|
||||
}
|
||||
const REGISTRY = /* @__PURE__ */ new Map();
|
||||
const ROOT_MARGIN = "800px 0px";
|
||||
const IDLE_TIMEOUT = 3e3;
|
||||
function scheduleIdle(callback) {
|
||||
if (typeof window.requestIdleCallback === "function") {
|
||||
const handle2 = window.requestIdleCallback(callback, { timeout: IDLE_TIMEOUT });
|
||||
return () => window.cancelIdleCallback?.(handle2);
|
||||
}
|
||||
const handle = window.setTimeout(callback, IDLE_TIMEOUT);
|
||||
return () => window.clearTimeout(handle);
|
||||
}
|
||||
function useDeferredSimilarAi(artworkId, anchorRef) {
|
||||
const [state, setState] = reactExports.useState(() => ({
|
||||
id: artworkId,
|
||||
items: REGISTRY.get(Number(artworkId || 0))?.items || []
|
||||
}));
|
||||
const [loading, setLoading] = reactExports.useState(false);
|
||||
const [error, setError] = reactExports.useState(false);
|
||||
reactExports.useEffect(() => {
|
||||
let cancelled = false;
|
||||
const id = Number(artworkId || 0);
|
||||
const controller = new AbortController();
|
||||
let attachedEntry = null;
|
||||
let triggered = false;
|
||||
const currentEntry = REGISTRY.get(id);
|
||||
const cached = currentEntry?.status === "completed" ? currentEntry.items : null;
|
||||
setState({ id, items: cached || [] });
|
||||
setLoading(false);
|
||||
setError(false);
|
||||
if (!id || cached) {
|
||||
return () => controller.abort();
|
||||
}
|
||||
let stopIdle = () => {
|
||||
};
|
||||
let observer = null;
|
||||
let observationEstablished = false;
|
||||
const detach = () => {
|
||||
if (!attachedEntry) return;
|
||||
attachedEntry.subscribers -= 1;
|
||||
const entry = attachedEntry;
|
||||
attachedEntry = null;
|
||||
if (entry.subscribers === 0 && entry.status === "in-flight") {
|
||||
entry.controller.abort();
|
||||
}
|
||||
};
|
||||
const attach = (entry) => {
|
||||
if (cancelled || attachedEntry === entry) return;
|
||||
attachedEntry = entry;
|
||||
entry.subscribers += 1;
|
||||
setLoading(true);
|
||||
entry.promise.then((nextItems) => {
|
||||
if (!cancelled) setState({ id, items: nextItems });
|
||||
}).catch((requestError) => {
|
||||
if (!cancelled && requestError?.name !== "AbortError") {
|
||||
setState({ id, items: [] });
|
||||
setError(true);
|
||||
}
|
||||
}).finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
if (attachedEntry === entry) {
|
||||
attachedEntry = null;
|
||||
entry.subscribers -= 1;
|
||||
}
|
||||
});
|
||||
};
|
||||
const load = async () => {
|
||||
if (triggered || cancelled) return;
|
||||
triggered = true;
|
||||
observer?.disconnect();
|
||||
stopIdle();
|
||||
let entry = REGISTRY.get(id);
|
||||
if (entry?.status === "completed") {
|
||||
if (!cancelled) setState({ id, items: entry.items });
|
||||
return;
|
||||
}
|
||||
if (entry?.status !== "in-flight" || entry.controller.signal.aborted) {
|
||||
const requestController = new AbortController();
|
||||
entry = {
|
||||
status: "in-flight",
|
||||
controller: requestController,
|
||||
promise: null,
|
||||
subscribers: 0
|
||||
};
|
||||
REGISTRY.set(id, entry);
|
||||
entry.promise = Promise.resolve().then(() => fetch(`/api/art/${id}/similar-ai`, {
|
||||
credentials: "same-origin",
|
||||
signal: requestController.signal
|
||||
})).then((response) => {
|
||||
if (!response.ok) throw new Error("similar fetch failed");
|
||||
return response.json();
|
||||
}).then((payload) => {
|
||||
const nextItems = Array.isArray(payload?.data) ? payload.data : [];
|
||||
if (REGISTRY.get(id) === entry) {
|
||||
REGISTRY.set(id, { status: "completed", items: nextItems });
|
||||
}
|
||||
return nextItems;
|
||||
}).catch((requestError) => {
|
||||
if (REGISTRY.get(id) === entry) REGISTRY.delete(id);
|
||||
throw requestError;
|
||||
});
|
||||
entry.promise.catch(() => {
|
||||
});
|
||||
}
|
||||
attach(entry);
|
||||
};
|
||||
if (typeof window.IntersectionObserver === "function" && anchorRef?.current) {
|
||||
try {
|
||||
observer = new window.IntersectionObserver((entries) => {
|
||||
if (entries.some((entry) => entry.isIntersecting)) load();
|
||||
}, { rootMargin: ROOT_MARGIN });
|
||||
observer.observe(anchorRef.current);
|
||||
observationEstablished = true;
|
||||
} catch {
|
||||
observer?.disconnect();
|
||||
observer = null;
|
||||
}
|
||||
}
|
||||
if (!observationEstablished) {
|
||||
stopIdle = scheduleIdle(load);
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
observer?.disconnect();
|
||||
stopIdle();
|
||||
detach();
|
||||
controller.abort();
|
||||
};
|
||||
}, [artworkId, anchorRef]);
|
||||
return {
|
||||
items: Number(state.id || 0) === Number(artworkId || 0) ? state.items : [],
|
||||
loading: Number(state.id || 0) === Number(artworkId || 0) ? loading : false,
|
||||
error: Number(state.id || 0) === Number(artworkId || 0) ? error : false
|
||||
};
|
||||
}
|
||||
function publisherToGroupSummary(publisher) {
|
||||
if (!publisher || publisher.type !== "group") return null;
|
||||
return {
|
||||
@@ -43215,7 +43426,7 @@ function publisherToGroupSummary(publisher) {
|
||||
}
|
||||
};
|
||||
}
|
||||
function ArtworkPage({ artwork: initialArtwork, related: initialRelated, presentMd: initialMd, presentLg: initialLg, presentXl: initialXl, presentSq: initialSq, canonicalUrl: initialCanonical, isAuthenticated = false, comments: initialComments = [], groupSummary: initialGroupSummary = null, reactionTotals: initialReactionTotals = {}, seo = null }) {
|
||||
function ArtworkPage({ artwork: initialArtwork, navigation: initialNavigation = null, related: initialRelated, presentMd: initialMd, presentLg: initialLg, presentXl: initialXl, presentSq: initialSq, canonicalUrl: initialCanonical, isAuthenticated = false, groupSummary: initialGroupSummary = null, reactionTotals: initialReactionTotals = {}, seo = null }) {
|
||||
const [viewerOpen, setViewerOpen] = reactExports.useState(false);
|
||||
const [showMatureArtwork, setShowMatureArtwork] = reactExports.useState(false);
|
||||
const openViewer = reactExports.useCallback(() => setViewerOpen(true), []);
|
||||
@@ -43236,12 +43447,12 @@ function ArtworkPage({ artwork: initialArtwork, related: initialRelated, present
|
||||
const [presentXl, setPresentXl] = reactExports.useState(initialXl);
|
||||
const [presentSq, setPresentSq] = reactExports.useState(initialSq);
|
||||
const [related, setRelated] = reactExports.useState(initialRelated);
|
||||
const [comments, setComments] = reactExports.useState(initialComments);
|
||||
const [canonicalUrl, setCanonicalUrl] = reactExports.useState(initialCanonical);
|
||||
const [groupSummary, setGroupSummary] = reactExports.useState(initialGroupSummary || publisherToGroupSummary(initialArtwork?.publisher));
|
||||
const [selectedMediaId, setSelectedMediaId] = reactExports.useState("cover");
|
||||
const [similarRecommendations, setSimilarRecommendations] = reactExports.useState([]);
|
||||
const [trendingRecommendations, setTrendingRecommendations] = reactExports.useState([]);
|
||||
const similarAiAnchorRef = reactExports.useRef(null);
|
||||
const { items: deferredSimilarRecommendations } = useDeferredSimilarAi(artwork?.id, similarAiAnchorRef);
|
||||
const [navState, setNavState] = reactExports.useState({ hasPrev: false, hasNext: false, navigatePrev: null, navigateNext: null });
|
||||
const initialArtworkIdRef = reactExports.useRef(initialArtwork?.id);
|
||||
const [reactionTotals, setReactionTotals] = reactExports.useState(initialReactionTotals ?? {});
|
||||
@@ -43250,27 +43461,6 @@ function ArtworkPage({ artwork: initialArtwork, related: initialRelated, present
|
||||
if (artwork.id === initialArtworkIdRef.current) return;
|
||||
axios.get(`/api/artworks/${artwork.id}/reactions`).then(({ data }) => setReactionTotals(data.totals ?? {})).catch(() => setReactionTotals({}));
|
||||
}, [artwork?.id]);
|
||||
reactExports.useEffect(() => {
|
||||
let isCancelled = false;
|
||||
const loadSimilarRecommendations = async () => {
|
||||
if (!artwork?.id) {
|
||||
setSimilarRecommendations([]);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/art/${artwork.id}/similar-ai`, { credentials: "same-origin" });
|
||||
if (!response.ok) throw new Error("similar fetch failed");
|
||||
const payload = await response.json();
|
||||
if (!isCancelled) setSimilarRecommendations(payload?.data || []);
|
||||
} catch {
|
||||
if (!isCancelled) setSimilarRecommendations([]);
|
||||
}
|
||||
};
|
||||
loadSimilarRecommendations();
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}, [artwork?.id]);
|
||||
reactExports.useEffect(() => {
|
||||
let isCancelled = false;
|
||||
const loadTrendingRecommendations = async () => {
|
||||
@@ -43308,7 +43498,6 @@ function ArtworkPage({ artwork: initialArtwork, related: initialRelated, present
|
||||
setCanonicalUrl(data.canonical_url ?? window.location.href);
|
||||
setGroupSummary(data.group_summary ?? publisherToGroupSummary(data.publisher));
|
||||
setSelectedMediaId("cover");
|
||||
setSimilarRecommendations([]);
|
||||
setTrendingRecommendations([]);
|
||||
setViewerOpen(false);
|
||||
setShowMatureArtwork(false);
|
||||
@@ -43424,22 +43613,22 @@ function ArtworkPage({ artwork: initialArtwork, related: initialRelated, present
|
||||
ArtworkComments,
|
||||
{
|
||||
artworkId: artwork.id,
|
||||
comments,
|
||||
isLoggedIn: isAuthenticated,
|
||||
loginUrl: "/login"
|
||||
}
|
||||
)), /* @__PURE__ */ React.createElement("aside", { className: "space-y-5 lg:sticky lg:top-6 lg:self-start" }, /* @__PURE__ */ React.createElement(CreatorSpotlight, { artwork, presentSq, related }), groupSummary ? /* @__PURE__ */ React.createElement(GroupSummaryPanel, { group: groupSummary, artwork }) : null, /* @__PURE__ */ React.createElement(ArtworkDetailsPanel, { artwork, stats: liveStats }), /* @__PURE__ */ React.createElement(ArtworkAwards, { artwork, initialAwards, isAuthenticated })))), /* @__PURE__ */ React.createElement("div", { className: "mt-14 w-full max-w-screen-2xl mx-auto min-h-[640px]" }, /* @__PURE__ */ React.createElement(
|
||||
)), /* @__PURE__ */ React.createElement("aside", { className: "space-y-5 lg:sticky lg:top-6 lg:self-start" }, /* @__PURE__ */ React.createElement(CreatorSpotlight, { artwork, presentSq, related }), groupSummary ? /* @__PURE__ */ React.createElement(GroupSummaryPanel, { group: groupSummary, artwork }) : null, /* @__PURE__ */ React.createElement(ArtworkDetailsPanel, { artwork, stats: liveStats }), /* @__PURE__ */ React.createElement(ArtworkAwards, { artwork, initialAwards, isAuthenticated })))), /* @__PURE__ */ React.createElement("div", { ref: similarAiAnchorRef, className: "mt-14 w-full max-w-screen-2xl mx-auto min-h-[640px]" }, /* @__PURE__ */ React.createElement(
|
||||
ArtworkRecommendationsRails,
|
||||
{
|
||||
artwork,
|
||||
related,
|
||||
similarApiData: similarRecommendations,
|
||||
similarApiData: deferredSimilarRecommendations,
|
||||
trendingData: trendingRecommendations
|
||||
}
|
||||
))), /* @__PURE__ */ React.createElement(
|
||||
ArtworkNavigator,
|
||||
{
|
||||
artworkId: artwork.id,
|
||||
navigation: artwork.navigation || initialNavigation,
|
||||
onNavigate: handleNavigate,
|
||||
onOpenViewer: openViewer,
|
||||
onReady: setNavState
|
||||
@@ -79881,7 +80070,7 @@ function CollectionShow() {
|
||||
reportEndpoint
|
||||
} = props;
|
||||
const [collection, setCollection] = React.useState(initialCollection);
|
||||
const [comments, setComments] = React.useState(initialComments || []);
|
||||
const [comments, setComments2] = React.useState(initialComments || []);
|
||||
const [submissions, setSubmissions] = React.useState(initialSubmissions || []);
|
||||
const [selectedArtworkId, setSelectedArtworkId] = React.useState(submissionArtworkOptions?.[0]?.id || "");
|
||||
const [state, setState] = React.useState({
|
||||
@@ -80148,12 +80337,12 @@ function CollectionShow() {
|
||||
method: "POST",
|
||||
body: { body: body2 }
|
||||
});
|
||||
setComments(payload?.comments || []);
|
||||
setComments2(payload?.comments || []);
|
||||
setCollection((current) => ({ ...current, comments_count: payload?.comments_count ?? current.comments_count }));
|
||||
}
|
||||
async function handleDeleteComment(commentId) {
|
||||
const payload = await requestJson$l(`${commentsEndpoint}/${commentId}`, { method: "DELETE" });
|
||||
setComments(payload?.comments || []);
|
||||
setComments2(payload?.comments || []);
|
||||
setCollection((current) => ({ ...current, comments_count: payload?.comments_count ?? current.comments_count }));
|
||||
}
|
||||
async function handleSubmitArtwork() {
|
||||
@@ -82717,7 +82906,7 @@ const FILTER_TABS = [
|
||||
];
|
||||
function LatestCommentsPage({ initialComments = [], initialMeta = {}, isAuthenticated = false }) {
|
||||
const [activeFilter, setActiveFilter] = reactExports.useState("all");
|
||||
const [comments, setComments] = reactExports.useState(initialComments);
|
||||
const [comments, setComments2] = reactExports.useState(initialComments);
|
||||
const [meta, setMeta] = reactExports.useState(initialMeta);
|
||||
const [loading, setLoading] = reactExports.useState(false);
|
||||
const [error, setError] = reactExports.useState(null);
|
||||
@@ -82733,7 +82922,7 @@ function LatestCommentsPage({ initialComments = [], initialMeta = {}, isAuthenti
|
||||
});
|
||||
if (res.status === 401) {
|
||||
setError("Please log in to view this feed.");
|
||||
setComments([]);
|
||||
setComments2([]);
|
||||
setMeta({});
|
||||
return;
|
||||
}
|
||||
@@ -82742,7 +82931,7 @@ function LatestCommentsPage({ initialComments = [], initialMeta = {}, isAuthenti
|
||||
return;
|
||||
}
|
||||
const json = await res.json();
|
||||
setComments(json.data ?? []);
|
||||
setComments2(json.data ?? []);
|
||||
setMeta(json.meta ?? {});
|
||||
} catch {
|
||||
setError("Network error. Please try again.");
|
||||
@@ -83131,7 +83320,7 @@ function formatRelative$1(isoString) {
|
||||
return `${d2}d ago`;
|
||||
}
|
||||
function PostComments({ postId, isLoggedIn, isOwn = false, initialCount = 0 }) {
|
||||
const [comments, setComments] = reactExports.useState([]);
|
||||
const [comments, setComments2] = reactExports.useState([]);
|
||||
const [loading, setLoading] = reactExports.useState(false);
|
||||
const [submitting, setSubmitting] = reactExports.useState(false);
|
||||
const [body2, setBody] = reactExports.useState("");
|
||||
@@ -83144,7 +83333,7 @@ function PostComments({ postId, isLoggedIn, isOwn = false, initialCount = 0 }) {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await axios.get(`/api/posts/${postId}/comments`, { params: { page: p } });
|
||||
setComments((prev) => p === 1 ? data.data : [...prev, ...data.data]);
|
||||
setComments2((prev) => p === 1 ? data.data : [...prev, ...data.data]);
|
||||
setHasMore(data.meta.current_page < data.meta.last_page);
|
||||
setPage(p);
|
||||
} catch {
|
||||
@@ -83163,7 +83352,7 @@ function PostComments({ postId, isLoggedIn, isOwn = false, initialCount = 0 }) {
|
||||
setError(null);
|
||||
try {
|
||||
const { data } = await axios.post(`/api/posts/${postId}/comments`, { body: body2 });
|
||||
setComments((prev) => [...prev, data.comment]);
|
||||
setComments2((prev) => [...prev, data.comment]);
|
||||
setBody("");
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.message ?? "Failed to post comment.");
|
||||
@@ -83175,7 +83364,7 @@ function PostComments({ postId, isLoggedIn, isOwn = false, initialCount = 0 }) {
|
||||
if (!window.confirm("Delete this comment?")) return;
|
||||
try {
|
||||
await axios.delete(`/api/posts/${postId}/comments/${commentId}`);
|
||||
setComments((prev) => prev.filter((c) => c.id !== commentId));
|
||||
setComments2((prev) => prev.filter((c) => c.id !== commentId));
|
||||
} catch {
|
||||
}
|
||||
};
|
||||
@@ -83183,12 +83372,12 @@ function PostComments({ postId, isLoggedIn, isOwn = false, initialCount = 0 }) {
|
||||
try {
|
||||
if (comment.is_highlighted) {
|
||||
await axios.delete(`/api/posts/${postId}/comments/${comment.id}/highlight`);
|
||||
setComments(
|
||||
setComments2(
|
||||
(prev) => prev.map((c) => c.id === comment.id ? { ...c, is_highlighted: false } : c)
|
||||
);
|
||||
} else {
|
||||
await axios.post(`/api/posts/${postId}/comments/${comment.id}/highlight`);
|
||||
setComments(
|
||||
setComments2(
|
||||
(prev) => prev.map((c) => ({ ...c, is_highlighted: c.id === comment.id }))
|
||||
);
|
||||
}
|
||||
@@ -94339,7 +94528,7 @@ function Skeleton() {
|
||||
)));
|
||||
}
|
||||
function NewsComments({ articleId, isLoggedIn = false, loginUrl = "/login" }) {
|
||||
const [comments, setComments] = reactExports.useState([]);
|
||||
const [comments, setComments2] = reactExports.useState([]);
|
||||
const [loading, setLoading] = reactExports.useState(false);
|
||||
const [page, setPage] = reactExports.useState(1);
|
||||
const [lastPage, setLastPage] = reactExports.useState(1);
|
||||
@@ -94352,9 +94541,9 @@ function NewsComments({ articleId, isLoggedIn = false, loginUrl = "/login" }) {
|
||||
try {
|
||||
const { data } = await axios.get(`/api/news/articles/${articleId}/comments?page=${nextPage}`);
|
||||
if (nextPage === 1) {
|
||||
setComments(data.data ?? []);
|
||||
setComments2(data.data ?? []);
|
||||
} else {
|
||||
setComments((current) => [...current, ...data.data ?? []]);
|
||||
setComments2((current) => [...current, ...data.data ?? []]);
|
||||
}
|
||||
setPage(data.meta?.current_page ?? nextPage);
|
||||
setLastPage(data.meta?.last_page ?? 1);
|
||||
@@ -94371,7 +94560,7 @@ function NewsComments({ articleId, isLoggedIn = false, loginUrl = "/login" }) {
|
||||
}, [loadComments]);
|
||||
const handlePosted = reactExports.useCallback((newComment) => {
|
||||
const comment = { ...newComment, replies: newComment.replies || [] };
|
||||
setComments((current) => [comment, ...current]);
|
||||
setComments2((current) => [comment, ...current]);
|
||||
setTotal((current) => current + 1);
|
||||
}, []);
|
||||
const handleReplyPosted = reactExports.useCallback((parentId, newReply) => {
|
||||
@@ -94384,7 +94573,7 @@ function NewsComments({ articleId, isLoggedIn = false, loginUrl = "/login" }) {
|
||||
}
|
||||
return comment;
|
||||
});
|
||||
setComments((current) => insertReply(current));
|
||||
setComments2((current) => insertReply(current));
|
||||
setTotal((current) => current + 1);
|
||||
}, []);
|
||||
const handleDelete = reactExports.useCallback(async (commentId) => {
|
||||
@@ -95510,7 +95699,8 @@ const TABS$1 = [
|
||||
{ id: "about", label: "About", icon: "fa-id-card" },
|
||||
{ id: "stats", label: "Stats", icon: "fa-chart-bar" },
|
||||
{ id: "favourites", label: "Favourites", icon: "fa-heart" },
|
||||
{ id: "activity", label: "Activity", icon: "fa-bolt" }
|
||||
{ id: "activity", label: "Activity", icon: "fa-bolt" },
|
||||
{ id: "followers", label: "Followers", icon: "fa-users" }
|
||||
];
|
||||
function ProfileTabs({ activeTab, onTabChange }) {
|
||||
const navRef = reactExports.useRef(null);
|
||||
@@ -98120,12 +98310,65 @@ function TabWorlds({ worldHistory, isOwner }) {
|
||||
/* @__PURE__ */ React.createElement(ProfileWorldHistorySection, { history: 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"
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function TabFollowers({ listing, username, followerCount }) {
|
||||
const items = Array.isArray(listing?.data) ? listing.data : [];
|
||||
const total = Number(listing?.total ?? followerCount ?? 0);
|
||||
const currentPage = Number(listing?.current_page ?? 1);
|
||||
const lastPage = Number(listing?.last_page ?? 1);
|
||||
const prevUrl = listing?.prev_page_url ?? null;
|
||||
const nextUrl = listing?.next_page_url ?? null;
|
||||
return /* @__PURE__ */ React.createElement(
|
||||
"div",
|
||||
{
|
||||
id: "tabpanel-followers",
|
||||
role: "tabpanel",
|
||||
"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())),
|
||||
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);
|
||||
return /* @__PURE__ */ React.createElement("li", { key: follower.id }, /* @__PURE__ */ React.createElement(
|
||||
"a",
|
||||
{
|
||||
href: follower.profile_url,
|
||||
className: "flex items-center gap-3 rounded-2xl border border-white/8 bg-white/[0.03] px-3 py-3 transition-colors hover:border-sky-300/25 hover:bg-white/[0.06]"
|
||||
},
|
||||
follower.avatar_url ? /* @__PURE__ */ React.createElement(
|
||||
"img",
|
||||
{
|
||||
src: follower.avatar_url,
|
||||
alt: "",
|
||||
className: "h-12 w-12 rounded-xl object-cover"
|
||||
}
|
||||
) : /* @__PURE__ */ React.createElement("span", { className: "flex h-12 w-12 items-center justify-center rounded-xl bg-white/5 text-slate-500" }, /* @__PURE__ */ React.createElement("i", { className: "fa-solid fa-user" })),
|
||||
/* @__PURE__ */ React.createElement("span", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("span", { className: "block truncate text-sm font-semibold text-white" }, handle), follower.username ? /* @__PURE__ */ React.createElement("span", { className: "block truncate text-xs text-slate-400" }, "@", follower.username) : null, /* @__PURE__ */ React.createElement("span", { className: "mt-1 block text-[11px] text-slate-500" }, [follower.rank, followedAt ? `Followed ${followedAt}` : null].filter(Boolean).join(" · ")))
|
||||
));
|
||||
})),
|
||||
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 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 VALID_TABS = ["posts", "artworks", "stories", "achievements", "worlds", "collections", "about", "stats", "favourites", "activity"];
|
||||
const VALID_TABS = ["posts", "artworks", "stories", "achievements", "worlds", "collections", "about", "stats", "favourites", "activity", "followers"];
|
||||
function getInitialTab(initialTab = "posts") {
|
||||
if (typeof window === "undefined") {
|
||||
return VALID_TABS.includes(initialTab) ? initialTab : "posts";
|
||||
@@ -98154,6 +98397,7 @@ function ProfileShow() {
|
||||
socialLinks,
|
||||
followerCount,
|
||||
recentFollowers,
|
||||
followersListing,
|
||||
followContext,
|
||||
followAnalytics,
|
||||
suggestedUsers,
|
||||
@@ -98184,6 +98428,11 @@ function ProfileShow() {
|
||||
const [activeTab, setActiveTab] = reactExports.useState(() => getInitialTab(initialTab));
|
||||
const handleTabChange = reactExports.useCallback((tab2) => {
|
||||
if (!VALID_TABS.includes(tab2)) return;
|
||||
if (tab2 === "followers") {
|
||||
const targetBase = profileTabUrls?.followers || `${profileUrl || ""}/followers`;
|
||||
window.location.assign(targetBase);
|
||||
return;
|
||||
}
|
||||
setActiveTab(tab2);
|
||||
try {
|
||||
const currentUrl = new URL(window.location.href);
|
||||
@@ -98344,6 +98593,13 @@ function ProfileShow() {
|
||||
followerCount,
|
||||
creatorStories
|
||||
}
|
||||
), activeTab === "followers" && /* @__PURE__ */ React.createElement(
|
||||
TabFollowers,
|
||||
{
|
||||
listing: followersListing,
|
||||
username: user.username || user.name,
|
||||
followerCount
|
||||
}
|
||||
))));
|
||||
}
|
||||
const __vite_glob_0_110 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
||||
|
||||
Reference in New Issue
Block a user