From eb7d0bbcdca1418476bf5e497c1f2f6b51b54db5 Mon Sep 17 00:00:00 2001
From: Your Name
Date: Sun, 31 May 2026 21:37:12 +0100
Subject: [PATCH] feat: add loading skeleton for creator holdings list
---
src/components/common/CreatorSkeleton.tsx | 44 ++++++++++++++
src/pages/LandingPage.tsx | 71 ++++++++++++-----------
2 files changed, 82 insertions(+), 33 deletions(-)
diff --git a/src/components/common/CreatorSkeleton.tsx b/src/components/common/CreatorSkeleton.tsx
index 3f5aa3c..67d00a1 100644
--- a/src/components/common/CreatorSkeleton.tsx
+++ b/src/components/common/CreatorSkeleton.tsx
@@ -121,4 +121,48 @@ export const CreatorProfileHeaderSkeleton: React.FC<{
);
};
+/**
+ * Loading skeleton for a single creator holding card (#304). Matches the
+ * structure in `LandingPage` (rounded-2xl, border, p-4) with placeholders for
+ * the creator title and holdings/price metadata.
+ */
+export const CreatorHoldingsSkeleton: React.FC<{
+ className?: string;
+ disableShimmer?: boolean;
+}> = ({ className, disableShimmer = false }) => {
+ const blockClass = disableShimmer
+ ? skeletonStaticBlockClass
+ : skeletonBlockClass;
+
+ return (
+
+ );
+};
+
+/**
+ * A grid of creator holdings skeletons to be shown while the portfolio is
+ * loading. Matches the 3-column responsive grid used for the live list.
+ */
+export const CreatorHoldingsListSkeleton: React.FC<{
+ count?: number;
+ disableShimmer?: boolean;
+ className?: string;
+}> = ({ count = 3, disableShimmer = false, className }) => {
+ return (
+
+ {Array.from({ length: count }).map((_, i) => (
+
+ ))}
+
+ );
+};
+
export default CreatorSkeleton;
diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx
index 5c90e81..80d8066 100644
--- a/src/pages/LandingPage.tsx
+++ b/src/pages/LandingPage.tsx
@@ -8,6 +8,7 @@ import StickyFilterBar from '@/components/common/StickyFilterBar';
import CreatorCard from '@/components/common/CreatorCard';
import {
CreatorGridSkeleton,
+ CreatorHoldingsListSkeleton,
CreatorProfileHeaderSkeleton,
} from '@/components/common/CreatorSkeleton';
import EmptyState from '@/components/common/EmptyState';
@@ -906,40 +907,44 @@ function LandingPage() {
-
- {heldKeyPositions
- .filter(
- position =>
- position.quantity && position.quantity > 0
- )
- .map(position => {
- const creator = creators.find(
- item => item.id === position.creatorId
- );
- return (
-
-
- {creator?.title ?? 'Unknown creator'}
-
-
- {formatNumber(position.quantity)} keys ·{' '}
- {position.isPriceLoading
- ? 'Refreshing price'
- : position.isPriceStale
- ? 'Price stale'
- : formatDisplayKeyPrice(
- resolveCreatorKeyPriceStroops(
- position
- )
- )}
+ {isLoading ? (
+
+ ) : (
+
+ {heldKeyPositions
+ .filter(
+ position =>
+ position.quantity && position.quantity > 0
+ )
+ .map(position => {
+ const creator = creators.find(
+ item => item.id === position.creatorId
+ );
+ return (
+
+
+ {creator?.title ?? 'Unknown creator'}
+
+
+ {formatNumber(position.quantity)} keys ·{' '}
+ {position.isPriceLoading
+ ? 'Refreshing price'
+ : position.isPriceStale
+ ? 'Price stale'
+ : formatDisplayKeyPrice(
+ resolveCreatorKeyPriceStroops(
+ position
+ )
+ )}
+
-
- );
- })}
-
+ );
+ })}
+
+ )}