+
+
+
+
+ Theme Marketplace
+
+
+
+
+ Theme Editor
+
+
+
Visual Editor
diff --git a/src/components/dashboard/storefront/editor/theme-marketplace-panel.tsx b/src/components/dashboard/storefront/editor/theme-marketplace-panel.tsx
index bd6104d3..5e050616 100644
--- a/src/components/dashboard/storefront/editor/theme-marketplace-panel.tsx
+++ b/src/components/dashboard/storefront/editor/theme-marketplace-panel.tsx
@@ -136,11 +136,22 @@ export function ThemeMarketplacePanel({ currentTheme, onApplyTheme }: ThemeMarke
{/* Header */}
-
-
Theme Marketplace
-
- Browse and install professional themes for your store
-
+
{/* Search and Filters */}
@@ -316,6 +327,14 @@ function getCategoryForTheme(id: ThemeTemplateId): ThemeCategory {
elegant: 'popular',
minimal: 'minimal',
boutique: 'modern',
+ 'tech-nova': 'popular',
+ 'fashion-forward': 'popular',
+ 'cafe-bistro': 'modern',
+ 'glow-up': 'modern',
+ 'home-vibes': 'classic',
+ 'sport-zone': 'popular',
+ 'jewel-box': 'popular',
+ bookshelf: 'classic',
};
return categories[id] || 'all';
}
@@ -328,6 +347,14 @@ function getTagsForTheme(id: ThemeTemplateId): string[] {
elegant: ['Luxury', 'Sophisticated', 'Premium'],
minimal: ['Ultra-clean', 'Simple', 'Apple-inspired'],
boutique: ['Playful', 'Friendly', 'Small Business'],
+ 'tech-nova': ['Electronics', 'Dark', 'Futuristic'],
+ 'fashion-forward': ['Fashion', 'Editorial', 'Apparel'],
+ 'cafe-bistro': ['Food', 'Coffee', 'Warm'],
+ 'glow-up': ['Beauty', 'Cosmetics', 'Skincare'],
+ 'home-vibes': ['Home', 'Decor', 'Lifestyle'],
+ 'sport-zone': ['Sports', 'Fitness', 'Dynamic'],
+ 'jewel-box': ['Jewelry', 'Luxury', 'Gold'],
+ bookshelf: ['Books', 'Education', 'Academic'],
};
return tags[id] || [];
}
diff --git a/src/lib/storefront/theme-templates.ts b/src/lib/storefront/theme-templates.ts
index f797de8a..6f4bd458 100644
--- a/src/lib/storefront/theme-templates.ts
+++ b/src/lib/storefront/theme-templates.ts
@@ -1,10 +1,3 @@
-/**
- * Storefront Theme Templates
- *
- * Predefined theme configurations for storefronts.
- * Each template defines colors, typography, layout, and default component styles.
- */
-
import type { ThemeTemplate, ThemeTemplateId } from './types';
/**
@@ -14,12 +7,14 @@ const modernTheme: ThemeTemplate = {
id: 'modern',
name: 'Modern',
description: 'Clean, minimal design with subtle colors and spacious layout',
+ category: 'GENERAL',
+ tags: ['clean', 'minimal', 'spacious', 'professional'],
theme: {
templateId: 'modern',
colors: {
- primary: '#4F46E5', // Indigo
- secondary: '#10B981', // Emerald
- accent: '#F59E0B', // Amber
+ primary: '#4F46E5',
+ secondary: '#10B981',
+ accent: '#F59E0B',
background: '#FFFFFF',
foreground: '#111827',
muted: '#F3F4F6',
@@ -48,13 +43,15 @@ const classicTheme: ThemeTemplate = {
id: 'classic',
name: 'Classic',
description: 'Traditional e-commerce style with warm, inviting colors',
+ category: 'GENERAL',
+ tags: ['traditional', 'warm', 'e-commerce', 'trusted'],
theme: {
templateId: 'classic',
colors: {
- primary: '#B91C1C', // Red
- secondary: '#0369A1', // Blue
- accent: '#D97706', // Orange
- background: '#FFFBEB', // Warm white
+ primary: '#B91C1C',
+ secondary: '#0369A1',
+ accent: '#D97706',
+ background: '#FFFBEB',
foreground: '#1F2937',
muted: '#FEF3C7',
},
@@ -83,13 +80,15 @@ const boldTheme: ThemeTemplate = {
id: 'bold',
name: 'Bold',
description: 'Vibrant colors with large typography for maximum impact',
+ category: 'GENERAL',
+ tags: ['vibrant', 'bold', 'dark', 'impactful'],
theme: {
templateId: 'bold',
colors: {
- primary: '#DC2626', // Bright Red
- secondary: '#7C3AED', // Purple
- accent: '#FACC15', // Yellow
- background: '#18181B', // Dark
+ primary: '#DC2626',
+ secondary: '#7C3AED',
+ accent: '#FACC15',
+ background: '#18181B',
foreground: '#FAFAFA',
muted: '#27272A',
},
@@ -118,13 +117,15 @@ const elegantTheme: ThemeTemplate = {
id: 'elegant',
name: 'Elegant',
description: 'Luxury, sophisticated design with refined typography',
+ category: 'GENERAL',
+ tags: ['luxury', 'sophisticated', 'refined', 'premium'],
theme: {
templateId: 'elegant',
colors: {
- primary: '#1F2937', // Dark gray
- secondary: '#B45309', // Gold
- accent: '#059669', // Teal
- background: '#FAFAF9', // Off-white
+ primary: '#1F2937',
+ secondary: '#B45309',
+ accent: '#059669',
+ background: '#FAFAF9',
foreground: '#1C1917',
muted: '#E7E5E4',
},
@@ -153,13 +154,15 @@ const minimalTheme: ThemeTemplate = {
id: 'minimal',
name: 'Minimal',
description: 'Ultra-clean, whitespace-focused design with subtle elegance',
+ category: 'GENERAL',
+ tags: ['minimalist', 'clean', 'whitespace', 'apple-style'],
theme: {
templateId: 'minimal',
colors: {
- primary: '#000000', // Pure black
- secondary: '#6B7280', // Gray
- accent: '#3B82F6', // Blue
- background: '#FFFFFF', // Pure white
+ primary: '#000000',
+ secondary: '#6B7280',
+ accent: '#3B82F6',
+ background: '#FFFFFF',
foreground: '#000000',
muted: '#F9FAFB',
},
@@ -187,13 +190,15 @@ const boutiqueTheme: ThemeTemplate = {
id: 'boutique',
name: 'Boutique',
description: 'Warm and inviting design perfect for small businesses',
+ category: 'FASHION',
+ tags: ['small-business', 'playful', 'warm', 'boutique'],
theme: {
templateId: 'boutique',
colors: {
- primary: '#EC4899', // Pink
- secondary: '#8B5CF6', // Purple
- accent: '#F59E0B', // Amber
- background: '#FFFBF5', // Warm cream
+ primary: '#EC4899',
+ secondary: '#8B5CF6',
+ accent: '#F59E0B',
+ background: '#FFFBF5',
foreground: '#1F2937',
muted: '#FEF3E2',
},
@@ -215,6 +220,304 @@ const boutiqueTheme: ThemeTemplate = {
defaultProductCard: 'standard',
};
+// ─────────────────────────────────────────────
+// Extended Marketplace Themes (categorised)
+// ─────────────────────────────────────────────
+
+/**
+ * TechNova - Electronics & Gadgets store
+ */
+const techNovaTheme: ThemeTemplate = {
+ id: 'tech-nova',
+ name: 'TechNova',
+ description: 'Futuristic dark theme built for electronics & gadget stores',
+ category: 'ELECTRONICS',
+ tags: ['electronics', 'gadgets', 'dark', 'futuristic', 'tech'],
+ theme: {
+ templateId: 'tech-nova',
+ colors: {
+ primary: '#06B6D4', // Cyan
+ secondary: '#3B82F6', // Blue
+ accent: '#A855F7', // Purple
+ background: '#0F172A', // Slate dark
+ foreground: '#F1F5F9',
+ muted: '#1E293B',
+ },
+ typography: {
+ fontFamily: 'geist',
+ baseFontSize: 16,
+ headingScale: 1.333,
+ },
+ layout: 'full-width',
+ borderRadius: 'md',
+ },
+ defaultHero: {
+ style: 'gradient',
+ backgroundGradient: 'from-slate-900 via-cyan-950 to-slate-900',
+ alignment: 'center',
+ minHeight: 'xl',
+ },
+ defaultProductCard: 'overlay',
+};
+
+/**
+ * FashionForward - Fashion & Apparel
+ */
+const fashionForwardTheme: ThemeTemplate = {
+ id: 'fashion-forward',
+ name: 'FashionForward',
+ description: 'Editorial fashion aesthetic — clean lines with striking typography',
+ category: 'FASHION',
+ tags: ['fashion', 'apparel', 'editorial', 'clothing', 'style'],
+ theme: {
+ templateId: 'fashion-forward',
+ colors: {
+ primary: '#111111',
+ secondary: '#C9A96E', // Gold
+ accent: '#E63946', // Red
+ background: '#FAFAFA',
+ foreground: '#111111',
+ muted: '#F0EDE6',
+ },
+ typography: {
+ fontFamily: 'inter',
+ headingFontFamily: 'playfair',
+ baseFontSize: 15,
+ headingScale: 1.414,
+ },
+ layout: 'full-width',
+ borderRadius: 'none',
+ },
+ defaultHero: {
+ style: 'split',
+ alignment: 'left',
+ minHeight: 'xl',
+ overlayOpacity: 20,
+ },
+ defaultProductCard: 'overlay',
+};
+
+/**
+ * CafeBistro - Food, Coffee & Beverages
+ */
+const cafeBistroTheme: ThemeTemplate = {
+ id: 'cafe-bistro',
+ name: 'CafeBistro',
+ description: 'Warm, earthy palette designed for food, café & beverage brands',
+ category: 'FOOD_BEVERAGE',
+ tags: ['food', 'coffee', 'cafe', 'restaurant', 'warm', 'earthy'],
+ theme: {
+ templateId: 'cafe-bistro',
+ colors: {
+ primary: '#92400E', // Brown
+ secondary: '#16A34A', // Green
+ accent: '#F97316', // Orange
+ background: '#FFFDF7', // Cream
+ foreground: '#292524',
+ muted: '#FEF9EF',
+ },
+ typography: {
+ fontFamily: 'poppins',
+ headingFontFamily: 'playfair',
+ baseFontSize: 16,
+ headingScale: 1.333,
+ },
+ layout: 'boxed',
+ borderRadius: 'lg',
+ },
+ defaultHero: {
+ style: 'image',
+ alignment: 'center',
+ minHeight: 'lg',
+ overlayOpacity: 30,
+ },
+ defaultProductCard: 'detailed',
+};
+
+/**
+ * GlowUp - Health, Beauty & Cosmetics
+ */
+const glowUpTheme: ThemeTemplate = {
+ id: 'glow-up',
+ name: 'GlowUp',
+ description: 'Soft, radiant design for health, beauty and cosmetics brands',
+ category: 'HEALTH_BEAUTY',
+ tags: ['beauty', 'cosmetics', 'skincare', 'health', 'feminine', 'soft'],
+ theme: {
+ templateId: 'glow-up',
+ colors: {
+ primary: '#BE185D', // Deep rose
+ secondary: '#7C3AED', // Violet
+ accent: '#F59E0B', // Gold
+ background: '#FFF5F7', // Blush
+ foreground: '#1F2937',
+ muted: '#FCE7F3',
+ },
+ typography: {
+ fontFamily: 'poppins',
+ headingFontFamily: 'playfair',
+ baseFontSize: 15,
+ headingScale: 1.25,
+ },
+ layout: 'centered',
+ borderRadius: 'xl',
+ },
+ defaultHero: {
+ style: 'gradient',
+ backgroundGradient: 'from-rose-50 via-pink-50 to-purple-50',
+ alignment: 'center',
+ minHeight: 'lg',
+ },
+ defaultProductCard: 'minimal',
+};
+
+/**
+ * HomeVibes - Home Decor & Lifestyle
+ */
+const homeVibesTheme: ThemeTemplate = {
+ id: 'home-vibes',
+ name: 'HomeVibes',
+ description: 'Natural, cozy palette for home decor and lifestyle products',
+ category: 'HOME_LIFESTYLE',
+ tags: ['home', 'decor', 'furniture', 'natural', 'cozy', 'lifestyle'],
+ theme: {
+ templateId: 'home-vibes',
+ colors: {
+ primary: '#78350F', // Warm brown
+ secondary: '#4D7C0F', // Olive
+ accent: '#0F766E', // Teal
+ background: '#FDFAF4', // Linen
+ foreground: '#1C1917',
+ muted: '#F5F0E8',
+ },
+ typography: {
+ fontFamily: 'inter',
+ headingFontFamily: 'playfair',
+ baseFontSize: 16,
+ headingScale: 1.25,
+ },
+ layout: 'full-width',
+ borderRadius: 'md',
+ },
+ defaultHero: {
+ style: 'gradient',
+ backgroundGradient: 'from-amber-50 via-stone-50 to-green-50',
+ alignment: 'left',
+ minHeight: 'lg',
+ },
+ defaultProductCard: 'standard',
+};
+
+/**
+ * SportZone - Sports, Fitness & Outdoors
+ */
+const sportZoneTheme: ThemeTemplate = {
+ id: 'sport-zone',
+ name: 'SportZone',
+ description: 'High-energy, dynamic design for sports and fitness brands',
+ category: 'SPORTS_OUTDOORS',
+ tags: ['sports', 'fitness', 'outdoors', 'dynamic', 'energetic'],
+ theme: {
+ templateId: 'sport-zone',
+ colors: {
+ primary: '#DC2626', // Red
+ secondary: '#1D4ED8', // Blue
+ accent: '#16A34A', // Green
+ background: '#0F0F0F', // Near black
+ foreground: '#F9FAFB',
+ muted: '#1F1F1F',
+ },
+ typography: {
+ fontFamily: 'montserrat',
+ baseFontSize: 16,
+ headingScale: 1.5,
+ },
+ layout: 'full-width',
+ borderRadius: 'sm',
+ },
+ defaultHero: {
+ style: 'gradient',
+ backgroundGradient: 'from-red-950 via-gray-950 to-blue-950',
+ alignment: 'center',
+ minHeight: 'xl',
+ },
+ defaultProductCard: 'overlay',
+};
+
+/**
+ * JewelBox - Jewelry & Accessories
+ */
+const jewelBoxTheme: ThemeTemplate = {
+ id: 'jewel-box',
+ name: 'JewelBox',
+ description: 'Opulent dark design with gold accents for jewelry stores',
+ category: 'JEWELRY',
+ tags: ['jewelry', 'accessories', 'luxury', 'gold', 'dark', 'opulent'],
+ theme: {
+ templateId: 'jewel-box',
+ colors: {
+ primary: '#D4AF37', // Gold
+ secondary: '#8B6914', // Dark gold
+ accent: '#E8CEFF', // Lavender
+ background: '#0A0A0A', // Jet black
+ foreground: '#F5F5F0',
+ muted: '#1A1A1A',
+ },
+ typography: {
+ fontFamily: 'inter',
+ headingFontFamily: 'playfair',
+ baseFontSize: 15,
+ headingScale: 1.25,
+ },
+ layout: 'centered',
+ borderRadius: 'none',
+ },
+ defaultHero: {
+ style: 'minimal',
+ alignment: 'center',
+ minHeight: 'lg',
+ backgroundGradient: 'from-black via-yellow-950 to-black',
+ },
+ defaultProductCard: 'minimal',
+};
+
+/**
+ * Bookshelf - Books & Education
+ */
+const bookshelfTheme: ThemeTemplate = {
+ id: 'bookshelf',
+ name: 'Bookshelf',
+ description: 'Intellectual, clean design for book stores and education platforms',
+ category: 'BOOKS_EDUCATION',
+ tags: ['books', 'education', 'knowledge', 'academic', 'reading'],
+ theme: {
+ templateId: 'bookshelf',
+ colors: {
+ primary: '#1E3A5F', // Navy
+ secondary: '#C2410C', // Terra cotta
+ accent: '#0D9488', // Teal
+ background: '#FFFEF7', // Ivory
+ foreground: '#1A1A2E',
+ muted: '#F3F0E6',
+ },
+ typography: {
+ fontFamily: 'inter',
+ headingFontFamily: 'playfair',
+ baseFontSize: 16,
+ headingScale: 1.333,
+ },
+ layout: 'boxed',
+ borderRadius: 'sm',
+ },
+ defaultHero: {
+ style: 'split',
+ alignment: 'left',
+ minHeight: 'md',
+ backgroundGradient: 'from-blue-950 to-indigo-950',
+ },
+ defaultProductCard: 'detailed',
+};
+
/**
* All available theme templates
*/
@@ -225,6 +528,14 @@ export const themeTemplates: Record
= {
elegant: elegantTheme,
minimal: minimalTheme,
boutique: boutiqueTheme,
+ 'tech-nova': techNovaTheme,
+ 'fashion-forward': fashionForwardTheme,
+ 'cafe-bistro': cafeBistroTheme,
+ 'glow-up': glowUpTheme,
+ 'home-vibes': homeVibesTheme,
+ 'sport-zone': sportZoneTheme,
+ 'jewel-box': jewelBoxTheme,
+ bookshelf: bookshelfTheme,
};
/**
@@ -241,6 +552,13 @@ export function getAllThemeTemplates(): ThemeTemplate[] {
return Object.values(themeTemplates);
}
+/**
+ * Get themes filtered by category
+ */
+export function getThemesByCategory(category: ThemeTemplate['category']): ThemeTemplate[] {
+ return Object.values(themeTemplates).filter((t) => t.category === category);
+}
+
/**
* Generate CSS variables from theme settings
*/
@@ -272,4 +590,3 @@ function getBorderRadiusValue(radius: ThemeTemplate['theme']['borderRadius']): s
};
return values[radius] || '8px';
}
-
diff --git a/src/lib/storefront/types.ts b/src/lib/storefront/types.ts
index 8d62abc4..f626fe19 100644
--- a/src/lib/storefront/types.ts
+++ b/src/lib/storefront/types.ts
@@ -6,7 +6,35 @@
*/
// Theme template identifiers
-export type ThemeTemplateId = 'modern' | 'classic' | 'bold' | 'elegant' | 'minimal' | 'boutique';
+export type ThemeTemplateId =
+ | 'modern'
+ | 'classic'
+ | 'bold'
+ | 'elegant'
+ | 'minimal'
+ | 'boutique'
+ // Extended marketplace themes
+ | 'tech-nova'
+ | 'fashion-forward'
+ | 'cafe-bistro'
+ | 'glow-up'
+ | 'home-vibes'
+ | 'sport-zone'
+ | 'jewel-box'
+ | 'bookshelf';
+
+// Theme marketplace categories (mirrors Prisma ThemeCategory enum)
+export type ThemeCategory =
+ | 'GENERAL'
+ | 'ELECTRONICS'
+ | 'FASHION'
+ | 'FOOD_BEVERAGE'
+ | 'HEALTH_BEAUTY'
+ | 'HOME_LIFESTYLE'
+ | 'SPORTS_OUTDOORS'
+ | 'BOOKS_EDUCATION'
+ | 'JEWELRY'
+ | 'AUTOMOTIVE';
// Layout variants
export type LayoutVariant = 'full-width' | 'boxed' | 'centered';
@@ -488,9 +516,40 @@ export interface ThemeTemplate {
id: ThemeTemplateId;
name: string;
description: string;
+ category: ThemeCategory;
+ tags: string[];
preview?: string; // Preview image URL
theme: ThemeSettings;
defaultHero: Partial;
defaultProductCard: ProductCardStyle;
}
+/**
+ * Marketplace theme metadata returned from the API
+ */
+export interface MarketplaceThemeMeta {
+ id: string;
+ slug: string;
+ name: string;
+ description: string;
+ longDescription?: string;
+ category: ThemeCategory;
+ tags: string[];
+ previewImageUrl?: string;
+ thumbnailUrl?: string;
+ previewImages: string[];
+ config: ThemeSettings;
+ status: 'ACTIVE' | 'DRAFT' | 'ARCHIVED';
+ isFeatured: boolean;
+ isPremium: boolean;
+ price: number;
+ author: string;
+ version: string;
+ downloadCount: number;
+ averageRating: number;
+ ratingCount: number;
+ createdAt: string;
+ updatedAt: string;
+ isInstalled?: boolean;
+}
+
diff --git a/tsconfig.json b/tsconfig.json
index e3b11f49..2d7f2a9d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -40,6 +40,7 @@
"node_modules",
"scripts",
".next",
+ "apps",
"src/test/**",
"e2e/**",
"coverage/**",
diff --git a/vercel.json b/vercel.json
index 9c6f2572..602fd977 100644
--- a/vercel.json
+++ b/vercel.json
@@ -32,3 +32,4 @@
},
"crons": []
}
+
From 296a2176be0951547e07db06a93e9145c620a8ad Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 5 Mar 2026 11:31:01 +0000
Subject: [PATCH 3/3] fix: address code review comments + add migration and
docs
- Add MOCK_DOWNLOAD_COUNTS/MOCK_AVERAGE_RATINGS named constants
- Type themeConfig as ThemeSettings (not object) in install route
- Fix NEXT_PUBLIC_MAIN_APP_URL fallback to 'http://localhost:3000' in zone clients
- Extract headingFontSize calculation variable in typography preview
- Use radiusClasses object lookup instead of nested ternaries
- Remove redundant type assertions in editor page (StorefrontDraft.theme properly typed)
- Add Prisma migration SQL for MarketplaceTheme/ThemeInstallation/ThemeRating
- Add prisma/seed-marketplace.mjs for seeding 14 themes
- Add docs/MULTI_ZONE_SETUP.md with setup guide
Co-authored-by: rezwana-karim <126201034+rezwana-karim@users.noreply.github.com>
---
apps/theme-editor/src/app/page.tsx | 4 +-
.../src/components/theme-editor-client.tsx | 48 ++--
apps/theme-editor/src/lib/types.ts | 2 +-
.../src/components/theme-detail-client.tsx | 2 +-
.../src/components/theme-gallery.tsx | 2 +-
docs/MULTI_ZONE_SETUP.md | 192 ++++++++++++++
.../migration.sql | 92 +++++++
prisma/seed-marketplace.mjs | 246 ++++++++++++++++++
.../marketplace/themes/[id]/install/route.ts | 4 +-
src/app/api/marketplace/themes/route.ts | 7 +-
10 files changed, 572 insertions(+), 27 deletions(-)
create mode 100644 docs/MULTI_ZONE_SETUP.md
create mode 100644 prisma/migrations/20260305000000_add_theme_marketplace/migration.sql
create mode 100644 prisma/seed-marketplace.mjs
diff --git a/apps/theme-editor/src/app/page.tsx b/apps/theme-editor/src/app/page.tsx
index a9588f1c..cf7a552c 100644
--- a/apps/theme-editor/src/app/page.tsx
+++ b/apps/theme-editor/src/app/page.tsx
@@ -79,8 +79,8 @@ export default async function ThemeEditorPage({
// Theme priority: marketplace theme URL param > draft/published > default
const initialTheme: ThemeSettings =
- (marketplaceThemeConfig as ThemeSettings | null) ??
- (config?.theme as ThemeSettings | undefined) ??
+ marketplaceThemeConfig ??
+ config?.theme ??
DEFAULT_THEME;
const storeName = storeInfo?.name ?? 'My Store';
diff --git a/apps/theme-editor/src/components/theme-editor-client.tsx b/apps/theme-editor/src/components/theme-editor-client.tsx
index 0751fc20..538b318a 100644
--- a/apps/theme-editor/src/components/theme-editor-client.tsx
+++ b/apps/theme-editor/src/components/theme-editor-client.tsx
@@ -474,12 +474,19 @@ function TypographySection({
{/* Preview */}
Preview
-
- Heading
-
-
- Body text sample.
-
+ {(() => {
+ const headingFontSize = typography.baseFontSize * typography.headingScale * typography.headingScale;
+ return (
+ <>
+
+ Heading
+
+
+ Body text sample.
+
+ >
+ );
+ })()}
);
@@ -536,18 +543,23 @@ function LayoutSection({
{/* Visual preview */}
- {(['none', 'sm', 'md', 'lg', 'xl'] as const).map((r) => (
-
- ))}
+ {(() => {
+ const radiusClasses: Record
= {
+ none: 'rounded-none',
+ sm: 'rounded',
+ md: 'rounded-md',
+ lg: 'rounded-xl',
+ xl: 'rounded-2xl',
+ };
+ return (['none', 'sm', 'md', 'lg', 'xl'] as const).map((r) => (
+
+ ));
+ })()}
diff --git a/apps/theme-editor/src/lib/types.ts b/apps/theme-editor/src/lib/types.ts
index c504d9c1..1f513f9c 100644
--- a/apps/theme-editor/src/lib/types.ts
+++ b/apps/theme-editor/src/lib/types.ts
@@ -48,7 +48,7 @@ export interface ThemeSettings {
}
export interface StorefrontDraft {
- theme: ThemeSettings;
+ theme?: ThemeSettings;
storeName?: string;
storeSlug?: string;
[key: string]: unknown;
diff --git a/apps/theme-marketplace/src/components/theme-detail-client.tsx b/apps/theme-marketplace/src/components/theme-detail-client.tsx
index d959a8f6..5d5c0a29 100644
--- a/apps/theme-marketplace/src/components/theme-detail-client.tsx
+++ b/apps/theme-marketplace/src/components/theme-detail-client.tsx
@@ -13,7 +13,7 @@ export function ThemeDetailClient({ theme, storeId }: Props) {
const [installed, setInstalled] = useState(theme.isInstalled ?? false);
const [error, setError] = useState('');
- const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL ?? '';
+ const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL ?? 'http://localhost:3000';
const handleInstall = async () => {
if (!storeId) {
diff --git a/apps/theme-marketplace/src/components/theme-gallery.tsx b/apps/theme-marketplace/src/components/theme-gallery.tsx
index 3e290677..653e5d08 100644
--- a/apps/theme-marketplace/src/components/theme-gallery.tsx
+++ b/apps/theme-marketplace/src/components/theme-gallery.tsx
@@ -37,7 +37,7 @@ export function ThemeGallery({ initialData, storeId }: Props) {
});
try {
- const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL ?? '';
+ const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL ?? 'http://localhost:3000';
const apiUrl = new URL(`${mainAppUrl}/api/marketplace/themes`);
if (category) apiUrl.searchParams.set('category', category);
if (q) apiUrl.searchParams.set('q', q);
diff --git a/docs/MULTI_ZONE_SETUP.md b/docs/MULTI_ZONE_SETUP.md
new file mode 100644
index 00000000..cd44ffbd
--- /dev/null
+++ b/docs/MULTI_ZONE_SETUP.md
@@ -0,0 +1,192 @@
+# Theme Marketplace & Theme Editor — Multi-Zone Setup
+
+This document describes the **multi-zone architecture** for the StormCom Theme Marketplace and Theme Editor.
+
+---
+
+## Architecture Overview
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ Main StormCom App (localhost:3000) │
+│ │
+│ /themes/* ──rewrite──▶ Theme Marketplace │
+│ /themes/editor/* ──rewrite──▶ Theme Editor │
+│ /api/marketplace/themes/* (local API routes) │
+└─────────────────────────────────────────────────────────┘
+ │ │
+ ▼ ▼
+┌──────────────────┐ ┌───────────────────┐
+│ Theme │ │ Theme Editor │
+│ Marketplace │ │ (localhost:3002) │
+│ (localhost:3001)│ │ │
+│ basePath: │ │ basePath: │
+│ /themes │ │ /themes/editor │
+└──────────────────┘ └───────────────────┘
+```
+
+Both zone apps communicate back to the main app via its REST API for all data operations (auth, storefront config, marketplace themes).
+
+---
+
+## Local Development
+
+### 1. Start the Main App
+
+```bash
+npm run dev # Starts on port 3000
+```
+
+### 2. Start the Theme Marketplace Zone
+
+```bash
+cd apps/theme-marketplace
+npm install
+NEXT_PUBLIC_MAIN_APP_URL=http://localhost:3000 npm run dev
+# → http://localhost:3001
+```
+
+### 3. Start the Theme Editor Zone
+
+```bash
+cd apps/theme-editor
+npm install
+NEXT_PUBLIC_MAIN_APP_URL=http://localhost:3000 npm run dev
+# → http://localhost:3002
+```
+
+### 4. Access via Main App (multi-zone URLs)
+
+The main app's `next.config.ts` rewrites these routes:
+
+| URL | Zone |
+|-----|------|
+| `http://localhost:3000/themes` | Theme Marketplace (port 3001) |
+| `http://localhost:3000/themes/editor` | Theme Editor (port 3002) |
+
+---
+
+## Environment Variables
+
+### Main App (`.env.local`)
+
+```env
+# URLs of separately deployed zone apps
+THEME_MARKETPLACE_URL=http://localhost:3001
+THEME_EDITOR_URL=http://localhost:3002
+```
+
+### Zone Apps
+
+```env
+# URL of the main StormCom app (for API calls)
+NEXT_PUBLIC_MAIN_APP_URL=http://localhost:3000
+```
+
+---
+
+## Database Setup
+
+### Run Migration
+
+```bash
+export $(cat .env.local | grep -v '^#' | grep -v '^$' | sed 's/"//g' | xargs)
+npx prisma migrate dev
+```
+
+This adds:
+- `MarketplaceTheme` — stores theme catalog
+- `ThemeInstallation` — tracks which stores have installed which themes
+- `ThemeRating` — star ratings + reviews from store owners
+
+### Seed Marketplace Themes
+
+```bash
+node prisma/seed-marketplace.mjs
+```
+
+---
+
+## API Routes (Main App)
+
+| Method | Path | Description |
+|--------|------|-------------|
+| GET | `/api/marketplace/themes` | List themes (supports `category`, `q`, `sort`, `page`, `storeId`) |
+| GET | `/api/marketplace/themes/:id` | Get single theme details |
+| POST | `/api/marketplace/themes/:id/install` | Install theme to store (auth required) |
+| POST | `/api/marketplace/themes/:id/rate` | Submit star rating (auth required) |
+
+---
+
+## Theme Categories
+
+| Category | Description |
+|----------|-------------|
+| `GENERAL` | Multi-purpose store themes |
+| `ELECTRONICS` | Tech & gadget stores |
+| `FASHION` | Apparel & clothing |
+| `FOOD_BEVERAGE` | Cafés, restaurants, food brands |
+| `HEALTH_BEAUTY` | Cosmetics, skincare, wellness |
+| `HOME_LIFESTYLE` | Home decor & furniture |
+| `SPORTS_OUTDOORS` | Sports & fitness brands |
+| `BOOKS_EDUCATION` | Bookstores & educational platforms |
+| `JEWELRY` | Jewelry & luxury accessories |
+| `AUTOMOTIVE` | Auto parts & accessories |
+
+---
+
+## Built-in Themes (14 total)
+
+| Slug | Name | Category |
+|------|------|----------|
+| `modern` | Modern | GENERAL |
+| `classic` | Classic | GENERAL |
+| `bold` | Bold | GENERAL |
+| `elegant` | Elegant | GENERAL |
+| `minimal` | Minimal | GENERAL |
+| `boutique` | Boutique | FASHION |
+| `tech-nova` | TechNova | ELECTRONICS |
+| `fashion-forward` | FashionForward | FASHION |
+| `cafe-bistro` | CafeBistro | FOOD_BEVERAGE |
+| `glow-up` | GlowUp | HEALTH_BEAUTY |
+| `home-vibes` | HomeVibes | HOME_LIFESTYLE |
+| `sport-zone` | SportZone | SPORTS_OUTDOORS |
+| `jewel-box` | JewelBox | JEWELRY |
+| `bookshelf` | Bookshelf | BOOKS_EDUCATION |
+
+---
+
+## Theme Editor Features
+
+- **Live Preview** — iframe renders the store's actual storefront, updated in real-time via `postMessage`
+- **Color Editor** — Color picker + hex input for all 6 color tokens; 8 quick palette presets
+- **Typography Editor** — Body/heading font selection, font size slider (14–18px), heading scale slider
+- **Layout Editor** — Page layout (full-width / boxed / centered) + border radius presets
+- **Custom CSS** — Raw CSS override with CSS variable documentation
+- **Auto-save Draft** — Changes auto-save after 2 seconds of inactivity
+- **Publish** — One-click publish of draft to live storefront
+- **Device Preview** — Desktop / tablet / mobile viewport switcher
+
+---
+
+## Vercel Deployment (Multi-Zone)
+
+Deploy each zone as a separate Vercel project, then set environment variables:
+
+**Main app:**
+```
+THEME_MARKETPLACE_URL=https://your-theme-marketplace.vercel.app
+THEME_EDITOR_URL=https://your-theme-editor.vercel.app
+```
+
+**Theme Marketplace app (`apps/theme-marketplace`):**
+```
+NEXT_PUBLIC_MAIN_APP_URL=https://your-stormcom-app.vercel.app
+```
+
+**Theme Editor app (`apps/theme-editor`):**
+```
+NEXT_PUBLIC_MAIN_APP_URL=https://your-stormcom-app.vercel.app
+```
+
+Each zone app has its own `vercel.json` and `package.json`.
diff --git a/prisma/migrations/20260305000000_add_theme_marketplace/migration.sql b/prisma/migrations/20260305000000_add_theme_marketplace/migration.sql
new file mode 100644
index 00000000..c5c83f6b
--- /dev/null
+++ b/prisma/migrations/20260305000000_add_theme_marketplace/migration.sql
@@ -0,0 +1,92 @@
+-- CreateEnum
+CREATE TYPE "ThemeStatus" AS ENUM ('ACTIVE', 'DRAFT', 'ARCHIVED');
+
+-- CreateEnum
+CREATE TYPE "ThemeCategory" AS ENUM ('GENERAL', 'ELECTRONICS', 'FASHION', 'FOOD_BEVERAGE', 'HEALTH_BEAUTY', 'HOME_LIFESTYLE', 'SPORTS_OUTDOORS', 'BOOKS_EDUCATION', 'JEWELRY', 'AUTOMOTIVE');
+
+-- CreateTable
+CREATE TABLE "marketplace_themes" (
+ "id" TEXT NOT NULL,
+ "slug" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "description" TEXT NOT NULL,
+ "longDescription" TEXT,
+ "category" "ThemeCategory" NOT NULL DEFAULT 'GENERAL',
+ "tags" TEXT[] DEFAULT ARRAY[]::TEXT[],
+ "previewImageUrl" TEXT,
+ "thumbnailUrl" TEXT,
+ "previewImages" TEXT[] DEFAULT ARRAY[]::TEXT[],
+ "config" TEXT NOT NULL,
+ "status" "ThemeStatus" NOT NULL DEFAULT 'ACTIVE',
+ "isFeatured" BOOLEAN NOT NULL DEFAULT false,
+ "isPremium" BOOLEAN NOT NULL DEFAULT false,
+ "price" DOUBLE PRECISION NOT NULL DEFAULT 0,
+ "author" TEXT NOT NULL DEFAULT 'StormCom',
+ "version" TEXT NOT NULL DEFAULT '1.0.0',
+ "downloadCount" INTEGER NOT NULL DEFAULT 0,
+ "ratingSum" INTEGER NOT NULL DEFAULT 0,
+ "ratingCount" INTEGER NOT NULL DEFAULT 0,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "marketplace_themes_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "theme_installations" (
+ "id" TEXT NOT NULL,
+ "storeId" TEXT NOT NULL,
+ "themeId" TEXT NOT NULL,
+ "installedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "theme_installations_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "theme_ratings" (
+ "id" TEXT NOT NULL,
+ "storeId" TEXT NOT NULL,
+ "themeId" TEXT NOT NULL,
+ "rating" INTEGER NOT NULL,
+ "review" TEXT,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "theme_ratings_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "marketplace_themes_slug_key" ON "marketplace_themes"("slug");
+
+-- CreateIndex
+CREATE INDEX "marketplace_themes_category_status_idx" ON "marketplace_themes"("category", "status");
+
+-- CreateIndex
+CREATE INDEX "marketplace_themes_isFeatured_status_idx" ON "marketplace_themes"("isFeatured", "status");
+
+-- CreateIndex
+CREATE INDEX "marketplace_themes_downloadCount_idx" ON "marketplace_themes"("downloadCount");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "theme_installations_storeId_themeId_key" ON "theme_installations"("storeId", "themeId");
+
+-- CreateIndex
+CREATE INDEX "theme_installations_storeId_idx" ON "theme_installations"("storeId");
+
+-- CreateIndex
+CREATE INDEX "theme_installations_themeId_idx" ON "theme_installations"("themeId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "theme_ratings_storeId_themeId_key" ON "theme_ratings"("storeId", "themeId");
+
+-- CreateIndex
+CREATE INDEX "theme_ratings_themeId_idx" ON "theme_ratings"("themeId");
+
+-- AddForeignKey
+ALTER TABLE "theme_installations" ADD CONSTRAINT "theme_installations_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "stores"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "theme_installations" ADD CONSTRAINT "theme_installations_themeId_fkey" FOREIGN KEY ("themeId") REFERENCES "marketplace_themes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "theme_ratings" ADD CONSTRAINT "theme_ratings_themeId_fkey" FOREIGN KEY ("themeId") REFERENCES "marketplace_themes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/seed-marketplace.mjs b/prisma/seed-marketplace.mjs
new file mode 100644
index 00000000..d77441ca
--- /dev/null
+++ b/prisma/seed-marketplace.mjs
@@ -0,0 +1,246 @@
+/**
+ * Theme Marketplace Seed Script
+ *
+ * Seeds the marketplace_themes table with the 14 built-in theme templates.
+ * Run with: node prisma/seed-marketplace.mjs
+ */
+
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+const themes = [
+ {
+ slug: 'modern',
+ name: 'Modern',
+ description: 'Clean, minimal design with subtle colors and spacious layout',
+ category: 'GENERAL',
+ tags: ['clean', 'minimal', 'spacious', 'professional'],
+ isFeatured: true,
+ downloadCount: 850,
+ config: JSON.stringify({
+ templateId: 'modern',
+ colors: { primary: '#4F46E5', secondary: '#10B981', accent: '#F59E0B', background: '#FFFFFF', foreground: '#111827', muted: '#F3F4F6' },
+ typography: { fontFamily: 'geist', baseFontSize: 16, headingScale: 1.25 },
+ layout: 'full-width',
+ borderRadius: 'lg',
+ }),
+ },
+ {
+ slug: 'classic',
+ name: 'Classic',
+ description: 'Traditional e-commerce style with warm, inviting colors',
+ category: 'GENERAL',
+ tags: ['traditional', 'warm', 'e-commerce', 'trusted'],
+ downloadCount: 720,
+ config: JSON.stringify({
+ templateId: 'classic',
+ colors: { primary: '#B91C1C', secondary: '#0369A1', accent: '#D97706', background: '#FFFBEB', foreground: '#1F2937', muted: '#FEF3C7' },
+ typography: { fontFamily: 'roboto', headingFontFamily: 'playfair', baseFontSize: 16, headingScale: 1.333 },
+ layout: 'boxed',
+ borderRadius: 'sm',
+ }),
+ },
+ {
+ slug: 'bold',
+ name: 'Bold',
+ description: 'Vibrant colors with large typography for maximum impact',
+ category: 'GENERAL',
+ tags: ['vibrant', 'bold', 'dark', 'impactful'],
+ isFeatured: true,
+ downloadCount: 980,
+ config: JSON.stringify({
+ templateId: 'bold',
+ colors: { primary: '#DC2626', secondary: '#7C3AED', accent: '#FACC15', background: '#18181B', foreground: '#FAFAFA', muted: '#27272A' },
+ typography: { fontFamily: 'montserrat', baseFontSize: 18, headingScale: 1.5 },
+ layout: 'full-width',
+ borderRadius: 'none',
+ }),
+ },
+ {
+ slug: 'elegant',
+ name: 'Elegant',
+ description: 'Luxury, sophisticated design with refined typography',
+ category: 'GENERAL',
+ tags: ['luxury', 'sophisticated', 'refined', 'premium'],
+ downloadCount: 540,
+ config: JSON.stringify({
+ templateId: 'elegant',
+ colors: { primary: '#1F2937', secondary: '#B45309', accent: '#059669', background: '#FAFAF9', foreground: '#1C1917', muted: '#E7E5E4' },
+ typography: { fontFamily: 'inter', headingFontFamily: 'playfair', baseFontSize: 15, headingScale: 1.25 },
+ layout: 'centered',
+ borderRadius: 'md',
+ }),
+ },
+ {
+ slug: 'minimal',
+ name: 'Minimal',
+ description: 'Ultra-clean, whitespace-focused design with subtle elegance',
+ category: 'GENERAL',
+ tags: ['minimalist', 'clean', 'whitespace', 'apple-style'],
+ isFeatured: true,
+ downloadCount: 630,
+ config: JSON.stringify({
+ templateId: 'minimal',
+ colors: { primary: '#000000', secondary: '#6B7280', accent: '#3B82F6', background: '#FFFFFF', foreground: '#000000', muted: '#F9FAFB' },
+ typography: { fontFamily: 'geist', baseFontSize: 16, headingScale: 1.125 },
+ layout: 'centered',
+ borderRadius: 'sm',
+ }),
+ },
+ {
+ slug: 'boutique',
+ name: 'Boutique',
+ description: 'Warm and inviting design perfect for small businesses',
+ category: 'FASHION',
+ tags: ['small-business', 'playful', 'warm', 'boutique'],
+ downloadCount: 410,
+ config: JSON.stringify({
+ templateId: 'boutique',
+ colors: { primary: '#EC4899', secondary: '#8B5CF6', accent: '#F59E0B', background: '#FFFBF5', foreground: '#1F2937', muted: '#FEF3E2' },
+ typography: { fontFamily: 'poppins', headingFontFamily: 'poppins', baseFontSize: 16, headingScale: 1.333 },
+ layout: 'full-width',
+ borderRadius: 'xl',
+ }),
+ },
+ {
+ slug: 'tech-nova',
+ name: 'TechNova',
+ description: 'Futuristic dark theme built for electronics & gadget stores',
+ category: 'ELECTRONICS',
+ tags: ['electronics', 'gadgets', 'dark', 'futuristic', 'tech'],
+ isFeatured: true,
+ downloadCount: 380,
+ config: JSON.stringify({
+ templateId: 'tech-nova',
+ colors: { primary: '#06B6D4', secondary: '#3B82F6', accent: '#A855F7', background: '#0F172A', foreground: '#F1F5F9', muted: '#1E293B' },
+ typography: { fontFamily: 'geist', baseFontSize: 16, headingScale: 1.333 },
+ layout: 'full-width',
+ borderRadius: 'md',
+ }),
+ },
+ {
+ slug: 'fashion-forward',
+ name: 'FashionForward',
+ description: 'Editorial fashion aesthetic — clean lines with striking typography',
+ category: 'FASHION',
+ tags: ['fashion', 'apparel', 'editorial', 'clothing', 'style'],
+ downloadCount: 290,
+ config: JSON.stringify({
+ templateId: 'fashion-forward',
+ colors: { primary: '#111111', secondary: '#C9A96E', accent: '#E63946', background: '#FAFAFA', foreground: '#111111', muted: '#F0EDE6' },
+ typography: { fontFamily: 'inter', headingFontFamily: 'playfair', baseFontSize: 15, headingScale: 1.414 },
+ layout: 'full-width',
+ borderRadius: 'none',
+ }),
+ },
+ {
+ slug: 'cafe-bistro',
+ name: 'CafeBistro',
+ description: 'Warm, earthy palette designed for food, café & beverage brands',
+ category: 'FOOD_BEVERAGE',
+ tags: ['food', 'coffee', 'cafe', 'restaurant', 'warm', 'earthy'],
+ downloadCount: 270,
+ config: JSON.stringify({
+ templateId: 'cafe-bistro',
+ colors: { primary: '#92400E', secondary: '#16A34A', accent: '#F97316', background: '#FFFDF7', foreground: '#292524', muted: '#FEF9EF' },
+ typography: { fontFamily: 'poppins', headingFontFamily: 'playfair', baseFontSize: 16, headingScale: 1.333 },
+ layout: 'boxed',
+ borderRadius: 'lg',
+ }),
+ },
+ {
+ slug: 'glow-up',
+ name: 'GlowUp',
+ description: 'Soft, radiant design for health, beauty and cosmetics brands',
+ category: 'HEALTH_BEAUTY',
+ tags: ['beauty', 'cosmetics', 'skincare', 'health', 'feminine', 'soft'],
+ downloadCount: 250,
+ config: JSON.stringify({
+ templateId: 'glow-up',
+ colors: { primary: '#BE185D', secondary: '#7C3AED', accent: '#F59E0B', background: '#FFF5F7', foreground: '#1F2937', muted: '#FCE7F3' },
+ typography: { fontFamily: 'poppins', headingFontFamily: 'playfair', baseFontSize: 15, headingScale: 1.25 },
+ layout: 'centered',
+ borderRadius: 'xl',
+ }),
+ },
+ {
+ slug: 'home-vibes',
+ name: 'HomeVibes',
+ description: 'Natural, cozy palette for home decor and lifestyle products',
+ category: 'HOME_LIFESTYLE',
+ tags: ['home', 'decor', 'furniture', 'natural', 'cozy', 'lifestyle'],
+ downloadCount: 210,
+ config: JSON.stringify({
+ templateId: 'home-vibes',
+ colors: { primary: '#78350F', secondary: '#4D7C0F', accent: '#0F766E', background: '#FDFAF4', foreground: '#1C1917', muted: '#F5F0E8' },
+ typography: { fontFamily: 'inter', headingFontFamily: 'playfair', baseFontSize: 16, headingScale: 1.25 },
+ layout: 'full-width',
+ borderRadius: 'md',
+ }),
+ },
+ {
+ slug: 'sport-zone',
+ name: 'SportZone',
+ description: 'High-energy, dynamic design for sports and fitness brands',
+ category: 'SPORTS_OUTDOORS',
+ tags: ['sports', 'fitness', 'outdoors', 'dynamic', 'energetic'],
+ downloadCount: 180,
+ config: JSON.stringify({
+ templateId: 'sport-zone',
+ colors: { primary: '#DC2626', secondary: '#1D4ED8', accent: '#16A34A', background: '#0F0F0F', foreground: '#F9FAFB', muted: '#1F1F1F' },
+ typography: { fontFamily: 'montserrat', baseFontSize: 16, headingScale: 1.5 },
+ layout: 'full-width',
+ borderRadius: 'sm',
+ }),
+ },
+ {
+ slug: 'jewel-box',
+ name: 'JewelBox',
+ description: 'Opulent dark design with gold accents for jewelry stores',
+ category: 'JEWELRY',
+ tags: ['jewelry', 'accessories', 'luxury', 'gold', 'dark', 'opulent'],
+ downloadCount: 155,
+ config: JSON.stringify({
+ templateId: 'jewel-box',
+ colors: { primary: '#D4AF37', secondary: '#8B6914', accent: '#E8CEFF', background: '#0A0A0A', foreground: '#F5F5F0', muted: '#1A1A1A' },
+ typography: { fontFamily: 'inter', headingFontFamily: 'playfair', baseFontSize: 15, headingScale: 1.25 },
+ layout: 'centered',
+ borderRadius: 'none',
+ }),
+ },
+ {
+ slug: 'bookshelf',
+ name: 'Bookshelf',
+ description: 'Intellectual, clean design for book stores and education platforms',
+ category: 'BOOKS_EDUCATION',
+ tags: ['books', 'education', 'knowledge', 'academic', 'reading'],
+ downloadCount: 120,
+ config: JSON.stringify({
+ templateId: 'bookshelf',
+ colors: { primary: '#1E3A5F', secondary: '#C2410C', accent: '#0D9488', background: '#FFFEF7', foreground: '#1A1A2E', muted: '#F3F0E6' },
+ typography: { fontFamily: 'inter', headingFontFamily: 'playfair', baseFontSize: 16, headingScale: 1.333 },
+ layout: 'boxed',
+ borderRadius: 'sm',
+ }),
+ },
+];
+
+async function main() {
+ console.log('Seeding marketplace themes…');
+
+ for (const theme of themes) {
+ await prisma.marketplaceTheme.upsert({
+ where: { slug: theme.slug },
+ create: { ...theme, status: 'ACTIVE' },
+ update: { ...theme },
+ });
+ console.log(` ✓ ${theme.name} (${theme.category})`);
+ }
+
+ console.log(`\nDone! Seeded ${themes.length} themes.`);
+}
+
+main()
+ .catch((e) => { console.error(e); process.exit(1); })
+ .finally(() => prisma.$disconnect());
diff --git a/src/app/api/marketplace/themes/[id]/install/route.ts b/src/app/api/marketplace/themes/[id]/install/route.ts
index 5c9ff605..1916d38a 100644
--- a/src/app/api/marketplace/themes/[id]/install/route.ts
+++ b/src/app/api/marketplace/themes/[id]/install/route.ts
@@ -20,7 +20,7 @@ import {
parseStorefrontConfig,
stringifyStorefrontConfig,
} from '@/lib/storefront/defaults';
-import type { ThemeTemplateId } from '@/lib/storefront/types';
+import type { ThemeSettings, ThemeTemplateId } from '@/lib/storefront/types';
type RouteContext = { params: Promise<{ id: string }> };
@@ -70,7 +70,7 @@ export async function POST(request: NextRequest, props: RouteContext) {
}
// Resolve theme config — prefer DB, fall back to static template
- let themeConfig: object;
+ let themeConfig: ThemeSettings;
const dbTheme = await prisma.marketplaceTheme
.findFirst({ where: { OR: [{ id: themeId }, { slug: themeId }], status: 'ACTIVE' } })
.catch(() => null);
diff --git a/src/app/api/marketplace/themes/route.ts b/src/app/api/marketplace/themes/route.ts
index e6362beb..6a86705a 100644
--- a/src/app/api/marketplace/themes/route.ts
+++ b/src/app/api/marketplace/themes/route.ts
@@ -84,6 +84,9 @@ export async function GET(request: NextRequest) {
// Map static templates into the same shape as DB themes
// (return as plain objects — the client receives JSON anyway)
+ // Representative install counts and ratings for display on a fresh database.
+ const MOCK_DOWNLOAD_COUNTS = [850, 720, 540, 980, 630, 410, 380, 290, 270, 250, 210, 180, 155, 120] as const;
+ const MOCK_AVERAGE_RATINGS = [4.8, 4.6, 4.7, 4.9, 4.5, 4.7, 4.6, 4.8, 4.5, 4.7, 4.6, 4.8, 4.5, 4.7] as const;
const mockThemes = filteredStatic.map((t, idx) => ({
id: t.id,
slug: t.id,
@@ -102,8 +105,8 @@ export async function GET(request: NextRequest) {
price: 0,
author: 'StormCom',
version: '1.0.0',
- downloadCount: [850, 720, 540, 980, 630, 410, 380, 290, 270, 250, 210, 180, 155, 120][idx] ?? 100,
- ratingSum: Math.round(([4.8, 4.6, 4.7, 4.9, 4.5, 4.7, 4.6, 4.8, 4.5, 4.7, 4.6, 4.8, 4.5, 4.7][idx] ?? 4.5) * 10),
+ downloadCount: MOCK_DOWNLOAD_COUNTS[idx] ?? 100,
+ ratingSum: Math.round((MOCK_AVERAGE_RATINGS[idx] ?? 4.5) * 10),
ratingCount: 10,
createdAt: new Date(),
updatedAt: new Date(),