diff --git a/apps/web/src/app/App.tsx b/apps/web/src/app/App.tsx index fcde8eb..65df60e 100644 --- a/apps/web/src/app/App.tsx +++ b/apps/web/src/app/App.tsx @@ -46,7 +46,7 @@ function StorefrontLayout() { } /> } /> - } /> + } /> } /> } /> } /> diff --git a/apps/web/src/app/components/ProductCard.tsx b/apps/web/src/app/components/ProductCard.tsx index b8b9ffa..42fd6ba 100644 --- a/apps/web/src/app/components/ProductCard.tsx +++ b/apps/web/src/app/components/ProductCard.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { Link } from "react-router"; import { Heart } from "lucide-react"; -import { Product } from "../data/products"; +import { getProductPath, Product } from "../data/products"; import { cartStore } from "../store/cartStore"; interface ProductCardProps { @@ -21,7 +21,7 @@ export function ProductCard({ product, className = "" }: ProductCardProps) { return ( setHovered(true)} onMouseLeave={() => setHovered(false)} diff --git a/apps/web/src/app/data/products.ts b/apps/web/src/app/data/products.ts index 3a1a5ad..dfa7562 100644 --- a/apps/web/src/app/data/products.ts +++ b/apps/web/src/app/data/products.ts @@ -1,12 +1,13 @@ export interface Product { id: string; + slug: string; brand: string; name: string; price: number; originalPrice?: number; image: string; hoverImage: string; - category: string; + category: ProductCategory; tag?: string; isNew?: boolean; isBestSeller?: boolean; @@ -18,213 +19,163 @@ export interface Product { colors?: { name: string; hex: string }[]; } -export const products: Product[] = [ - { - id: "1", - brand: "MAISON VOSS", - name: "Structured Wool Blazer", - price: 1290, - image: "https://images.unsplash.com/photo-1629511565591-a1d494ad6c58?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1613915617430-8ab0fd7c6baf?w=800&h=1000&fit=crop&auto=format", - category: "Fashion", - tag: "NEW", - isNew: true, - rating: 4.8, - reviewCount: 124, - description: "Crafted from the finest Italian wool, this blazer embodies understated power. The structured silhouette moves with precision — a testament to old-world tailoring elevated for modern wardrobes.", - details: ["100% Italian Merino Wool", "Fully lined in silk charmeuse", "Notched lapel", "Two-button closure", "Dry clean only"], - sizes: ["XS", "S", "M", "L", "XL"], - colors: [{ name: "Midnight", hex: "#111111" }, { name: "Ivory", hex: "#F8F8F6" }, { name: "Camel", hex: "#C8A96B" }] - }, - { - id: "2", - brand: "LUMIÈRE", - name: "Silk Charmeuse Slip Dress", - price: 895, - image: "https://images.unsplash.com/photo-1662532577856-e8ee8b138a8b?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1770062422093-ae32c8fed2a3?w=800&h=1000&fit=crop&auto=format", - category: "Fashion", - isNew: true, - rating: 4.9, - reviewCount: 87, - description: "An ode to effortless luxury. Bias-cut pure silk cascades over the body with liquid ease. From private dinners to gallery openings, this is the dress that transcends occasion.", - details: ["100% Mulberry Silk", "Bias cut construction", "Adjustable spaghetti straps", "Side zip closure", "Dry clean recommended"], - sizes: ["XS", "S", "M", "L"], - colors: [{ name: "Champagne", hex: "#C8A96B" }, { name: "Onyx", hex: "#111111" }, { name: "Blush", hex: "#E8C4B8" }] - }, - { - id: "3", - brand: "CROFT & VALE", - name: "Leather Tote — Obsidian", - price: 1850, - image: "https://images.unsplash.com/photo-1590739225287-bd31519780c3?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1559563458-527698bf5295?w=800&h=1000&fit=crop&auto=format", - category: "Accessories", - isBestSeller: true, - rating: 4.7, - reviewCount: 203, - description: "Handcrafted in Florence from full-grain calfskin, the Obsidian Tote is built to last a lifetime. Its clean geometry and minimal hardware speak volumes without saying a word.", - details: ["Full-grain Italian calfskin", "Suede interior lining", "Gold-tone hardware", "Magnetic closure", "Interior zip pocket", "Made in Italy"], - colors: [{ name: "Obsidian", hex: "#111111" }, { name: "Cognac", hex: "#8B4513" }, { name: "Ecru", hex: "#F8F8F6" }] - }, - { - id: "4", - brand: "VELORIA", - name: "Eau de Parfum — Sève Noire", - price: 320, - image: "https://images.unsplash.com/photo-1622618991746-fe6004db3a47?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1543422655-ac1c6ca993ed?w=800&h=1000&fit=crop&auto=format", - category: "Fragrances", - tag: "EXCLUSIVE", - isNew: true, - rating: 4.9, - reviewCount: 156, - description: "Dark amber resin, Indonesian oud, and a heart of smoked vetiver. Sève Noire is a fragrance for those who understand that the most compelling stories are told in whispers.", - details: ["100ml Eau de Parfum", "Top: Bergamot, Cardamom", "Heart: Indonesian Oud, Rose Absolute", "Base: Smoked Vetiver, Amber Resin", "Concentration: 20% fragrance"], - colors: [] - }, - { - id: "5", - brand: "AUREL", - name: "Cellular Renewal Serum", - price: 245, - originalPrice: 290, - image: "https://images.unsplash.com/photo-1595425959632-34f2822322ce?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1598528738936-c50861cc75a9?w=800&h=1000&fit=crop&auto=format", - category: "Skincare", - isBestSeller: true, - rating: 4.8, - reviewCount: 342, - description: "A breakthrough in cellular renewal technology. This concentrated serum harnesses rare Alpine botanicals and next-generation peptides to visibly transform skin texture overnight.", - details: ["30ml / 1 fl oz", "Key: Alpine Rose Stem Cells, Hexapeptide-10", "Suitable for all skin types", "Clinically tested", "Fragrance-free"], - colors: [] - }, - { - id: "6", - brand: "NORDSEN", - name: "The Archive Derby — Suede", - price: 685, - image: "https://images.unsplash.com/photo-1682364853446-db043f643207?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1591348278900-019a8a2a8b1d?w=800&h=1000&fit=crop&auto=format", - category: "Footwear", - isBestSeller: true, - rating: 4.6, - reviewCount: 98, - description: "The Archive Derby reconsiders a century-old silhouette. Italian suede upper, leather sole, hand-stitched welt. Tradition reimagined for those who move through the world with intention.", - details: ["Italian suede upper", "Leather sole with rubber heel insert", "Goodyear welt construction", "Hand-stitched detailing", "Made in England"], - sizes: ["EU 38", "EU 39", "EU 40", "EU 41", "EU 42", "EU 43", "EU 44", "EU 45"], - colors: [{ name: "Noir Suede", hex: "#111111" }, { name: "Tobacco", hex: "#8B4513" }, { name: "Dove Grey", hex: "#888888" }] - }, - { - id: "7", - brand: "MAISON VOSS", - name: "Cashmere Turtleneck", - price: 680, - image: "https://images.unsplash.com/photo-1645561305502-63a9ba09ab09?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1675685828170-fe4b100acd24?w=800&h=1000&fit=crop&auto=format", - category: "Fashion", - isBestSeller: true, - rating: 4.9, - reviewCount: 211, - description: "Grade-A Mongolian cashmere, ribbed at the neck and cuffs. This is the sweater that renders all others unnecessary — a quiet declaration of taste.", - details: ["100% Grade-A Mongolian Cashmere", "12-gauge knit", "Ribbed trim", "Dry clean or hand wash cold", "Made in Scotland"], - sizes: ["XS", "S", "M", "L", "XL"], - colors: [{ name: "Oatmeal", hex: "#E8DCC8" }, { name: "Midnight", hex: "#111111" }, { name: "Camel", hex: "#C8A96B" }] - }, - { - id: "8", - brand: "CROFT & VALE", - name: "Silk Scarf — Cartography", - price: 395, - image: "https://images.unsplash.com/photo-1590739169125-a9438401596a?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1587467512961-120760940315?w=800&h=1000&fit=crop&auto=format", - category: "Accessories", - isNew: true, - rating: 4.7, - reviewCount: 67, - description: "Hand-rolled silk twill printed with maps of forgotten trade routes. A collector's piece that transforms any outfit into a statement of worldly sophistication.", - details: ["100% Silk Twill", "90 x 90 cm", "Hand-rolled edges", "Limited edition print", "Made in Lyon, France"], - colors: [] - }, - { - id: "9", - brand: "LUMIÈRE", - name: "Wide-Leg Tailored Trouser", - price: 590, - image: "https://images.unsplash.com/photo-1770062422145-c4c08e1f5cc5?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1638265499174-62c2cb29137a?w=800&h=1000&fit=crop&auto=format", - category: "Fashion", - rating: 4.5, - reviewCount: 89, - description: "Cut from Italian gabardine with an architectural drape, these trousers redefine the silhouette. High-waisted, wide through the leg — a modern classic.", - details: ["Italian Gabardine Wool", "High-rise waist", "Wide-leg cut", "Two side pockets", "Dry clean only"], - sizes: ["XS", "S", "M", "L", "XL"], - colors: [{ name: "Chalk", hex: "#F8F8F6" }, { name: "Midnight", hex: "#111111" }, { name: "Slate", hex: "#666666" }] - }, - { - id: "10", - brand: "VELORIA", - name: "Eau de Parfum — Blanc Absolu", - price: 280, - image: "https://images.unsplash.com/photo-1557170334-a9632e77c6e4?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1622618991746-fe6004db3a47?w=800&h=1000&fit=crop&auto=format", - category: "Fragrances", - rating: 4.8, - reviewCount: 134, - description: "Pure white flowers captured in glass. Blanc Absolu is an olfactive meditation — tuberose absolute, jasmine grandiflorum, and a trail of warm musk.", - details: ["75ml Eau de Parfum", "Top: Green Aldehydes, Bergamot", "Heart: Tuberose, Jasmine Grandiflorum", "Base: White Musk, Sandalwood"], - colors: [] - }, - { - id: "11", - brand: "AUREL", - name: "Luminous Crème — Masque", - price: 185, - image: "https://images.unsplash.com/photo-1631730486572-226d1f595b68?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1598528738936-c50861cc75a9?w=800&h=1000&fit=crop&auto=format", - category: "Beauty", - tag: "NEW", - isNew: true, - rating: 4.7, - reviewCount: 178, - description: "A weekly ritual in a jar. Rare kaolin, diamond powder, and hydrolyzed silk proteins work in concert to reveal the most luminous version of your complexion.", - details: ["75ml / 2.5 fl oz", "Key: Kaolin Clay, Diamond Powder, Silk Proteins", "Apply 1-2x weekly", "All skin types", "Vegan and cruelty-free"], - colors: [] - }, - { - id: "12", - brand: "NORDSEN", - name: "Chelsea Boot — Matte Leather", - price: 795, - image: "https://images.unsplash.com/photo-1682364853446-db043f643207?w=800&h=1000&fit=crop&auto=format", - hoverImage: "https://images.unsplash.com/photo-1559563458-527698bf5295?w=800&h=1000&fit=crop&auto=format", - category: "Footwear", - isBestSeller: true, - rating: 4.8, - reviewCount: 156, - description: "The Chelsea boot, elevated. Matte calf leather, elastic goring, stacked leather heel. Every detail considered, nothing superfluous.", - details: ["Matte Calf Leather upper", "Elastic goring", "Stacked leather heel", "Leather sole", "Made in Italy"], - sizes: ["EU 36", "EU 37", "EU 38", "EU 39", "EU 40", "EU 41", "EU 42"], - colors: [{ name: "Black Matte", hex: "#111111" }, { name: "Cognac", hex: "#8B4513" }] - } +export type ProductCategory = + | "Clothing" + | "Shoes" + | "Bags" + | "Accessories" + | "Fragrance" + | "Skincare"; + +type ProductSeed = { + id: string; + brand: string; + name: string; + price: number; + originalPrice?: number; + image: string; + hoverImage?: string; + category: ProductCategory; + color?: string; + tag?: string; + isNew?: boolean; + isBestSeller?: boolean; + rating?: number; + reviewCount?: number; + description?: string; + details?: string[]; +}; + +const productSeeds: ProductSeed[] = [ + { id: "1", brand: "MAISON VOSS", name: "Black Wool Coat", price: 1490, image: "https://images.unsplash.com/photo-1603189343302-e603f7add05a?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1683642765591-2370edc15193?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "Black", tag: "LOW STOCK", isBestSeller: true, rating: 4.8, reviewCount: 124 }, + { id: "2", brand: "LUMIERE", name: "White Cotton Shirt", price: 390, image: "https://images.unsplash.com/photo-1600508115810-0d733f925812?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1705249569862-3104399dae79?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "White", isNew: true, rating: 4.7, reviewCount: 88 }, + { id: "3", brand: "ATELIER NOIR", name: "Grey Tailored Trouser", price: 640, image: "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1664076458686-3449062080ac?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "Grey", rating: 4.6, reviewCount: 76 }, + { id: "4", brand: "NORDSEN", name: "Charcoal Leather Loafer", price: 685, image: "https://images.unsplash.com/photo-1562856416-ca417c64871f?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1605732440685-d0654d81aa30?w=800&h=1000&fit=crop&auto=format&q=80", category: "Shoes", color: "Charcoal", isBestSeller: true, rating: 4.7, reviewCount: 98 }, + { id: "5", brand: "MAISON VOSS", name: "Black Oversized Blazer", price: 1290, image: "https://images.unsplash.com/photo-1683642765591-2370edc15193?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1603189343302-e603f7add05a?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "Black", tag: "NEW", isNew: true, rating: 4.9, reviewCount: 141 }, + { id: "6", brand: "LUMIERE", name: "Ivory Silk Dress", price: 895, image: "https://images.unsplash.com/photo-1621036382228-d728f0d09e33?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1637536701369-f815af927b59?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "Ivory", isNew: true, rating: 4.9, reviewCount: 87 }, + { id: "7", brand: "CROFT & VALE", name: "Grey Leather Bag", price: 1250, image: "https://images.unsplash.com/photo-1575403538007-acb790100421?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1605733513597-a8f8341084e6?w=800&h=1000&fit=crop&auto=format&q=80", category: "Bags", color: "Grey", tag: "LOW STOCK", isBestSeller: true, rating: 4.7, reviewCount: 203 }, + { id: "8", brand: "ELAN STUDIO", name: "Black Sunglasses", price: 395, image: "https://images.unsplash.com/photo-1553614186-5fc725ac6396?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1637248666034-d5f4e91870a3?w=800&h=1000&fit=crop&auto=format&q=80", category: "Accessories", color: "Black", isBestSeller: true, rating: 4.6, reviewCount: 112 }, + { id: "9", brand: "VELORIA", name: "White Fragrance", price: 280, image: "https://images.unsplash.com/photo-1712273119968-84533ec59de9?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1764694187688-454d172e5ca0?w=800&h=1000&fit=crop&auto=format&q=80", category: "Fragrance", color: "White", isBestSeller: true, rating: 4.8, reviewCount: 134 }, + { id: "10", brand: "MAISON VOSS", name: "Charcoal Knit Sweater", price: 580, image: "https://images.unsplash.com/photo-1552393700-42696fb89bfa?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1653875842174-429c1b467548?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "Charcoal", rating: 4.8, reviewCount: 119 }, + { id: "11", brand: "LUMIERE", name: "Beige Trench Coat", price: 1380, image: "https://images.unsplash.com/photo-1762605135012-56a59a059e60?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1603189343302-e603f7add05a?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "Beige", rating: 4.7, reviewCount: 93 }, + { id: "12", brand: "NORDSEN", name: "Black Leather Boot", price: 795, image: "https://images.unsplash.com/photo-1605732440685-d0654d81aa30?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1562856416-ca417c64871f?w=800&h=1000&fit=crop&auto=format&q=80", category: "Shoes", color: "Black", isBestSeller: true, rating: 4.8, reviewCount: 156 }, + { id: "13", brand: "NORDSEN", name: "White Minimal Sneaker", price: 490, image: "https://images.unsplash.com/photo-1618947085672-1dcb69696c33?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1682364853446-db043f643207?w=800&h=1000&fit=crop&auto=format&q=80", category: "Shoes", color: "White", rating: 4.5, reviewCount: 74 }, + { id: "14", brand: "CROFT & VALE", name: "Black Structured Handbag", price: 1850, image: "https://images.unsplash.com/photo-1605733513597-a8f8341084e6?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1575403538007-acb790100421?w=800&h=1000&fit=crop&auto=format&q=80", category: "Bags", color: "Black", isBestSeller: true, rating: 4.9, reviewCount: 167 }, + { id: "15", brand: "ELAN STUDIO", name: "Grey Wool Scarf", price: 295, image: "https://images.unsplash.com/photo-1613915617430-8ab0fd7c6baf?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1683921470299-b8f0f3331657?w=800&h=1000&fit=crop&auto=format&q=80", category: "Accessories", color: "Grey", rating: 4.6, reviewCount: 61 }, + { id: "16", brand: "AUREL", name: "Ivory Skincare Set", price: 430, image: "https://images.unsplash.com/photo-1764694071462-db50e50a3925?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1595425959632-34f2822322ce?w=800&h=1000&fit=crop&auto=format&q=80", category: "Skincare", color: "Ivory", isBestSeller: true, rating: 4.8, reviewCount: 342 }, + { id: "17", brand: "CROFT & VALE", name: "Black Leather Belt", price: 245, image: "https://images.unsplash.com/photo-1683921470299-b8f0f3331657?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1613915617430-8ab0fd7c6baf?w=800&h=1000&fit=crop&auto=format&q=80", category: "Accessories", color: "Black", rating: 4.4, reviewCount: 58 }, + { id: "18", brand: "MAISON VOSS", name: "Beige Cashmere Sweater", price: 680, image: "https://images.unsplash.com/photo-1653875842174-429c1b467548?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1552393700-42696fb89bfa?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "Beige", isBestSeller: true, rating: 4.9, reviewCount: 211 }, + { id: "19", brand: "ATELIER NOIR", name: "Charcoal Wide-Leg Trouser", price: 590, image: "https://images.unsplash.com/photo-1664076458686-3449062080ac?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1574015974293-817f0ebebb74?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "Charcoal", rating: 4.5, reviewCount: 89 }, + { id: "20", brand: "LUMIERE", name: "White Tailored Blouse", price: 445, image: "https://images.unsplash.com/photo-1705249569862-3104399dae79?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1600508115810-0d733f925812?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "White", rating: 4.7, reviewCount: 72 }, + { id: "21", brand: "VELORIA", name: "Grey Fragrance", price: 310, image: "https://images.unsplash.com/photo-1764694187688-454d172e5ca0?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1712273119968-84533ec59de9?w=800&h=1000&fit=crop&auto=format&q=80", category: "Fragrance", color: "Grey", tag: "EXCLUSIVE", rating: 4.9, reviewCount: 156 }, + { id: "22", brand: "ELAN STUDIO", name: "Black Evening Dress", price: 1190, image: "https://images.unsplash.com/photo-1637536701369-f815af927b59?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1621036382228-d728f0d09e33?w=800&h=1000&fit=crop&auto=format&q=80", category: "Clothing", color: "Black", tag: "NEW", isNew: true, rating: 4.8, reviewCount: 101 }, + { id: "23", brand: "CROFT & VALE", name: "Faded Brown Leather Bag", price: 1450, image: "https://images.unsplash.com/photo-1683921377994-928bd73b889f?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1605733513597-a8f8341084e6?w=800&h=1000&fit=crop&auto=format&q=80", category: "Bags", color: "Brown", rating: 4.7, reviewCount: 94 }, + { id: "24", brand: "ELAN STUDIO", name: "Black Minimal Watch", price: 890, image: "https://images.unsplash.com/photo-1637248666034-d5f4e91870a3?w=800&h=1000&fit=crop&auto=format&q=80", hoverImage: "https://images.unsplash.com/photo-1553614186-5fc725ac6396?w=800&h=1000&fit=crop&auto=format&q=80", category: "Accessories", color: "Black", rating: 4.8, reviewCount: 68 }, ]; +const colorHex: Record = { + Black: "#111111", + White: "#F5F5F5", + Grey: "#888888", + Charcoal: "#3D3D3D", + Brown: "#6B4226", + Beige: "#D4B896", + Ivory: "#F5F0E8", +}; + +const categoryDetails: Record = { + Clothing: ["Premium cloth selected for structure and drape", "Tailored finish", "Minimal hardware", "Professional care recommended"], + Shoes: ["Italian leather upper", "Leather lining", "Cushioned insole", "Made for everyday wear"], + Bags: ["Full-grain leather", "Structured silhouette", "Interior pocket", "Protective dust bag included"], + Accessories: ["Premium materials", "Minimal hardware", "Signature VibeCart edit", "Protective packaging included"], + Fragrance: ["Eau de parfum", "Balanced top, heart, and base notes", "Designed for day-to-evening wear", "Made in small batches"], + Skincare: ["Curated regimen set", "Suitable for daily rituals", "Dermatologist-tested formula", "Packaged in recyclable glass"], +}; + +function slugify(value: string) { + return value + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .toLowerCase() + .replace(/&/g, "and") + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +function getDescription(seed: ProductSeed) { + const color = seed.color ? `${seed.color.toLowerCase()} ` : ""; + return `${seed.name} by ${seed.brand} is selected for a refined ${color}palette, considered proportions, and a quiet luxury finish that works across the modern wardrobe.`; +} + +function getSizes(category: ProductCategory) { + if (category === "Clothing") return ["XS", "S", "M", "L", "XL"]; + if (category === "Shoes") return ["EU 38", "EU 39", "EU 40", "EU 41", "EU 42", "EU 43", "EU 44", "EU 45"]; + return undefined; +} + +export const products: Product[] = productSeeds.map((seed, index) => ({ + id: seed.id, + slug: `${seed.id}-${slugify(seed.name)}`, + brand: seed.brand, + name: seed.name, + price: seed.price, + originalPrice: seed.originalPrice, + image: seed.image, + hoverImage: seed.hoverImage || seed.image, + category: seed.category, + tag: seed.tag, + isNew: seed.isNew, + isBestSeller: seed.isBestSeller, + rating: seed.rating || Number((4.5 + (index % 5) * 0.1).toFixed(1)), + reviewCount: seed.reviewCount || 60 + index * 11, + description: seed.description || getDescription(seed), + details: seed.details || categoryDetails[seed.category], + sizes: getSizes(seed.category), + colors: seed.color ? [{ name: seed.color, hex: colorHex[seed.color] || "#999999" }] : undefined, +})); + +export function getProductPath(product: Product) { + return `/product/${product.slug}`; +} + +export function findProductByRouteParam(param: string | undefined) { + if (!param) return undefined; + + let decoded = param; + try { + decoded = decodeURIComponent(param); + } catch { + decoded = param; + } + + const normalized = decoded.trim().toLowerCase(); + const numericId = normalized.match(/^(?:p)?(\d+)(?:-|$)/)?.[1]; + + return products.find((product) => + normalized === product.id || + normalized === `p${product.id}` || + normalized === product.slug || + normalized === slugify(product.name) || + product.id === numericId + ); +} + export const categories = [ - { name: "Fashion", image: "https://images.unsplash.com/photo-1629511565591-a1d494ad6c58?w=600&h=800&fit=crop&auto=format", count: 284 }, - { name: "Beauty", image: "https://images.unsplash.com/photo-1631730486572-226d1f595b68?w=600&h=800&fit=crop&auto=format", count: 156 }, - { name: "Accessories", image: "https://images.unsplash.com/photo-1590739225287-bd31519780c3?w=600&h=800&fit=crop&auto=format", count: 198 }, - { name: "Footwear", image: "https://images.unsplash.com/photo-1682364853446-db043f643207?w=600&h=800&fit=crop&auto=format", count: 112 }, - { name: "Fragrances", image: "https://images.unsplash.com/photo-1622618991746-fe6004db3a47?w=600&h=800&fit=crop&auto=format", count: 89 }, - { name: "Skincare", image: "https://images.unsplash.com/photo-1595425959632-34f2822322ce?w=600&h=800&fit=crop&auto=format", count: 143 }, + { name: "Clothing", image: products[0].image, count: 284 }, + { name: "Shoes", image: products[3].image, count: 112 }, + { name: "Bags", image: products[6].image, count: 198 }, + { name: "Accessories", image: products[7].image, count: 156 }, + { name: "Fragrance", image: products[8].image, count: 89 }, + { name: "Skincare", image: products[15].image, count: 143 }, ]; export const brands = [ { name: "MAISON VOSS", tagline: "Tailored Precision" }, - { name: "LUMIÈRE", tagline: "Fluid Elegance" }, + { name: "LUMIERE", tagline: "Fluid Elegance" }, + { name: "ATELIER NOIR", tagline: "Architectural Essentials" }, + { name: "NORDSEN", tagline: "Heritage Footwear" }, { name: "CROFT & VALE", tagline: "Artisan Leather" }, + { name: "ELAN STUDIO", tagline: "Minimal Accessories" }, { name: "VELORIA", tagline: "Rare Parfums" }, { name: "AUREL", tagline: "Scientific Luxury" }, - { name: "NORDSEN", tagline: "Heritage Footwear" }, - { name: "OSTARA", tagline: "Conscious Living" }, - { name: "ELARA", tagline: "Pure Beauty" }, ]; diff --git a/apps/web/src/app/pages/CartPage.tsx b/apps/web/src/app/pages/CartPage.tsx index 85b47fe..e760563 100644 --- a/apps/web/src/app/pages/CartPage.tsx +++ b/apps/web/src/app/pages/CartPage.tsx @@ -3,7 +3,7 @@ import { Link } from "react-router"; import { Minus, Plus, X, ArrowRight, ShoppingBag } from "lucide-react"; import { cartStore, CartItem } from "../store/cartStore"; import { ProductCard } from "../components/ProductCard"; -import { products } from "../data/products"; +import { getProductPath, products } from "../data/products"; export function CartPage() { const [items, setItems] = useState(cartStore.getCart()); @@ -50,7 +50,7 @@ export function CartPage() {
{items.map((item, idx) => (
- +
{item.product.name}
@@ -61,7 +61,7 @@ export function CartPage() {

{item.product.brand}

- +

{item.product.name}

diff --git a/apps/web/src/app/pages/HomePage.tsx b/apps/web/src/app/pages/HomePage.tsx index 7bea1f9..6f67626 100644 --- a/apps/web/src/app/pages/HomePage.tsx +++ b/apps/web/src/app/pages/HomePage.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { Link } from "react-router"; import { Heart } from "lucide-react"; +import { findProductByRouteParam, getProductPath } from "../data/products"; /* ─── Image registry — 22 muted/neutral/monochrome images ──────────────── 6 category images @@ -57,16 +58,16 @@ const CATEGORIES = [ const NEW_ARRIVALS = [ { brand: "MAISON VOSS", name: "Oversized Black Coat", price: 1490, img: IMG.new_1, hover: IMG.edit_1, id: "1" }, - { brand: "LUMIÈRE", name: "White Tailored Blazer", price: 890, img: IMG.new_2, hover: IMG.campaign_2, id: "2" }, - { brand: "CROFT & VALE", name: "Grey Leather Bag", price: 1250, img: IMG.new_3, hover: IMG.new_4, id: "3" }, - { brand: "NORDSEN", name: "Black Leather Shoes", price: 685, img: IMG.new_4, hover: IMG.new_3, id: "6" }, + { brand: "LUMIÈRE", name: "White Tailored Blazer", price: 890, img: IMG.new_2, hover: IMG.campaign_2, id: "20" }, + { brand: "CROFT & VALE", name: "Grey Leather Bag", price: 1250, img: IMG.new_3, hover: IMG.new_4, id: "7" }, + { brand: "NORDSEN", name: "Black Leather Shoes", price: 685, img: IMG.new_4, hover: IMG.new_3, id: "12" }, ]; const BEST_SELLERS = [ - { brand: "ELARA", name: "Black Sunglasses", price: 395, img: IMG.best_1, hover: IMG.edit_2, id: "7" }, - { brand: "CROFT & VALE", name: "Grey Handbag", price: 1850, img: IMG.best_2, hover: IMG.new_3, id: "3" }, + { brand: "ELARA", name: "Black Sunglasses", price: 395, img: IMG.best_1, hover: IMG.edit_2, id: "8" }, + { brand: "CROFT & VALE", name: "Grey Handbag", price: 1850, img: IMG.best_2, hover: IMG.new_3, id: "7" }, { brand: "MAISON VOSS", name: "Black Wool Coat", price: 1290, img: IMG.best_3, hover: IMG.new_1, id: "1" }, - { brand: "VELORIA", name: "White Fragrance", price: 280, img: IMG.best_4, hover: IMG.cat_beauty, id: "4" }, + { brand: "VELORIA", name: "White Fragrance", price: 280, img: IMG.best_4, hover: IMG.cat_beauty, id: "9" }, ]; const EDITORIAL = [ @@ -135,10 +136,12 @@ interface CardItem { function ProductTile({ item }: { item: CardItem }) { const [hovered, setHovered] = useState(false); const [loved, setLoved] = useState(false); + const product = findProductByRouteParam(item.id); + const productPath = product ? getProductPath(product) : `/product/${item.id}`; return ( setHovered(true)} onMouseLeave={() => setHovered(false)} diff --git a/apps/web/src/app/pages/ProductPage.tsx b/apps/web/src/app/pages/ProductPage.tsx index 052362d..21388df 100644 --- a/apps/web/src/app/pages/ProductPage.tsx +++ b/apps/web/src/app/pages/ProductPage.tsx @@ -1,7 +1,7 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useParams, Link } from "react-router"; import { Heart, Minus, Plus, ShoppingBag, Share2, ArrowRight, Star, ChevronDown } from "lucide-react"; -import { products } from "../data/products"; +import { findProductByRouteParam, products } from "../data/products"; import { cartStore } from "../store/cartStore"; import { ProductCard } from "../components/ProductCard"; @@ -12,8 +12,9 @@ const reviews = [ ]; export function ProductPage() { - const { id } = useParams(); - const product = products.find(p => p.id === id) || products[0]; + const { slug } = useParams(); + const resolvedProduct = findProductByRouteParam(slug); + const product = resolvedProduct || products[0]; const [activeImage, setActiveImage] = useState(0); const [selectedSize, setSelectedSize] = useState(product.sizes?.[0]); const [selectedColor, setSelectedColor] = useState(product.colors?.[0]?.name); @@ -25,6 +26,16 @@ export function ProductPage() { const images = [product.image, product.hoverImage]; const related = products.filter(p => p.category === product.category && p.id !== product.id).slice(0, 4); + useEffect(() => { + setActiveImage(0); + setSelectedSize(product.sizes?.[0]); + setSelectedColor(product.colors?.[0]?.name); + setQuantity(1); + setWishlisted(cartStore.isWishlisted(product.id)); + setAddedToCart(false); + setExpandedSection("description"); + }, [product]); + const handleAddToCart = () => { cartStore.addToCart(product, quantity, selectedSize, selectedColor); setAddedToCart(true); @@ -46,6 +57,28 @@ export function ProductPage() { { id: "care", label: "Care Instructions", content: "To preserve the integrity of this piece, follow the care label instructions carefully. When in doubt, professional cleaning is recommended for all luxury garments." }, ]; + if (!resolvedProduct) { + return ( +
+
+

+ Product not found +

+

+ This product link may be outdated. +

+ + RETURN TO SHOP + +
+
+ ); + } + return (
{/* Breadcrumb */} diff --git a/apps/web/src/app/pages/ShopPage.tsx b/apps/web/src/app/pages/ShopPage.tsx index cdd7197..f2e43dc 100644 --- a/apps/web/src/app/pages/ShopPage.tsx +++ b/apps/web/src/app/pages/ShopPage.tsx @@ -1,6 +1,7 @@ import { useState, useMemo, useEffect } from "react"; import { useSearchParams, Link } from "react-router"; import { X, SlidersHorizontal, ChevronDown } from "lucide-react"; +import { findProductByRouteParam, getProductPath } from "../data/products"; /* ─── Products ──────────────────────────────────────────────────────────── */ type Gender = "Men" | "Women" | "Unisex"; @@ -409,9 +410,11 @@ function ProductGrid({ items, onClear, cols = "desktop" }: { items: Item[]; onCl /* ── Product tile ─────────────────────────────────────────────────────────── */ function ProductTile({ item }: { item: Item }) { const [hovered, setHovered] = useState(false); + const product = findProductByRouteParam(item.id); + const productPath = product ? getProductPath(product) : `/product/${item.id}`; return ( setHovered(true)} onMouseLeave={() => setHovered(false)}