From 741c4599d1466096d6d2d62c5d0ea65d686878ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 23:20:27 +0000 Subject: [PATCH 1/3] Initial plan From 4bd2a1a2c67af6c0f899f9926ab6cea7ff4ea77b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 23:28:44 +0000 Subject: [PATCH 2/3] Add storefront configuration types and ProductTabs component - Create src/types/storefront-config.ts with StorefrontConfig, HeroConfig, CategoriesConfig, FeaturedProductsConfig, NewsletterConfig, and TrustBadgesConfig interfaces - Create src/lib/storefront/get-default-config.ts with getDefaultStorefrontConfig helper - Create src/app/store/[slug]/components/product-tabs.tsx with Description, Specifications, and Reviews tabs - Update product detail page to use ProductTabs component instead of inline tabs - All components properly typed and follow shadcn/ui patterns Co-authored-by: syed-reza98 <71028588+syed-reza98@users.noreply.github.com> --- .../store/[slug]/components/product-tabs.tsx | 231 ++++++++++++++++++ .../[slug]/products/[productSlug]/page.tsx | 72 +----- src/lib/storefront/get-default-config.ts | 93 +++++++ src/types/storefront-config.ts | 88 +++++++ 4 files changed, 424 insertions(+), 60 deletions(-) create mode 100644 src/app/store/[slug]/components/product-tabs.tsx create mode 100644 src/lib/storefront/get-default-config.ts create mode 100644 src/types/storefront-config.ts diff --git a/src/app/store/[slug]/components/product-tabs.tsx b/src/app/store/[slug]/components/product-tabs.tsx new file mode 100644 index 00000000..28fe07f2 --- /dev/null +++ b/src/app/store/[slug]/components/product-tabs.tsx @@ -0,0 +1,231 @@ +"use client"; + +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Star, User } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface ProductTabsProps { + description?: string | null; + specifications?: Record; + sku?: string; + barcode?: string | null; + weight?: number | null; + dimensions?: { + length?: number | null; + width?: number | null; + height?: number | null; + }; + className?: string; +} + +/** + * Product tabs component for displaying Description, Specifications, and Reviews + * Used on product detail pages to organize product information + */ +export function ProductTabs({ + description, + specifications = {}, + sku, + barcode, + weight, + dimensions, + className, +}: ProductTabsProps) { + // Mock reviews data (can be replaced with actual data from API) + const mockReviews = [ + { + id: "1", + author: "John Doe", + rating: 5, + date: "2024-03-15", + comment: "Excellent product! Highly recommended.", + verified: true, + }, + { + id: "2", + author: "Jane Smith", + rating: 4, + date: "2024-03-10", + comment: "Good quality, fast delivery.", + verified: true, + }, + ]; + + const hasSpecs = Object.keys(specifications).length > 0 || sku || barcode || weight || + (dimensions?.length && dimensions?.width && dimensions?.height); + + return ( + + + + Description + + {hasSpecs && ( + + Specifications + + )} + + Reviews ({mockReviews.length}) + + + + {/* Description Tab */} + +
+ {description ? ( +

+ {description} +

+ ) : ( +

+ No description available for this product. +

+ )} +
+
+ + {/* Specifications Tab */} + {hasSpecs && ( + +
+ {sku && ( +
+ SKU + {sku} +
+ )} + {barcode && ( +
+ Barcode + {barcode} +
+ )} + {weight && ( +
+ Weight + {weight} kg +
+ )} + {dimensions?.length && dimensions?.width && dimensions?.height && ( +
+ Dimensions + + {dimensions.length} × {dimensions.width} × {dimensions.height} cm + +
+ )} + {Object.entries(specifications).map(([key, value]) => ( +
+ {key} + {value} +
+ ))} +
+
+ )} + + {/* Reviews Tab */} + +
+ {/* Reviews Summary */} +
+
+
+
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+ 4.5 +
+

+ Based on {mockReviews.length} reviews +

+
+ +
+ + {/* Reviews List */} +
+ {mockReviews.map((review) => ( + + +
+ {/* Reviewer Info */} +
+
+
+ +
+
+

+ {review.author} + {review.verified && ( + + Verified Purchase + + )} +

+

+ {new Date(review.date).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + })} +

+
+
+
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+
+ + {/* Review Comment */} +

+ {review.comment} +

+
+
+
+ ))} +
+ + {/* Load More */} +
+ +
+
+
+
+ ); +} diff --git a/src/app/store/[slug]/products/[productSlug]/page.tsx b/src/app/store/[slug]/products/[productSlug]/page.tsx index 897d2c9b..08a1ab0d 100644 --- a/src/app/store/[slug]/products/[productSlug]/page.tsx +++ b/src/app/store/[slug]/products/[productSlug]/page.tsx @@ -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 { @@ -297,65 +297,17 @@ export default async function StoreProductPage({ params }: StoreProductPageProps {/* Product Details Tabs */}
- - - - Description - - - Specifications - - - - -
- {product.description ? ( -

- {product.description} -

- ) : ( -

- No description available for this product. -

- )} -
-
- - -
-
- SKU - {product.sku} -
- {product.barcode && ( -
- Barcode - {product.barcode} -
- )} - {product.weight && ( -
- Weight - {product.weight} kg -
- )} - {(product.length && product.width && product.height) && ( -
- Dimensions - - {product.length} × {product.width} × {product.height} cm - -
- )} -
-
-
+
{/* Related Products */} diff --git a/src/lib/storefront/get-default-config.ts b/src/lib/storefront/get-default-config.ts new file mode 100644 index 00000000..4bb90912 --- /dev/null +++ b/src/lib/storefront/get-default-config.ts @@ -0,0 +1,93 @@ +import type { StorefrontConfig } from "@/types/storefront-config"; + +/** + * Generate default storefront configuration for a store + * Uses store name and description to create sensible defaults + * + * @param storeName - The name of the store + * @param storeDescription - Optional description of the store + * @returns Complete StorefrontConfig with default values + */ +export function getDefaultStorefrontConfig( + storeName: string, + storeDescription?: string | null +): StorefrontConfig { + return { + // Hero section + hero: { + title: `Welcome to ${storeName}`, + subtitle: storeDescription || `Discover amazing products at ${storeName}`, + ctaText: "Shop Now", + ctaLink: "/products", + enabled: true, + }, + + // Categories section + categories: { + title: "Shop by Category", + subtitle: "Explore our wide range of products", + showCount: 6, + displayStyle: "grid", + enabled: true, + }, + + // Featured products section + featuredProducts: { + title: "Featured Products", + subtitle: "Check out our hand-picked selection", + maxProducts: 8, + displayStyle: "grid", + enabled: true, + }, + + // Newsletter section + newsletter: { + title: "Stay Updated", + subtitle: "Subscribe to our newsletter for exclusive offers and updates", + placeholder: "Enter your email", + buttonText: "Subscribe", + enabled: true, + }, + + // Trust badges + trustBadges: { + enabled: true, + badges: [ + { + icon: "truck", + title: "Free Shipping", + description: "On orders over $50", + }, + { + icon: "shield-check", + title: "Secure Payment", + description: "100% secure checkout", + }, + { + icon: "refresh-cw", + title: "Easy Returns", + description: "30-day return policy", + }, + { + icon: "headset", + title: "24/7 Support", + description: "Dedicated customer service", + }, + ], + }, + + // Theme settings (can be customized per tenant) + theme: { + primaryColor: undefined, + accentColor: undefined, + fontFamily: undefined, + }, + + // SEO settings + seo: { + title: storeName, + description: storeDescription || `Shop at ${storeName} for quality products`, + keywords: [], + }, + }; +} diff --git a/src/types/storefront-config.ts b/src/types/storefront-config.ts new file mode 100644 index 00000000..0285bbdb --- /dev/null +++ b/src/types/storefront-config.ts @@ -0,0 +1,88 @@ +/** + * Storefront Configuration Types + * Defines types for tenant-configurable storefront homepage and theme settings + */ + +/** + * Hero section configuration for storefront homepage + */ +export interface HeroConfig { + title: string; + subtitle: string; + ctaText: string; + ctaLink: string; + backgroundImage?: string; + enabled: boolean; +} + +/** + * Categories section configuration + */ +export interface CategoriesConfig { + title: string; + subtitle?: string; + showCount?: number; + displayStyle: "grid" | "carousel"; + enabled: boolean; +} + +/** + * Featured products section configuration + */ +export interface FeaturedProductsConfig { + title: string; + subtitle?: string; + maxProducts: number; + displayStyle: "grid" | "carousel"; + enabled: boolean; +} + +/** + * Newsletter subscription section configuration + */ +export interface NewsletterConfig { + title: string; + subtitle?: string; + placeholder: string; + buttonText: string; + enabled: boolean; +} + +/** + * Trust badges configuration (e.g., "Free Shipping", "30-Day Returns") + */ +export interface TrustBadgesConfig { + enabled: boolean; + badges: Array<{ + icon: string; + title: string; + description: string; + }>; +} + +/** + * Complete storefront configuration interface + * Used for tenant-specific homepage layout and theme customization + */ +export interface StorefrontConfig { + // Homepage sections + hero: HeroConfig; + categories: CategoriesConfig; + featuredProducts: FeaturedProductsConfig; + newsletter: NewsletterConfig; + trustBadges: TrustBadgesConfig; + + // Theme settings + theme: { + primaryColor?: string; + accentColor?: string; + fontFamily?: string; + }; + + // SEO settings + seo: { + title?: string; + description?: string; + keywords?: string[]; + }; +} From a92c6755800802368a6f7a98b3e93e700d9d8ee9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 23:33:38 +0000 Subject: [PATCH 3/3] Improve ProductTabs reusability - address code review feedback - Move mock reviews data to module-level constant - Add reviews and averageRating props to ProductTabsProps - Calculate average rating from reviews array if not provided - Add Review interface type definition - Add empty state message when no reviews exist - Use reviews prop throughout component instead of hardcoded data Co-authored-by: syed-reza98 <71028588+syed-reza98@users.noreply.github.com> --- .../store/[slug]/components/product-tabs.tsx | 168 ++++++++++-------- 1 file changed, 98 insertions(+), 70 deletions(-) diff --git a/src/app/store/[slug]/components/product-tabs.tsx b/src/app/store/[slug]/components/product-tabs.tsx index 28fe07f2..abf287df 100644 --- a/src/app/store/[slug]/components/product-tabs.tsx +++ b/src/app/store/[slug]/components/product-tabs.tsx @@ -6,6 +6,35 @@ import { Button } from "@/components/ui/button"; import { Star, User } from "lucide-react"; import { cn } from "@/lib/utils"; +// Mock reviews data for demonstration (can be replaced with actual data from API) +const MOCK_REVIEWS = [ + { + id: "1", + author: "John Doe", + rating: 5, + date: "2024-03-15", + comment: "Excellent product! Highly recommended.", + verified: true, + }, + { + id: "2", + author: "Jane Smith", + rating: 4, + date: "2024-03-10", + comment: "Good quality, fast delivery.", + verified: true, + }, +]; + +interface Review { + id: string; + author: string; + rating: number; + date: string; + comment: string; + verified: boolean; +} + interface ProductTabsProps { description?: string | null; specifications?: Record; @@ -17,6 +46,8 @@ interface ProductTabsProps { width?: number | null; height?: number | null; }; + reviews?: Review[]; + averageRating?: number; className?: string; } @@ -31,27 +62,16 @@ export function ProductTabs({ barcode, weight, dimensions, + reviews = MOCK_REVIEWS, + averageRating, className, }: ProductTabsProps) { - // Mock reviews data (can be replaced with actual data from API) - const mockReviews = [ - { - id: "1", - author: "John Doe", - rating: 5, - date: "2024-03-15", - comment: "Excellent product! Highly recommended.", - verified: true, - }, - { - id: "2", - author: "Jane Smith", - rating: 4, - date: "2024-03-10", - comment: "Good quality, fast delivery.", - verified: true, - }, - ]; + // Calculate average rating from reviews if not provided + const calculatedRating = averageRating ?? ( + reviews.length > 0 + ? reviews.reduce((sum, review) => sum + review.rating, 0) / reviews.length + : 0 + ); const hasSpecs = Object.keys(specifications).length > 0 || sku || barcode || weight || (dimensions?.length && dimensions?.width && dimensions?.height); @@ -77,7 +97,7 @@ export function ProductTabs({ value="reviews" className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent" > - Reviews ({mockReviews.length}) + Reviews ({reviews.length}) @@ -149,17 +169,19 @@ export function ProductTabs({ key={star} className={cn( "h-5 w-5", - star <= 4.5 + star <= calculatedRating ? "fill-yellow-400 text-yellow-400" : "text-gray-300" )} /> ))} - 4.5 + + {calculatedRating > 0 ? calculatedRating.toFixed(1) : "N/A"} +

- Based on {mockReviews.length} reviews + Based on {reviews.length} {reviews.length === 1 ? "review" : "reviews"}

@@ -167,57 +189,63 @@ export function ProductTabs({ {/* Reviews List */}
- {mockReviews.map((review) => ( - - -
- {/* Reviewer Info */} -
-
-
- + {reviews.length > 0 ? ( + reviews.map((review) => ( + + +
+ {/* Reviewer Info */} +
+
+
+ +
+
+

+ {review.author} + {review.verified && ( + + Verified Purchase + + )} +

+

+ {new Date(review.date).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + })} +

+
-
-

- {review.author} - {review.verified && ( - - Verified Purchase - - )} -

-

- {new Date(review.date).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - })} -

+
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))}
-
- {[1, 2, 3, 4, 5].map((star) => ( - - ))} -
-
- {/* Review Comment */} -

- {review.comment} -

-
-
-
- ))} + {/* Review Comment */} +

+ {review.comment} +

+
+ + + )) + ) : ( +
+

No reviews yet. Be the first to review this product!

+
+ )}
{/* Load More */}