This commit completes the migration to a fully module-driven architecture: ## Models Migration - Moved all domain models from models/database/ to their respective modules: - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc. - cms: MediaFile, VendorTheme - messaging: Email, VendorEmailSettings, VendorEmailTemplate - core: AdminMenuConfig - models/database/ now only contains Base and TimestampMixin (infrastructure) ## Schemas Migration - Moved all domain schemas from models/schema/ to their respective modules: - tenancy: company, vendor, admin, team, vendor_domain - cms: media, image, vendor_theme - messaging: email - models/schema/ now only contains base.py and auth.py (infrastructure) ## Routes Migration - Moved admin routes from app/api/v1/admin/ to modules: - menu_config.py -> core module - modules.py -> tenancy module - module_config.py -> tenancy module - app/api/v1/admin/ now only aggregates auto-discovered module routes ## Menu System - Implemented module-driven menu system with MenuDiscoveryService - Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT - Added MenuItemDefinition and MenuSectionDefinition dataclasses - Each module now defines its own menu items in definition.py - MenuService integrates with MenuDiscoveryService for template rendering ## Documentation - Updated docs/architecture/models-structure.md - Updated docs/architecture/menu-management.md - Updated architecture validation rules for new exceptions ## Architecture Validation - Updated MOD-019 rule to allow base.py in models/schema/ - Created core module exceptions.py and schemas/ directory - All validation errors resolved (only warnings remain) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
250 lines
8.1 KiB
Python
250 lines
8.1 KiB
Python
# app/core/theme_presets.py
|
|
"""
|
|
Theme presets for vendor shops.
|
|
|
|
Presets define default color schemes, fonts, and layouts that vendors can choose from.
|
|
Each preset provides a complete theme configuration that can be customized further.
|
|
"""
|
|
|
|
from app.modules.cms.models import VendorTheme
|
|
|
|
THEME_PRESETS = {
|
|
"default": {
|
|
"colors": {
|
|
"primary": "#6366f1", # Indigo
|
|
"secondary": "#8b5cf6", # Purple
|
|
"accent": "#ec4899", # Pink
|
|
"background": "#ffffff", # White
|
|
"text": "#1f2937", # Gray-800
|
|
"border": "#e5e7eb", # Gray-200
|
|
},
|
|
"fonts": {"heading": "Inter, sans-serif", "body": "Inter, sans-serif"},
|
|
"layout": {"style": "grid", "header": "fixed", "product_card": "modern"},
|
|
},
|
|
"modern": {
|
|
"colors": {
|
|
"primary": "#6366f1", # Indigo - Modern tech look
|
|
"secondary": "#8b5cf6", # Purple
|
|
"accent": "#ec4899", # Pink
|
|
"background": "#ffffff", # White
|
|
"text": "#1f2937", # Gray-800
|
|
"border": "#e5e7eb", # Gray-200
|
|
},
|
|
"fonts": {"heading": "Inter, sans-serif", "body": "Inter, sans-serif"},
|
|
"layout": {"style": "grid", "header": "fixed", "product_card": "modern"},
|
|
},
|
|
"classic": {
|
|
"colors": {
|
|
"primary": "#1e40af", # Dark blue - Traditional
|
|
"secondary": "#7c3aed", # Purple
|
|
"accent": "#dc2626", # Red
|
|
"background": "#ffffff", # White
|
|
"text": "#1f2937", # Gray-800
|
|
"border": "#d1d5db", # Gray-300
|
|
},
|
|
"fonts": {"heading": "Georgia, serif", "body": "Arial, sans-serif"},
|
|
"layout": {"style": "list", "header": "static", "product_card": "classic"},
|
|
},
|
|
"minimal": {
|
|
"colors": {
|
|
"primary": "#000000", # Black - Ultra minimal
|
|
"secondary": "#404040", # Dark gray
|
|
"accent": "#666666", # Medium gray
|
|
"background": "#ffffff", # White
|
|
"text": "#000000", # Black
|
|
"border": "#e5e7eb", # Light gray
|
|
},
|
|
"fonts": {"heading": "Helvetica, sans-serif", "body": "Helvetica, sans-serif"},
|
|
"layout": {"style": "grid", "header": "transparent", "product_card": "minimal"},
|
|
},
|
|
"vibrant": {
|
|
"colors": {
|
|
"primary": "#f59e0b", # Orange - Bold & energetic
|
|
"secondary": "#ef4444", # Red
|
|
"accent": "#8b5cf6", # Purple
|
|
"background": "#ffffff", # White
|
|
"text": "#1f2937", # Gray-800
|
|
"border": "#fbbf24", # Yellow
|
|
},
|
|
"fonts": {"heading": "Poppins, sans-serif", "body": "Open Sans, sans-serif"},
|
|
"layout": {"style": "masonry", "header": "fixed", "product_card": "modern"},
|
|
},
|
|
"elegant": {
|
|
"colors": {
|
|
"primary": "#6b7280", # Gray - Sophisticated
|
|
"secondary": "#374151", # Dark gray
|
|
"accent": "#d97706", # Amber
|
|
"background": "#ffffff", # White
|
|
"text": "#1f2937", # Gray-800
|
|
"border": "#e5e7eb", # Gray-200
|
|
},
|
|
"fonts": {"heading": "Playfair Display, serif", "body": "Lato, sans-serif"},
|
|
"layout": {"style": "grid", "header": "fixed", "product_card": "classic"},
|
|
},
|
|
"nature": {
|
|
"colors": {
|
|
"primary": "#059669", # Green - Natural & eco
|
|
"secondary": "#10b981", # Emerald
|
|
"accent": "#f59e0b", # Amber
|
|
"background": "#ffffff", # White
|
|
"text": "#1f2937", # Gray-800
|
|
"border": "#d1fae5", # Light green
|
|
},
|
|
"fonts": {"heading": "Montserrat, sans-serif", "body": "Open Sans, sans-serif"},
|
|
"layout": {"style": "grid", "header": "fixed", "product_card": "modern"},
|
|
},
|
|
}
|
|
|
|
|
|
def get_preset(preset_name: str) -> dict:
|
|
"""
|
|
Get a theme preset by name.
|
|
|
|
Args:
|
|
preset_name: Name of the preset (e.g., 'modern', 'classic')
|
|
|
|
Returns:
|
|
dict: Theme configuration
|
|
|
|
Raises:
|
|
ValueError: If preset name is unknown
|
|
"""
|
|
if preset_name not in THEME_PRESETS:
|
|
available = ", ".join(THEME_PRESETS.keys())
|
|
raise ValueError(f"Unknown preset: {preset_name}. Available: {available}")
|
|
|
|
return THEME_PRESETS[preset_name]
|
|
|
|
|
|
def apply_preset(theme: VendorTheme, preset_name: str) -> VendorTheme:
|
|
"""
|
|
Apply a preset to a vendor theme.
|
|
|
|
Args:
|
|
theme: VendorTheme instance to update
|
|
preset_name: Name of the preset to apply
|
|
|
|
Returns:
|
|
VendorTheme: Updated theme instance
|
|
|
|
Raises:
|
|
ValueError: If preset name is unknown
|
|
|
|
Example:
|
|
theme = VendorTheme(vendor_id=1)
|
|
apply_preset(theme, "modern")
|
|
db.add(theme)
|
|
db.commit()
|
|
"""
|
|
preset = get_preset(preset_name)
|
|
|
|
# Set theme name
|
|
theme.theme_name = preset_name
|
|
|
|
# Apply colors (all of them!)
|
|
theme.colors = preset["colors"]
|
|
|
|
# Apply fonts
|
|
theme.font_family_heading = preset["fonts"]["heading"]
|
|
theme.font_family_body = preset["fonts"]["body"]
|
|
|
|
# Apply layout settings
|
|
theme.layout_style = preset["layout"]["style"]
|
|
theme.header_style = preset["layout"]["header"]
|
|
theme.product_card_style = preset["layout"]["product_card"]
|
|
|
|
# Mark as active
|
|
theme.is_active = True
|
|
|
|
return theme
|
|
|
|
|
|
def get_available_presets() -> list[str]:
|
|
"""
|
|
Get list of available preset names.
|
|
|
|
Returns:
|
|
list: Available preset names
|
|
"""
|
|
return list(THEME_PRESETS.keys())
|
|
|
|
|
|
def get_preset_preview(preset_name: str) -> dict:
|
|
"""
|
|
Get preview information for a preset (for UI display).
|
|
|
|
Args:
|
|
preset_name: Name of the preset
|
|
|
|
Returns:
|
|
dict: Preview info with colors, fonts, description
|
|
"""
|
|
preset = get_preset(preset_name)
|
|
|
|
descriptions = {
|
|
"default": "Clean and professional - perfect for getting started",
|
|
"modern": "Contemporary tech-inspired design with vibrant colors",
|
|
"classic": "Traditional and trustworthy with serif typography",
|
|
"minimal": "Ultra-clean black and white aesthetic",
|
|
"vibrant": "Bold and energetic with bright accent colors",
|
|
"elegant": "Sophisticated gray tones with refined typography",
|
|
"nature": "Fresh and eco-friendly green color palette",
|
|
}
|
|
|
|
return {
|
|
"name": preset_name,
|
|
"description": descriptions.get(preset_name, ""),
|
|
"primary_color": preset["colors"]["primary"],
|
|
"secondary_color": preset["colors"]["secondary"],
|
|
"accent_color": preset["colors"]["accent"],
|
|
"heading_font": preset["fonts"]["heading"],
|
|
"body_font": preset["fonts"]["body"],
|
|
"layout_style": preset["layout"]["style"],
|
|
}
|
|
|
|
|
|
def create_custom_preset(
|
|
colors: dict, fonts: dict, layout: dict, name: str = "custom"
|
|
) -> dict:
|
|
"""
|
|
Create a custom preset from provided settings.
|
|
|
|
Args:
|
|
colors: Dict with primary, secondary, accent, background, text, border
|
|
fonts: Dict with heading and body fonts
|
|
layout: Dict with style, header, product_card
|
|
name: Name for the custom preset
|
|
|
|
Returns:
|
|
dict: Custom preset configuration
|
|
|
|
Example:
|
|
custom = create_custom_preset(
|
|
colors={"primary": "#ff0000", "secondary": "#00ff00", ...},
|
|
fonts={"heading": "Arial", "body": "Arial"},
|
|
layout={"style": "grid", "header": "fixed", "product_card": "modern"},
|
|
name="my_custom_theme"
|
|
)
|
|
"""
|
|
# Validate colors
|
|
required_colors = ["primary", "secondary", "accent", "background", "text", "border"]
|
|
for color_key in required_colors:
|
|
if color_key not in colors:
|
|
colors[color_key] = THEME_PRESETS["default"]["colors"][color_key]
|
|
|
|
# Validate fonts
|
|
if "heading" not in fonts:
|
|
fonts["heading"] = "Inter, sans-serif"
|
|
if "body" not in fonts:
|
|
fonts["body"] = "Inter, sans-serif"
|
|
|
|
# Validate layout
|
|
if "style" not in layout:
|
|
layout["style"] = "grid"
|
|
if "header" not in layout:
|
|
layout["header"] = "fixed"
|
|
if "product_card" not in layout:
|
|
layout["product_card"] = "modern"
|
|
|
|
return {"colors": colors, "fonts": fonts, "layout": layout}
|