Skip to content
Draft
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
32 changes: 32 additions & 0 deletions src/app/store/[slug]/components/product-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { cn } from "@/lib/utils";
import { Loader2 } from "lucide-react";
import { PriceDisplay } from "./price-display";
import { Skeleton } from "@/components/ui/skeleton";

interface ProductCardProps {
product: {
Expand Down Expand Up @@ -127,3 +128,34 @@ export function ProductCard({ product, storeSlug, className }: ProductCardProps)
</Link>
);
}

/**
* Product card skeleton loader
* Used for loading states in product grids
*/
export function ProductCardSkeleton({ className }: { className?: string }) {
return (
<Card className={cn("overflow-hidden", className)}>
<CardContent className="p-0">
{/* Image Skeleton */}
<Skeleton className="aspect-square w-full" />

{/* Product Info Skeleton */}
<div className="p-4 space-y-3">
{/* Category */}
<Skeleton className="h-3 w-20" />

{/* Product Name */}
<div className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
</div>

{/* Price */}
<Skeleton className="h-6 w-24" />
</div>
</CardContent>
</Card>
);
}

100 changes: 100 additions & 0 deletions src/app/store/[slug]/components/product-tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"use client";

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

interface ProductTabsProps {
description?: string | null;
specifications: {
sku: string;
barcode?: string | null;
weight?: number | null;
length?: number | null;
width?: number | null;
height?: number | null;
};
className?: string;
}

/**
* Product tabs component for product detail page
* Features: Description, Specifications, and Reviews tabs
* Uses shadcn/ui Tabs primitives
*/
export function ProductTabs({ description, specifications, className }: ProductTabsProps) {
return (
<Tabs defaultValue="description" className={className}>
<TabsList className="w-full justify-start border-b rounded-none h-auto p-0 bg-transparent">
<TabsTrigger
value="description"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
>
Description
</TabsTrigger>
<TabsTrigger
value="specs"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
>
Specifications
</TabsTrigger>
<TabsTrigger
value="reviews"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
>
Reviews
</TabsTrigger>
</TabsList>

<TabsContent value="description" className="mt-6">
<div className="prose prose-sm max-w-none dark:prose-invert">
{description ? (
<p className="text-muted-foreground leading-relaxed whitespace-pre-wrap">
{description}
</p>
) : (
<p className="text-muted-foreground">
No description available for this product.
</p>
)}
</div>
</TabsContent>

<TabsContent value="specs" className="mt-6">
<div className="grid gap-3">
<div className="grid grid-cols-3 gap-4 py-3 border-b">
<span className="font-medium">SKU</span>
<span className="col-span-2 text-muted-foreground">{specifications.sku}</span>
</div>
{specifications.barcode && (
<div className="grid grid-cols-3 gap-4 py-3 border-b">
<span className="font-medium">Barcode</span>
<span className="col-span-2 text-muted-foreground">{specifications.barcode}</span>
</div>
)}
{specifications.weight && (
<div className="grid grid-cols-3 gap-4 py-3 border-b">
<span className="font-medium">Weight</span>
<span className="col-span-2 text-muted-foreground">{specifications.weight} kg</span>
</div>
)}
{(specifications.length && specifications.width && specifications.height) && (
<div className="grid grid-cols-3 gap-4 py-3 border-b">
<span className="font-medium">Dimensions</span>
<span className="col-span-2 text-muted-foreground">
{specifications.length} × {specifications.width} × {specifications.height} cm
</span>
</div>
)}
</div>
</TabsContent>

<TabsContent value="reviews" className="mt-6">
<div className="text-center py-12">
<p className="text-muted-foreground mb-4">
No reviews yet. Be the first to review this product!
</p>
{/* TODO: Add review form and list when review feature is implemented */}
</div>
</TabsContent>
</Tabs>
);
}
76 changes: 16 additions & 60 deletions src/app/store/[slug]/products/[productSlug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { StockBadge } from "../../components/stock-badge";
import { VariantSelector } from "../../components/variant-selector";
import { AddToCartButton } from "../../components/add-to-cart-button";
import { ProductGrid } from "../../components/product-grid";
import { ProductTabs } from "../../components/product-tabs";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Package, TruckIcon, RefreshCw, ShieldCheck } from "lucide-react";

