- Update ProductCard to use Saleor Product type - Update products listing page to fetch from Saleor - Update product detail page with Saleor integration - Add language switching support (SR/EN) - Add SEO metadata generation - Implement static params generation for all product slugs - Add availability checking based on variant quantity
172 lines
5.6 KiB
TypeScript
172 lines
5.6 KiB
TypeScript
import Image from "next/image";
|
|
import { getProductBySlug, getProducts, getProductPrice, getProductImage, getLocalizedProduct, formatPrice } from "@/lib/saleor";
|
|
import Header from "@/components/layout/Header";
|
|
import Footer from "@/components/layout/Footer";
|
|
import type { Product } from "@/types/saleor";
|
|
|
|
interface ProductPageProps {
|
|
params: Promise<{ slug: string; locale?: string }>;
|
|
}
|
|
|
|
// Generate static params for all products
|
|
export async function generateStaticParams() {
|
|
try {
|
|
const products = await getProducts("SR", 100);
|
|
const params: Array<{ slug: string; locale: string }> = [];
|
|
|
|
products.forEach((product: Product) => {
|
|
// Serbian slug
|
|
params.push({ slug: product.slug, locale: "sr" });
|
|
|
|
// English slug (if translation exists)
|
|
if (product.translation?.slug) {
|
|
params.push({ slug: product.translation.slug, locale: "en" });
|
|
}
|
|
});
|
|
|
|
return params;
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function generateMetadata({ params }: ProductPageProps) {
|
|
const { slug, locale = "sr" } = await params;
|
|
const product = await getProductBySlug(slug, locale.toUpperCase());
|
|
|
|
if (!product) {
|
|
return {
|
|
title: locale === "en" ? "Product Not Found" : "Proizvod nije pronađen",
|
|
};
|
|
}
|
|
|
|
const localized = getLocalizedProduct(product, locale.toUpperCase());
|
|
|
|
return {
|
|
title: localized.name,
|
|
description: localized.seoDescription || localized.description?.slice(0, 160),
|
|
alternates: {
|
|
canonical: `/products/${product.slug}`,
|
|
languages: {
|
|
"sr": `/products/${product.slug}`,
|
|
"en": product.translation?.slug ? `/products/${product.translation.slug}` : `/products/${product.slug}`,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
export default async function ProductPage({ params }: ProductPageProps) {
|
|
const { slug, locale = "sr" } = await params;
|
|
const product = await getProductBySlug(slug, locale.toUpperCase());
|
|
|
|
if (!product) {
|
|
return (
|
|
<main className="min-h-screen">
|
|
<Header />
|
|
<div className="pt-24 text-center">
|
|
<h1 className="text-2xl">
|
|
{locale === "en" ? "Product not found" : "Proizvod nije pronađen"}
|
|
</h1>
|
|
</div>
|
|
<Footer />
|
|
</main>
|
|
);
|
|
}
|
|
|
|
const localized = getLocalizedProduct(product, locale.toUpperCase());
|
|
const image = getProductImage(product);
|
|
const price = getProductPrice(product);
|
|
const variant = product.variants?.[0];
|
|
const isAvailable = variant?.quantityAvailable > 0;
|
|
|
|
// Determine language based on which slug matched
|
|
const isEnglishSlug = slug === product.translation?.slug;
|
|
const currentLocale = isEnglishSlug ? "en" : "sr";
|
|
|
|
// URLs for language switcher
|
|
const serbianUrl = `/products/${product.slug}`;
|
|
const englishUrl = product.translation?.slug
|
|
? `/products/${product.translation.slug}`
|
|
: serbianUrl;
|
|
|
|
return (
|
|
<main className="min-h-screen">
|
|
<Header />
|
|
|
|
<section className="pt-24 pb-20 px-4">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
|
|
{/* Product Image */}
|
|
<div className="relative aspect-[4/5] bg-background-ice overflow-hidden">
|
|
<Image
|
|
src={image}
|
|
alt={localized.name}
|
|
fill
|
|
className="object-cover"
|
|
priority
|
|
/>
|
|
</div>
|
|
|
|
{/* Product Info */}
|
|
<div className="flex flex-col">
|
|
<h1 className="text-4xl font-serif mb-4">{localized.name}</h1>
|
|
|
|
{price && (
|
|
<div className="text-2xl mb-6">{price}</div>
|
|
)}
|
|
|
|
{localized.description && (
|
|
<div
|
|
className="prose max-w-none mb-8"
|
|
dangerouslySetInnerHTML={{ __html: localized.description }}
|
|
/>
|
|
)}
|
|
|
|
{/* Add to Cart Button */}
|
|
<button
|
|
className="inline-block bg-foreground text-white px-8 py-4 text-lg font-medium text-center hover:bg-opacity-90 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
disabled={!isAvailable}
|
|
>
|
|
{isAvailable
|
|
? (currentLocale === "en" ? "Add to Cart" : "Dodaj u korpu")
|
|
: (currentLocale === "en" ? "Out of Stock" : "Nema na stanju")
|
|
}
|
|
</button>
|
|
|
|
{/* SKU */}
|
|
{variant?.sku && (
|
|
<p className="mt-4 text-sm text-foreground-muted">
|
|
SKU: {variant.sku}
|
|
</p>
|
|
)}
|
|
|
|
{/* Language Switcher */}
|
|
<div className="mt-8 pt-8 border-t">
|
|
<p className="text-sm text-foreground-muted mb-2">
|
|
{currentLocale === "en" ? "Language:" : "Jezik:"}
|
|
</p>
|
|
<div className="flex gap-4">
|
|
<a
|
|
href={serbianUrl}
|
|
className={`text-sm font-medium ${currentLocale === "sr" ? "text-foreground" : "text-foreground-muted hover:text-foreground"}`}
|
|
>
|
|
🇷🇸 Srpski
|
|
</a>
|
|
<a
|
|
href={englishUrl}
|
|
className={`text-sm font-medium ${currentLocale === "en" ? "text-foreground" : "text-foreground-muted hover:text-foreground"}`}
|
|
>
|
|
🇬🇧 English
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<Footer />
|
|
</main>
|
|
);
|
|
}
|