Redesign phase 1: Homepage polish and design system foundation

- Fix newsletter subscribe box centering on homepage
- Fix header overlap on product pages (pt-[72px] instead of pt-[100px])
- Add scroll-mt-[72px] for smooth scroll anchor offset
- Add HeroVideo component with video hero placeholder
- Add REDESIGN_SPECIFICATION.md with 9-phase design plan
- Clean up globals.css theme declarations and comments
- Update Header with improved sticky behavior and cart
- Update ProductDetail with better layout and spacing
- Update CartDrawer with improved slide-out cart UI
- Add English translations for updated pages
- Various CSS refinements across pages
This commit is contained in:
Unchained
2026-03-21 16:22:17 +02:00
parent 9d639fbd64
commit 7c05bd2346
22 changed files with 2653 additions and 884 deletions

View File

@@ -1,70 +1,97 @@
import { getProducts } from "@/lib/woocommerce";
import { getProductBySlug, getProducts, getLocalizedProduct } from "@/lib/saleor";
import Header from "@/components/layout/Header";
import Footer from "@/components/layout/Footer";
import ProductDetail from "@/components/product/ProductDetail";
import type { Product } from "@/types/saleor";
import type { Metadata } from "next";
export const dynamic = 'force-dynamic';
interface ProductPageProps {
params: Promise<{ slug: string }>;
}
// Disable static generation - this page will be server-rendered
export const generateStaticParams = undefined;
export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
let product = null;
// Generate static params for all products
export async function generateStaticParams() {
try {
const products = await getProducts();
product = products.find((p) => (p.slug || p.id.toString()) === slug);
const products = await getProducts("EN", 100);
const params: Array<{ slug: string }> = [];
products.forEach((product: Product) => {
// English slug (if translation exists)
if (product.translation?.slug) {
params.push({ slug: product.translation.slug });
} else {
params.push({ slug: product.slug });
}
});
return params;
} catch (e) {
// Fallback
return [];
}
}
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
const { slug } = await params;
const product = await getProductBySlug(slug, "EN");
if (!product) {
return {
title: "Product Not Found",
};
}
const localized = getLocalizedProduct(product, "EN");
return {
title: localized.name,
description: localized.seoDescription || localized.description?.slice(0, 160),
};
}
export default async function ProductPage({ params }: ProductPageProps) {
const { slug } = await params;
const product = await getProductBySlug(slug, "EN");
if (!product) {
return (
<main className="min-h-screen">
<main className="min-h-screen bg-white">
<Header />
<div className="pt-24 text-center">
<h1 className="text-2xl">Product not found</h1>
<div className="pt-[120px] text-center px-4">
<h1 className="text-2xl font-medium mb-4">Product not found</h1>
<p className="text-[#666666] mb-8">
The product you&apos;re looking for doesn&apos;t exist or has been removed.
</p>
<a
href="/products"
className="inline-block px-8 py-3 bg-black text-white text-sm uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors"
>
Browse Products
</a>
</div>
<Footer />
</main>
);
}
const image = product.images?.[0]?.src || '/placeholder.jpg';
const price = product.sale_price || product.price;
// Get related products
let relatedProducts: Product[] = [];
try {
const allProducts = await getProducts("EN", 8);
relatedProducts = allProducts
.filter((p: Product) => p.id !== product.id)
.slice(0, 4);
} catch (e) {
// Ignore error
}
return (
<main className="min-h-screen">
<main className="min-h-screen bg-white">
<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">
<div className="relative aspect-[4/5] bg-background-ice overflow-hidden">
<img
src={image}
alt={product.name}
className="object-cover w-full h-full"
/>
</div>
<div className="flex flex-col">
<h1 className="text-4xl font-serif mb-4">{product.name}</h1>
<div className="text-2xl mb-6">{price} RSD</div>
<div className="prose max-w-none mb-8" dangerouslySetInnerHTML={{ __html: product.description || '' }} />
<button
className="inline-block bg-foreground text-white px-8 py-4 text-lg font-medium text-center hover:bg-opacity-90 transition-all"
>
Add to Cart
</button>
</div>
</div>
</div>
</section>
<ProductDetail
product={product}
relatedProducts={relatedProducts}
locale="EN"
/>
<Footer />
</main>
);

View File

@@ -1,7 +1,8 @@
import { getProducts } from "@/lib/woocommerce";
import { getProducts } from "@/lib/saleor";
import Header from "@/components/layout/Header";
import Footer from "@/components/layout/Footer";
import ProductCard from "@/components/product/ProductCard";
import { ChevronDown } from "lucide-react";
export const metadata = {
title: "Products - ManoonOils",
@@ -9,38 +10,71 @@ export const metadata = {
};
export default async function ProductsPage() {
let products: any[] = [];
try {
products = await getProducts();
} catch (e) {
console.log('Failed to fetch products during build');
}
const publishedProducts = products.filter((p) => p.status === "publish");
const products = await getProducts("EN");
return (
<main className="min-h-screen pt-16 md:pt-20">
<>
<Header />
<section className="py-20 px-4">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl md:text-5xl font-serif text-center mb-16">
All Products
</h1>
{publishedProducts.length === 0 ? (
<p className="text-center text-foreground-muted">No products available</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{publishedProducts.map((product, index) => (
<ProductCard key={product.id} product={product} index={index} />
))}
<main className="min-h-screen bg-white">
{/* Page Header */}
<div className="pt-[104px]">
<div className="border-b border-[#e5e5e5]">
<div className="container py-8 md:py-12">
<div className="flex flex-col md:flex-row md:items-end md:justify-between gap-4">
<div>
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-2 block">Our Collection</span>
<h1 className="text-3xl md:text-4xl font-medium">All Products</h1>
</div>
{/* Sort Dropdown */}
<div className="flex items-center gap-3">
<span className="text-sm text-[#666666]">{products.length} products</span>
<div className="relative">
<select
className="appearance-none bg-transparent border border-[#e5e5e5] pl-4 pr-10 py-2 text-sm focus:outline-none focus:border-black cursor-pointer"
defaultValue="featured"
>
<option value="featured">Featured</option>
<option value="newest">Newest</option>
<option value="price-low">Price: Low to High</option>
<option value="price-high">Price: High to Low</option>
</select>
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none text-[#666666]" />
</div>
</div>
</div>
</div>
)}
</div>
{/* Products Grid */}
<section className="py-12 md:py-16">
<div className="container">
{products.length === 0 ? (
<div className="text-center py-20">
<p className="text-[#666666] mb-4">No products available</p>
<p className="text-sm text-[#999999]">Please check back later for new arrivals.</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
{products.map((product, index) => (
<ProductCard
key={product.id}
product={product}
index={index}
locale="EN"
/>
))}
</div>
)}
</div>
</section>
</div>
</section>
</main>
<Footer />
</main>
<div className="pt-16">
<Footer />
</div>
</>
);
}