chore: transfer repo
This commit is contained in:
122
app/product/[handle]/page.tsx
Normal file
122
app/product/[handle]/page.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { BackButton } from '@/components/product/BackButton';
|
||||
import { ProductDescription } from '@/components/product/ProductDescription';
|
||||
import { container } from '@/lib/utils';
|
||||
import { ProductProvider } from 'components/product/product-context';
|
||||
import { ProductDetailsSection } from 'components/product/ProductDetailsSection';
|
||||
import { ProductGallery } from 'components/product/ProductGallery';
|
||||
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
||||
import { getProduct } from 'lib/shopify';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ handle: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const params = await props.params;
|
||||
const product = await getProduct(params.handle);
|
||||
|
||||
if (!product) return notFound();
|
||||
|
||||
const { url, width, height, altText: alt } = product.featuredImage || {};
|
||||
const indexable = !product.tags.includes(HIDDEN_PRODUCT_TAG);
|
||||
|
||||
return {
|
||||
title: product.seo.title || product.title,
|
||||
description: product.seo.description || product.description,
|
||||
robots: {
|
||||
index: indexable,
|
||||
follow: indexable,
|
||||
googleBot: {
|
||||
index: indexable,
|
||||
follow: indexable
|
||||
}
|
||||
},
|
||||
openGraph: url
|
||||
? {
|
||||
images: [
|
||||
{
|
||||
url,
|
||||
width,
|
||||
height,
|
||||
alt
|
||||
}
|
||||
]
|
||||
}
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ProductPage(props: { params: Promise<{ handle: string }> }) {
|
||||
const params = await props.params;
|
||||
const product = await getProduct(params.handle);
|
||||
|
||||
if (!product) return notFound();
|
||||
|
||||
const productJsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Product',
|
||||
name: product.title,
|
||||
description: product.description,
|
||||
image: product.featuredImage.url,
|
||||
offers: {
|
||||
'@type': 'AggregateOffer',
|
||||
availability: product.availableForSale
|
||||
? 'https://schema.org/InStock'
|
||||
: 'https://schema.org/OutOfStock',
|
||||
priceCurrency: product.priceRange.minVariantPrice.currencyCode,
|
||||
highPrice: product.priceRange.maxVariantPrice.amount,
|
||||
lowPrice: product.priceRange.minVariantPrice.amount
|
||||
}
|
||||
};
|
||||
|
||||
// If product has only one image, duplicate it to create the gallery effect
|
||||
const galleryImages = product.images.length === 1
|
||||
? Array(5).fill(product.images[0]).map(image => ({
|
||||
url: image.url,
|
||||
altText: image.altText,
|
||||
width: image.width,
|
||||
height: image.height
|
||||
}))
|
||||
: product.images;
|
||||
|
||||
return (
|
||||
<ProductProvider>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(productJsonLd)
|
||||
}}
|
||||
/>
|
||||
<div className="py-4">
|
||||
<div className={container}>
|
||||
<BackButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={container}>
|
||||
<div className="flex flex-col md:flex-row lg:gap-8">
|
||||
<div className="w-full lg:w-3/5">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="relative aspect-square h-full max-h-[600px] w-full overflow-hidden" />
|
||||
}
|
||||
>
|
||||
<ProductGallery
|
||||
images={galleryImages.slice(0, 5)}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-2/5 mt-8 lg:mt-0">
|
||||
<Suspense fallback={null}>
|
||||
<ProductDescription product={product} />
|
||||
<ProductDetailsSection />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ProductProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user