From ee6a56fea7611aa7a11346f65e45cc8781ec8eb4 Mon Sep 17 00:00:00 2001 From: William Luke Date: Fri, 10 Apr 2026 10:59:07 +0300 Subject: [PATCH] fix: preserve layout wrapper in EditableImageOverlay when not editing EditableImageOverlay returned a bare fragment when canEdit was false, stripping the relative positioning context and className props that children depend on for absolute positioning. This caused hero sections on voucher and pool pages to break during loading before auth resolved. Also adds skeleton placeholders for async content in both hero sections. Co-Authored-By: Claude Opus 4.6 --- .../pools/[address]/pool-client-page.tsx | 42 ++++++++++++++++--- src/components/editable-image-overlay.tsx | 2 +- .../voucher/voucher-hero-section.tsx | 15 ++++--- src/components/voucher/voucher-home-tab.tsx | 20 +++++++-- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/app/(main)/pools/[address]/pool-client-page.tsx b/src/app/(main)/pools/[address]/pool-client-page.tsx index 30ab7093..309a242a 100644 --- a/src/app/(main)/pools/[address]/pool-client-page.tsx +++ b/src/app/(main)/pools/[address]/pool-client-page.tsx @@ -10,6 +10,7 @@ import { EditableImageOverlay } from "~/components/editable-image-overlay"; import { ContentContainer } from "~/components/layout/content-container"; import { useSwapPool } from "~/components/pools/hooks"; import { Badge } from "~/components/ui/badge"; +import { Skeleton } from "~/components/ui/skeleton"; import { useAuth } from "~/hooks/use-auth"; import { useIsContractOwner } from "~/hooks/use-is-owner"; import { trpc } from "~/lib/trpc"; @@ -22,7 +23,7 @@ export function PoolClientPage() { const { address } = useParams<{ address: string }>(); const pool_address = getAddress(address); const { data: pool } = useSwapPool(pool_address); - const { data: metadata } = trpc.pool.get.useQuery(pool_address); + const { data: metadata, isLoading: isMetadataLoading } = trpc.pool.get.useQuery(pool_address); const isOwner = useIsContractOwner(pool_address); const auth = useAuth(); @@ -143,7 +144,13 @@ export function PoolClientPage() { {/* Tags */} - {metadata?.tags && metadata.tags.length > 0 && ( + {isMetadataLoading ? ( +
+ + + +
+ ) : metadata?.tags && metadata.tags.length > 0 ? (
@@ -158,14 +165,19 @@ export function PoolClientPage() { ))}
- )} + ) : null} {/* Description */} - {metadata?.swap_pool_description && ( + {isMetadataLoading ? ( +
+ + +
+ ) : metadata?.swap_pool_description ? (

{metadata.swap_pool_description}

- )} + ) : null} {/* Action Buttons */}
{pool && }
@@ -180,8 +192,26 @@ export function PoolClientPage() { {/* Modern Tabs Section */}
- {metadata && pool && ( + {metadata && pool ? ( + ) : ( +
+
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+ +
+
+
+ {Array.from({ length: 8 }).map((_, i) => ( + + ))} +
+
)}
diff --git a/src/components/editable-image-overlay.tsx b/src/components/editable-image-overlay.tsx index 676101a4..858ebeb0 100644 --- a/src/components/editable-image-overlay.tsx +++ b/src/components/editable-image-overlay.tsx @@ -44,7 +44,7 @@ export function EditableImageOverlay({ const { uploadFile } = useFileUpload(); if (!canEdit) { - return <>{children}; + return
{children}
; } const onSelectFile = (e: React.ChangeEvent) => { diff --git a/src/components/voucher/voucher-hero-section.tsx b/src/components/voucher/voucher-hero-section.tsx index 837854c6..c0d396f7 100644 --- a/src/components/voucher/voucher-hero-section.tsx +++ b/src/components/voucher/voucher-hero-section.tsx @@ -4,6 +4,7 @@ import Image from "next/image"; import { toast } from "sonner"; import { EditableImageOverlay } from "~/components/editable-image-overlay"; import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"; +import { Skeleton } from "~/components/ui/skeleton"; import { BasicVoucherFunctions } from "~/components/voucher/voucher-contract-functions"; import { useAuth } from "~/hooks/use-auth"; import { trpc } from "~/lib/trpc"; @@ -24,7 +25,7 @@ export function VoucherHeroSection({ }: VoucherHeroSectionProps) { const auth = useAuth(); const utils = trpc.useUtils(); - const { data: voucher } = trpc.voucher.byAddress.useQuery( + const { data: voucher, isLoading } = trpc.voucher.byAddress.useQuery( { voucherAddress: address }, { enabled: !!address, @@ -130,25 +131,29 @@ export function VoucherHeroSection({

{details?.symbol}

- {voucher?.voucher_type && ( + {isLoading ? ( + + ) : voucher?.voucher_type ? ( - )} + ) : null} {/* Voucher Value */} - {voucher?.voucher_value && voucher?.voucher_uoa && ( + {isLoading ? ( + + ) : voucher?.voucher_value && voucher?.voucher_uoa ? (

1 {details?.symbol} = {voucher.voucher_value}{" "} {voucher.voucher_uoa} of Products

- )} + ) : null} {/* Action Buttons */}
diff --git a/src/components/voucher/voucher-home-tab.tsx b/src/components/voucher/voucher-home-tab.tsx index 013c9cf7..0064178c 100644 --- a/src/components/voucher/voucher-home-tab.tsx +++ b/src/components/voucher/voucher-home-tab.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { useState } from "react"; import { OfferList } from "~/components/products/offer-list"; import { Card, CardContent } from "~/components/ui/card"; +import { Skeleton } from "~/components/ui/skeleton"; import { useContractSinkAddress } from "~/hooks/use-sink-address"; import { useContractOwner } from "~/hooks/use-is-owner"; import { trpc } from "~/lib/trpc"; @@ -21,7 +22,7 @@ export function VoucherHomeTab({ }: VoucherHomeTabProps) { const [showSigners, setShowSigners] = useState(false); - const { data: voucher } = trpc.voucher.byAddress.useQuery( + const { data: voucher, isLoading } = trpc.voucher.byAddress.useQuery( { voucherAddress }, { enabled: !!voucherAddress, @@ -35,7 +36,20 @@ export function VoucherHomeTab({ ); return (
- {voucher?.voucher_description && ( + {isLoading ? ( + + + + + +
+ + + +
+
+
+ ) : voucher?.voucher_description ? (

@@ -161,7 +175,7 @@ export function VoucherHomeTab({ )} - )} + ) : null}