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 + ) + )} +
-
- ); - })} -
+ ); + })} +
+ )}