interface StoreProductPageProps {
Expand Down Expand Up @@ -61,6 +61,9 @@ export async function generateMetadata({
};
}

// Revalidate this page every 5 minutes (300 seconds)
export const revalidate = 300;

export default async function StoreProductPage({ params }: StoreProductPageProps) {
const { slug, productSlug } = await params;

Expand Down Expand Up @@ -297,65 +300,18 @@ export default async function StoreProductPage({ params }: StoreProductPageProps

{/* Product Details Tabs */}
<div className="mb-16">
<Tabs defaultValue="description" className="w-full">
<TabsList className="w-full justify-start border-b rounded-none h-auto p-0 bg-transparent">
<TabsTrigger
value="description"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
>
Description
</TabsTrigger>
<TabsTrigger
value="specs"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
>
Specifications
</TabsTrigger>
</TabsList>

<TabsContent value="description" className="mt-6">
<div className="prose prose-sm max-w-none dark:prose-invert">
{product.description ? (
<p className="text-muted-foreground leading-relaxed whitespace-pre-wrap">
{product.description}
</p>
) : (
<p className="text-muted-foreground">
No description available for this product.
</p>
)}
</div>
</TabsContent>

<TabsContent value="specs" className="mt-6">
<div className="grid gap-3">
<div className="grid grid-cols-3 gap-4 py-3 border-b">
<span className="font-medium">SKU</span>
<span className="col-span-2 text-muted-foreground">{product.sku}</span>
</div>
{product.barcode && (
<div className="grid grid-cols-3 gap-4 py-3 border-b">
<span className="font-medium">Barcode</span>
<span className="col-span-2 text-muted-foreground">{product.barcode}</span>
</div>
)}
{product.weight && (
<div className="grid grid-cols-3 gap-4 py-3 border-b">
<span className="font-medium">Weight</span>
<span className="col-span-2 text-muted-foreground">{product.weight} kg</span>
</div>
)}
{(product.length && product.width && product.height) && (
<div className="grid grid-cols-3 gap-4 py-3 border-b">
<span className="font-medium">Dimensions</span>
<span className="col-span-2 text-muted-foreground">
{product.length} × {product.width} × {product.height} cm
</span>
</div>
)}
</div>
</TabsContent>
</Tabs>
<ProductTabs
description={product.description}
specifications={{
sku: product.sku,
barcode: product.barcode,
weight: product.weight,
length: product.length,
width: product.width,
height: product.height,
}}
className="w-full"
/>
</div>

{/* Related Products */}
Expand Down
127 changes: 127 additions & 0 deletions src/types/storefront-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* Storefront Configuration Types
* Shared types for multi-tenant storefront configuration
*/

export interface HeroConfig {
title: string;
subtitle?: string;
ctaText?: string;
ctaLink?: string;
backgroundImage?: string;
overlayOpacity?: number;
}

export interface CategoryConfig {
id: string;
name: string;
slug: string;
description?: string;
image?: string;
}

export interface CategoriesConfig {
enabled: boolean;
title?: string;
subtitle?: string;
categories: CategoryConfig[];
}

export interface FeaturedProductsConfig {
enabled: boolean;
title?: string;
subtitle?: string;
limit?: number;
}

export interface TrustBadge {
icon: string;
title: string;
description: string;
}

export interface TrustBadgesConfig {
enabled: boolean;
badges: TrustBadge[];
}

export interface NewsletterConfig {
enabled: boolean;
title?: string;
subtitle?: string;
placeholder?: string;
buttonText?: string;
}

export interface StorefrontConfig {
hero: HeroConfig;
categories: CategoriesConfig;
featuredProducts: FeaturedProductsConfig;
trustBadges: TrustBadgesConfig;
newsletter: NewsletterConfig;
}

/**
* Get default storefront configuration
* @param storeName - Store name for dynamic config
* @param storeDescription - Store description for dynamic config
* @returns Default storefront configuration
*/
export function getDefaultStorefrontConfig(
storeName: string,
storeDescription?: string
): StorefrontConfig {
return {
hero: {
title: `Welcome to ${storeName}`,
subtitle: storeDescription || `Discover amazing products at ${storeName}`,
ctaText: "Shop Now",
ctaLink: "/products",
overlayOpacity: 0.4,
},
categories: {
enabled: true,
title: "Shop by Category",
subtitle: "Browse our curated collections",
categories: [],
},
featuredProducts: {
enabled: true,
title: "Featured Products",
subtitle: "Hand-picked items just for you",
limit: 8,
},
trustBadges: {
enabled: true,
badges: [
{
icon: "truck",
title: "Free Shipping",
description: "On orders over $50",
},
{
icon: "refresh",
title: "Easy Returns",
description: "30-day return policy",
},
{
icon: "shield",
title: "Secure Payment",
description: "100% secure checkout",
},
{
icon: "headphones",
title: "24/7 Support",
description: "Dedicated customer service",
},
],
},
newsletter: {
enabled: true,
title: "Stay Updated",
subtitle: "Subscribe to our newsletter for exclusive offers and updates",
placeholder: "Enter your email",
buttonText: "Subscribe",
},
};
}