Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/template/components/product-card/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function ProductCardImage({
{fallbackTitle}
</div>
)}
{hasSlideshow && <ProductCardSlideshow images={images} sizes={sizes} />}
{hasSlideshow && !outOfStock && <ProductCardSlideshow images={images} sizes={sizes} />}
{outOfStock && (
<div className="absolute inset-0 bg-black/60 flex items-center justify-center">
<span className="text-destructive-foreground font-medium text-xs px-2 py-1 bg-destructive rounded">
Expand Down Expand Up @@ -216,6 +216,7 @@ function ProductCardSkeleton({
}

export {
aspectRatioClasses,
ProductCard,
type ProductCardAspectRatio,
ProductCardBadge,
Expand Down
28 changes: 19 additions & 9 deletions apps/template/components/product-detail/product-detail-page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Suspense } from "react";

import {
aspectRatioClasses,
type ProductCardAspectRatio,
} from "@/components/product-card/components";
import { ProductSchema } from "@/components/product-detail/schema";
import { RelatedProductsSection } from "@/components/product/related-products-section";
import { BreadcrumbSchema } from "@/components/schema/breadcrumb-schema";
Expand All @@ -10,6 +14,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import { siteConfig } from "@/lib/config";
import type { Locale } from "@/lib/i18n";
import type { ProductDetails } from "@/lib/types";
import { cn } from "@/lib/utils";

import { ProductDetailSection } from "./product-detail-section";

Expand All @@ -24,22 +29,21 @@ function ProductBreadcrumbSchema({ title, handle }: { title: string; handle: str
);
}

function ProductPageFallback() {
function ProductPageFallback({ aspectRatio }: { aspectRatio: ProductCardAspectRatio }) {
const tile = cn("w-full rounded-none", aspectRatioClasses);
return (
<Sections>
<div className="grid gap-10 lg:grid-cols-10 lg:items-start lg:gap-5">
<div className="lg:col-span-6">
{/* Mobile: single full-bleed square + pagination space */}
<div className="grid gap-5 lg:hidden -mx-5">
<Skeleton className="aspect-square w-full rounded-none" />
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
<div className="h-1.5" />
</div>
{/* Desktop: 2×2 grid */}
<div className="hidden lg:grid grid-cols-2 gap-2.5">
<Skeleton className="aspect-square w-full rounded-none" />
<Skeleton className="aspect-square w-full rounded-none" />
<Skeleton className="aspect-square w-full rounded-none" />
<Skeleton className="aspect-square w-full rounded-none" />
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
</div>
</div>
<div className="grid gap-10 lg:sticky lg:top-20 lg:col-span-4">
Expand All @@ -55,10 +59,12 @@ async function ProductContent({
productPromise,
locale,
variantIdPromise,
aspectRatio,
}: {
productPromise: Promise<ProductDetails>;
locale: Locale;
variantIdPromise: Promise<string | undefined>;
aspectRatio: ProductCardAspectRatio;
}) {
const product = await productPromise;
const { handle, title } = product;
Expand Down Expand Up @@ -86,6 +92,7 @@ async function ProductContent({
product={product}
locale={locale}
variantIdPromise={variantIdPromise}
aspectRatio={aspectRatio}
/>
<RelatedProductsSection handle={handle} locale={locale} />
</Sections>
Expand All @@ -97,19 +104,22 @@ export async function ProductDetailPage({
productPromise,
locale,
variantIdPromise,
aspectRatio = "square",
}: {
productPromise: Promise<ProductDetails>;
locale: Locale;
variantIdPromise: Promise<string | undefined>;
aspectRatio?: ProductCardAspectRatio;
}) {
return (
<Page className="pt-0">
<Container className="bg-background">
<Suspense fallback={<ProductPageFallback />}>
<Suspense fallback={<ProductPageFallback aspectRatio={aspectRatio} />}>
<ProductContent
productPromise={productPromise}
locale={locale}
variantIdPromise={variantIdPromise}
aspectRatio={aspectRatio}
/>
</Suspense>
</Container>
Expand Down
46 changes: 39 additions & 7 deletions apps/template/components/product-detail/product-detail-section.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { getTranslations } from "next-intl/server";
import { Suspense } from "react";

import {
aspectRatioClasses,
type ProductCardAspectRatio,
} from "@/components/product-card/components";
import { BuyButtons } from "@/components/product-detail/buy-buttons";
import {
ProductInfoDescription,
Expand Down Expand Up @@ -36,10 +40,12 @@ export async function ProductDetailSection({
product,
locale,
variantIdPromise,
aspectRatio = "square",
}: {
product: ProductDetails;
locale: Locale;
variantIdPromise: Promise<string | undefined>;
aspectRatio?: ProductCardAspectRatio;
}) {
const { handle, title, featuredImage, images, videos, variants, options } = product;

Expand All @@ -66,15 +72,28 @@ export async function ProductDetailSection({
otherImages={getSharedImages(images, options, variants)}
videos={videos}
title={title}
aspectRatio={aspectRatio}
className="lg:col-span-6"
desktopSlot={
<Suspense
fallback={
<>
<Skeleton className="aspect-square w-full rounded-none" />
<Skeleton className="aspect-square w-full rounded-none" />
<Skeleton className="aspect-square w-full rounded-none" />
<Skeleton className="aspect-square w-full rounded-none" />
<Skeleton
data-aspect-ratio={aspectRatio}
className={cn("w-full rounded-none", aspectRatioClasses)}
/>
<Skeleton
data-aspect-ratio={aspectRatio}
className={cn("w-full rounded-none", aspectRatioClasses)}
/>
<Skeleton
data-aspect-ratio={aspectRatio}
className={cn("w-full rounded-none", aspectRatioClasses)}
/>
<Skeleton
data-aspect-ratio={aspectRatio}
className={cn("w-full rounded-none", aspectRatioClasses)}
/>
</>
}
>
Expand All @@ -83,14 +102,21 @@ export async function ProductDetailSection({
options={options}
variants={variants}
title={title}
aspectRatio={aspectRatio}
variantIdPromise={variantIdPromise}
/>
</Suspense>
}
mobileSlot={
<Suspense
fallback={
<div className="relative shrink-0 w-full aspect-square snap-start snap-always overflow-hidden">
<div
data-aspect-ratio={aspectRatio}
className={cn(
"relative shrink-0 w-full snap-start snap-always overflow-hidden",
aspectRatioClasses,
)}
>
<Skeleton className="size-full rounded-none" />
</div>
}
Expand All @@ -100,6 +126,7 @@ export async function ProductDetailSection({
options={options}
variants={variants}
title={title}
aspectRatio={aspectRatio}
variantIdPromise={variantIdPromise}
/>
</Suspense>
Expand All @@ -110,6 +137,7 @@ export async function ProductDetailSection({
otherImages={images}
videos={videos}
title={title}
aspectRatio={aspectRatio}
className="lg:col-span-6"
/>
)}
Expand Down Expand Up @@ -297,12 +325,14 @@ async function ResolvedColorImages({
options,
variants,
title,
aspectRatio,
variantIdPromise,
}: {
images: ImageType[];
options: ProductOption[];
variants: ProductVariant[];
title: string;
aspectRatio: ProductCardAspectRatio;
variantIdPromise: Promise<string | undefined>;
}) {
const variantId = await variantIdPromise;
Expand All @@ -316,20 +346,22 @@ async function ResolvedColorImages({

if (colorImages.length === 0) return null;

return <ColorImageGrid images={colorImages} title={title} />;
return <ColorImageGrid images={colorImages} title={title} aspectRatio={aspectRatio} />;
}

async function ResolvedColorCarouselImages({
images,
options,
variants,
title,
aspectRatio,
variantIdPromise,
}: {
images: ImageType[];
options: ProductOption[];
variants: ProductVariant[];
title: string;
aspectRatio: ProductCardAspectRatio;
variantIdPromise: Promise<string | undefined>;
}) {
const variantId = await variantIdPromise;
Expand All @@ -343,5 +375,5 @@ async function ResolvedColorCarouselImages({

if (colorImages.length === 0) return null;

return <ColorImageCarouselItems images={colorImages} title={title} />;
return <ColorImageCarouselItems images={colorImages} title={title} aspectRatio={aspectRatio} />;
}
Loading