feat(hosting): add industry template infrastructure (Workstream 3B)
5 industry templates as JSON presets, each with theme + multi-page content:
- generic: clean minimal (homepage, about, contact)
- restaurant: warm tones, Playfair Display (homepage, about, menu, contact)
- construction: amber/earth tones, Montserrat (homepage, services, projects, contact)
- auto-parts: red/bold, parts-focused (homepage, catalog, contact)
- professional-services: navy/blue, Merriweather (homepage, services, team, contact)
Each template has:
- meta.json (name, description, tags, languages)
- theme.json (colors, fonts, layout, header style)
- pages/*.json (section-based homepage + content pages with i18n)
- {{placeholder}} variables for prospect data injection
TemplateService loads from templates_library/ directory with caching.
GET /admin/hosting/sites/templates endpoint to list available templates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,13 +22,26 @@ from app.modules.hosting.schemas.hosted_site import (
|
|||||||
HostedSiteUpdate,
|
HostedSiteUpdate,
|
||||||
SendProposalRequest,
|
SendProposalRequest,
|
||||||
)
|
)
|
||||||
|
from app.modules.hosting.schemas.template import TemplateListResponse, TemplateResponse
|
||||||
from app.modules.hosting.services.hosted_site_service import hosted_site_service
|
from app.modules.hosting.services.hosted_site_service import hosted_site_service
|
||||||
|
from app.modules.hosting.services.template_service import template_service
|
||||||
from app.modules.tenancy.schemas.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
router = APIRouter(prefix="/sites")
|
router = APIRouter(prefix="/sites")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/templates", response_model=TemplateListResponse)
|
||||||
|
def list_templates(
|
||||||
|
current_admin: UserContext = Depends(get_current_admin_api),
|
||||||
|
):
|
||||||
|
"""List available industry templates for POC site generation."""
|
||||||
|
templates = template_service.list_templates()
|
||||||
|
return TemplateListResponse(
|
||||||
|
templates=[TemplateResponse(**t) for t in templates],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _to_response(site) -> HostedSiteResponse:
|
def _to_response(site) -> HostedSiteResponse:
|
||||||
"""Convert a hosted site model to response schema."""
|
"""Convert a hosted site model to response schema."""
|
||||||
return HostedSiteResponse(
|
return HostedSiteResponse(
|
||||||
|
|||||||
21
app/modules/hosting/schemas/template.py
Normal file
21
app/modules/hosting/schemas/template.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# app/modules/hosting/schemas/template.py
|
||||||
|
"""Pydantic schemas for template responses."""
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateResponse(BaseModel):
|
||||||
|
"""Schema for a single template."""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
tags: list[str] = []
|
||||||
|
languages: list[str] = []
|
||||||
|
pages: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateListResponse(BaseModel):
|
||||||
|
"""Schema for template list response."""
|
||||||
|
|
||||||
|
templates: list[TemplateResponse]
|
||||||
114
app/modules/hosting/services/template_service.py
Normal file
114
app/modules/hosting/services/template_service.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# app/modules/hosting/services/template_service.py
|
||||||
|
"""
|
||||||
|
Template service for the hosting module.
|
||||||
|
|
||||||
|
Loads and manages industry templates from the templates_library directory.
|
||||||
|
Templates are JSON files that define page content, themes, and sections
|
||||||
|
for different business types (restaurant, construction, etc.).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TEMPLATES_DIR = Path(__file__).parent.parent / "templates_library"
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateService:
|
||||||
|
"""Manages industry templates for POC site generation."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._manifest = None
|
||||||
|
self._cache: dict[str, dict] = {}
|
||||||
|
|
||||||
|
def _load_manifest(self) -> dict:
|
||||||
|
"""Load the manifest.json file."""
|
||||||
|
if self._manifest is None:
|
||||||
|
manifest_path = TEMPLATES_DIR / "manifest.json"
|
||||||
|
self._manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
return self._manifest
|
||||||
|
|
||||||
|
def list_templates(self) -> list[dict]:
|
||||||
|
"""List all available templates with metadata."""
|
||||||
|
manifest = self._load_manifest()
|
||||||
|
templates = []
|
||||||
|
for entry in manifest.get("templates", []):
|
||||||
|
template_id = entry["id"]
|
||||||
|
meta = self._load_meta(template_id)
|
||||||
|
templates.append({
|
||||||
|
"id": template_id,
|
||||||
|
"name": meta.get("name", entry.get("name", template_id)),
|
||||||
|
"description": meta.get("description", entry.get("description", "")),
|
||||||
|
"tags": meta.get("tags", entry.get("tags", [])),
|
||||||
|
"languages": meta.get("languages", []),
|
||||||
|
"pages": entry.get("pages", []),
|
||||||
|
})
|
||||||
|
return templates
|
||||||
|
|
||||||
|
def get_template(self, template_id: str) -> dict | None:
|
||||||
|
"""Load a complete template with meta, theme, and all pages."""
|
||||||
|
if template_id in self._cache:
|
||||||
|
return self._cache[template_id]
|
||||||
|
|
||||||
|
template_dir = TEMPLATES_DIR / template_id
|
||||||
|
if not template_dir.is_dir():
|
||||||
|
return None
|
||||||
|
|
||||||
|
meta = self._load_meta(template_id)
|
||||||
|
theme = self._load_json(template_dir / "theme.json")
|
||||||
|
pages = self._load_pages(template_dir)
|
||||||
|
|
||||||
|
template = {
|
||||||
|
"id": template_id,
|
||||||
|
"meta": meta,
|
||||||
|
"theme": theme,
|
||||||
|
"pages": pages,
|
||||||
|
}
|
||||||
|
self._cache[template_id] = template
|
||||||
|
return template
|
||||||
|
|
||||||
|
def get_theme(self, template_id: str) -> dict | None:
|
||||||
|
"""Load just the theme configuration for a template."""
|
||||||
|
template_dir = TEMPLATES_DIR / template_id
|
||||||
|
return self._load_json(template_dir / "theme.json")
|
||||||
|
|
||||||
|
def get_page(self, template_id: str, page_slug: str) -> dict | None:
|
||||||
|
"""Load a single page definition from a template."""
|
||||||
|
page_path = TEMPLATES_DIR / template_id / "pages" / f"{page_slug}.json"
|
||||||
|
return self._load_json(page_path)
|
||||||
|
|
||||||
|
def template_exists(self, template_id: str) -> bool:
|
||||||
|
"""Check if a template exists."""
|
||||||
|
return (TEMPLATES_DIR / template_id / "meta.json").is_file()
|
||||||
|
|
||||||
|
def _load_meta(self, template_id: str) -> dict:
|
||||||
|
"""Load meta.json for a template."""
|
||||||
|
return self._load_json(TEMPLATES_DIR / template_id / "meta.json") or {}
|
||||||
|
|
||||||
|
def _load_pages(self, template_dir: Path) -> list[dict]:
|
||||||
|
"""Load all page JSONs from a template's pages/ directory."""
|
||||||
|
pages_dir = template_dir / "pages"
|
||||||
|
if not pages_dir.is_dir():
|
||||||
|
return []
|
||||||
|
pages = []
|
||||||
|
for page_file in sorted(pages_dir.glob("*.json")):
|
||||||
|
page_data = self._load_json(page_file)
|
||||||
|
if page_data:
|
||||||
|
pages.append(page_data)
|
||||||
|
return pages
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_json(path: Path) -> dict | None:
|
||||||
|
"""Safely load a JSON file."""
|
||||||
|
if not path.is_file():
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
except (json.JSONDecodeError, OSError) as e:
|
||||||
|
logger.warning("Failed to load template file %s: %s", path, e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
template_service = TemplateService()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"id": "auto-parts", "name": "Auto Parts & Garage", "description": "Template for auto parts shops, garages, and car dealers", "tags": ["automotive", "garage", "car", "parts"], "languages": ["en", "fr", "de"]}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"slug": "catalog",
|
||||||
|
"title": "Catalog",
|
||||||
|
"title_translations": {"en": "Parts Catalog", "fr": "Catalogue de pièces", "de": "Teilekatalog"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Parts Catalog</h2>\n<p>Browse our extensive catalog of auto parts for all major brands.</p>",
|
||||||
|
"fr": "<h2>Catalogue de pièces</h2>\n<p>Parcourez notre catalogue complet de pièces auto pour toutes les grandes marques.</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"slug": "contact",
|
||||||
|
"title": "Contact",
|
||||||
|
"title_translations": {"en": "Contact Us", "fr": "Contactez-nous", "de": "Kontakt"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"show_in_footer": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Contact Us</h2>\n<p>Visit our store or get in touch for parts inquiries.</p>\n<ul>\n<li>Phone: {{phone}}</li>\n<li>Email: {{email}}</li>\n<li>Address: {{address}}</li>\n</ul>",
|
||||||
|
"fr": "<h2>Contactez-nous</h2>\n<p>Visitez notre magasin ou contactez-nous pour vos demandes de pièces.</p>\n<ul>\n<li>Téléphone : {{phone}}</li>\n<li>Email : {{email}}</li>\n<li>Adresse : {{address}}</li>\n</ul>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"slug": "homepage",
|
||||||
|
"title": "{{business_name}}",
|
||||||
|
"template": "full",
|
||||||
|
"is_published": true,
|
||||||
|
"sections": {
|
||||||
|
"hero": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "{{business_name}}", "fr": "{{business_name}}"}},
|
||||||
|
"subtitle": {"translations": {"en": "Your trusted auto parts specialist in {{city}}", "fr": "Votre spécialiste pièces auto de confiance à {{city}}"}},
|
||||||
|
"background_type": "image",
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Browse Parts", "fr": "Voir les pièces"}}, "url": "/catalog", "style": "primary"},
|
||||||
|
{"label": {"translations": {"en": "Contact Us", "fr": "Contactez-nous"}}, "url": "/contact", "style": "secondary"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "Why Choose Us", "fr": "Pourquoi nous choisir"}},
|
||||||
|
"items": [
|
||||||
|
{"icon": "truck", "title": {"translations": {"en": "Fast Delivery", "fr": "Livraison rapide"}}, "description": {"translations": {"en": "Same-day delivery on in-stock parts", "fr": "Livraison le jour même pour les pièces en stock"}}},
|
||||||
|
{"icon": "shield-check", "title": {"translations": {"en": "Quality Guaranteed", "fr": "Qualité garantie"}}, "description": {"translations": {"en": "OEM and certified aftermarket parts", "fr": "Pièces OEM et aftermarket certifiées"}}},
|
||||||
|
{"icon": "currency-euro", "title": {"translations": {"en": "Best Prices", "fr": "Meilleurs prix"}}, "description": {"translations": {"en": "Competitive pricing on all brands", "fr": "Prix compétitifs sur toutes les marques"}}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "Need a specific part?", "fr": "Besoin d'une pièce spécifique ?"}},
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Contact Us", "fr": "Contactez-nous"}}, "url": "/contact", "style": "primary"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"theme_name": "modern",
|
||||||
|
"colors": {"primary": "#dc2626", "secondary": "#991b1b", "accent": "#f59e0b", "background": "#fafafa", "text": "#18181b", "border": "#e4e4e7"},
|
||||||
|
"font_family_heading": "Montserrat",
|
||||||
|
"font_family_body": "Inter",
|
||||||
|
"layout_style": "grid",
|
||||||
|
"header_style": "fixed"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"id": "construction", "name": "Construction & Renovation", "description": "Professional template for builders, renovators, and tradespeople", "tags": ["construction", "renovation", "building", "trades"], "languages": ["en", "fr", "de"]}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"slug": "contact",
|
||||||
|
"title": "Contact",
|
||||||
|
"title_translations": {"en": "Contact Us", "fr": "Contactez-nous", "de": "Kontakt"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"show_in_footer": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Get a Free Quote</h2>\n<p>Tell us about your project and we'll get back to you within 24 hours.</p>\n<ul>\n<li>Phone: {{phone}}</li>\n<li>Email: {{email}}</li>\n<li>Address: {{address}}</li>\n</ul>",
|
||||||
|
"fr": "<h2>Demandez un devis gratuit</h2>\n<p>Décrivez-nous votre projet et nous vous recontacterons sous 24h.</p>\n<ul>\n<li>Téléphone : {{phone}}</li>\n<li>Email : {{email}}</li>\n<li>Adresse : {{address}}</li>\n</ul>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"slug": "homepage",
|
||||||
|
"title": "{{business_name}}",
|
||||||
|
"template": "full",
|
||||||
|
"is_published": true,
|
||||||
|
"sections": {
|
||||||
|
"hero": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "{{business_name}}", "fr": "{{business_name}}"}},
|
||||||
|
"subtitle": {"translations": {"en": "Quality construction and renovation in {{city}}", "fr": "Construction et rénovation de qualité à {{city}}"}},
|
||||||
|
"background_type": "image",
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Get a Free Quote", "fr": "Devis gratuit"}}, "url": "/contact", "style": "primary"},
|
||||||
|
{"label": {"translations": {"en": "Our Projects", "fr": "Nos réalisations"}}, "url": "/projects", "style": "secondary"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "Our Services", "fr": "Nos Services"}},
|
||||||
|
"items": [
|
||||||
|
{"icon": "home", "title": {"translations": {"en": "New Construction", "fr": "Construction neuve"}}, "description": {"translations": {"en": "Custom-built homes and commercial buildings", "fr": "Maisons et bâtiments commerciaux sur mesure"}}},
|
||||||
|
{"icon": "wrench", "title": {"translations": {"en": "Renovation", "fr": "Rénovation"}}, "description": {"translations": {"en": "Complete interior and exterior renovation", "fr": "Rénovation complète intérieure et extérieure"}}},
|
||||||
|
{"icon": "color-swatch", "title": {"translations": {"en": "Painting & Finishing", "fr": "Peinture & Finitions"}}, "description": {"translations": {"en": "Professional painting and finishing work", "fr": "Travaux de peinture et finitions professionnels"}}},
|
||||||
|
{"icon": "shield-check", "title": {"translations": {"en": "Insulation", "fr": "Isolation"}}, "description": {"translations": {"en": "Energy-efficient insulation solutions", "fr": "Solutions d'isolation éco-énergétiques"}}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "What Our Clients Say", "fr": "Témoignages de nos clients"}},
|
||||||
|
"items": []
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "Ready to start your project?", "fr": "Prêt à démarrer votre projet ?"}},
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Request a Quote", "fr": "Demander un devis"}}, "url": "/contact", "style": "primary"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"slug": "projects",
|
||||||
|
"title": "Projects",
|
||||||
|
"title_translations": {"en": "Our Projects", "fr": "Nos Réalisations", "de": "Unsere Projekte"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Our Projects</h2>\n<p>Browse our portfolio of completed construction and renovation projects.</p>",
|
||||||
|
"fr": "<h2>Nos Réalisations</h2>\n<p>Découvrez notre portfolio de projets de construction et rénovation réalisés.</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"slug": "services",
|
||||||
|
"title": "Services",
|
||||||
|
"title_translations": {"en": "Our Services", "fr": "Nos Services", "de": "Unsere Leistungen"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Our Services</h2>\n<p>We offer a comprehensive range of construction and renovation services.</p>\n<h3>Construction</h3>\n<p>From foundations to finishing touches, we handle every aspect of new builds.</p>\n<h3>Renovation</h3>\n<p>Transform your existing space with our expert renovation team.</p>\n<h3>Painting & Decoration</h3>\n<p>Professional interior and exterior painting services.</p>",
|
||||||
|
"fr": "<h2>Nos Services</h2>\n<p>Nous proposons une gamme complète de services de construction et rénovation.</p>\n<h3>Construction</h3>\n<p>Des fondations aux finitions, nous gérons chaque aspect des constructions neuves.</p>\n<h3>Rénovation</h3>\n<p>Transformez votre espace avec notre équipe de rénovation experte.</p>\n<h3>Peinture & Décoration</h3>\n<p>Services professionnels de peinture intérieure et extérieure.</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"theme_name": "modern",
|
||||||
|
"colors": {
|
||||||
|
"primary": "#d97706",
|
||||||
|
"secondary": "#92400e",
|
||||||
|
"accent": "#fbbf24",
|
||||||
|
"background": "#fafaf9",
|
||||||
|
"text": "#1c1917",
|
||||||
|
"border": "#d6d3d1"
|
||||||
|
},
|
||||||
|
"font_family_heading": "Montserrat",
|
||||||
|
"font_family_body": "Open Sans",
|
||||||
|
"layout_style": "grid",
|
||||||
|
"header_style": "fixed"
|
||||||
|
}
|
||||||
7
app/modules/hosting/templates_library/generic/meta.json
Normal file
7
app/modules/hosting/templates_library/generic/meta.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"id": "generic",
|
||||||
|
"name": "Generic Business",
|
||||||
|
"description": "Clean, minimal template that works for any business type",
|
||||||
|
"tags": ["general", "minimal", "any"],
|
||||||
|
"languages": ["en", "fr", "de"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"slug": "about",
|
||||||
|
"title": "About Us",
|
||||||
|
"title_translations": {"en": "About Us", "fr": "À propos", "de": "Über uns"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"content": "{{about_content}}",
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>About {{business_name}}</h2>\n<p>{{about_paragraph}}</p>",
|
||||||
|
"fr": "<h2>À propos de {{business_name}}</h2>\n<p>{{about_paragraph}}</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"slug": "contact",
|
||||||
|
"title": "Contact",
|
||||||
|
"title_translations": {"en": "Contact Us", "fr": "Contact", "de": "Kontakt"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"show_in_footer": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Get in Touch</h2>\n<p>We'd love to hear from you. Reach out using the information below.</p>\n<ul>\n<li>Email: {{email}}</li>\n<li>Phone: {{phone}}</li>\n<li>Address: {{address}}</li>\n</ul>",
|
||||||
|
"fr": "<h2>Contactez-nous</h2>\n<p>N'hésitez pas à nous contacter.</p>\n<ul>\n<li>Email : {{email}}</li>\n<li>Téléphone : {{phone}}</li>\n<li>Adresse : {{address}}</li>\n</ul>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"slug": "homepage",
|
||||||
|
"title": "{{business_name}}",
|
||||||
|
"template": "full",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": false,
|
||||||
|
"sections": {
|
||||||
|
"hero": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "{{business_name}}", "fr": "{{business_name}}"}},
|
||||||
|
"subtitle": {"translations": {"en": "{{meta_description}}", "fr": "{{meta_description}}"}},
|
||||||
|
"background_type": "gradient",
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Contact Us", "fr": "Contactez-nous"}}, "url": "/contact", "style": "primary"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "What We Offer", "fr": "Nos Services"}},
|
||||||
|
"items": [
|
||||||
|
{"icon": "shield-check", "title": {"translations": {"en": "Quality", "fr": "Qualité"}}, "description": {"translations": {"en": "Committed to excellence in everything we do", "fr": "Engagés pour l'excellence dans tout ce que nous faisons"}}},
|
||||||
|
{"icon": "clock", "title": {"translations": {"en": "Reliability", "fr": "Fiabilité"}}, "description": {"translations": {"en": "Dependable service you can count on", "fr": "Un service fiable sur lequel vous pouvez compter"}}},
|
||||||
|
{"icon": "users", "title": {"translations": {"en": "Experience", "fr": "Expérience"}}, "description": {"translations": {"en": "Years of expertise at your service", "fr": "Des années d'expertise à votre service"}}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "Ready to get started?", "fr": "Prêt à commencer ?"}},
|
||||||
|
"subtitle": {"translations": {"en": "Contact us today for a free consultation", "fr": "Contactez-nous pour une consultation gratuite"}},
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Get in Touch", "fr": "Nous Contacter"}}, "url": "/contact", "style": "primary"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/modules/hosting/templates_library/generic/theme.json
Normal file
15
app/modules/hosting/templates_library/generic/theme.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"theme_name": "modern",
|
||||||
|
"colors": {
|
||||||
|
"primary": "#3b82f6",
|
||||||
|
"secondary": "#1e40af",
|
||||||
|
"accent": "#f59e0b",
|
||||||
|
"background": "#ffffff",
|
||||||
|
"text": "#1e293b",
|
||||||
|
"border": "#e2e8f0"
|
||||||
|
},
|
||||||
|
"font_family_heading": "Inter",
|
||||||
|
"font_family_body": "Inter",
|
||||||
|
"layout_style": "grid",
|
||||||
|
"header_style": "fixed"
|
||||||
|
}
|
||||||
40
app/modules/hosting/templates_library/manifest.json
Normal file
40
app/modules/hosting/templates_library/manifest.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"templates": [
|
||||||
|
{
|
||||||
|
"id": "generic",
|
||||||
|
"name": "Generic Business",
|
||||||
|
"description": "Clean, minimal template that works for any business type",
|
||||||
|
"tags": ["general", "minimal", "any"],
|
||||||
|
"pages": ["homepage", "about", "contact"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "restaurant",
|
||||||
|
"name": "Restaurant & Dining",
|
||||||
|
"description": "Elegant template for restaurants, cafés, bars, and catering",
|
||||||
|
"tags": ["food", "dining", "hospitality", "café"],
|
||||||
|
"pages": ["homepage", "about", "menu", "contact"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "construction",
|
||||||
|
"name": "Construction & Renovation",
|
||||||
|
"description": "Professional template for builders, renovators, and tradespeople",
|
||||||
|
"tags": ["construction", "renovation", "building", "trades"],
|
||||||
|
"pages": ["homepage", "services", "projects", "contact"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "auto-parts",
|
||||||
|
"name": "Auto Parts & Garage",
|
||||||
|
"description": "Template for auto parts shops, garages, and car dealers",
|
||||||
|
"tags": ["automotive", "garage", "car", "parts"],
|
||||||
|
"pages": ["homepage", "catalog", "contact"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "professional-services",
|
||||||
|
"name": "Professional Services",
|
||||||
|
"description": "Template for lawyers, accountants, consultants, and agencies",
|
||||||
|
"tags": ["professional", "consulting", "legal", "finance"],
|
||||||
|
"pages": ["homepage", "services", "team", "contact"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"id": "professional-services", "name": "Professional Services", "description": "Template for lawyers, accountants, consultants, and agencies", "tags": ["professional", "consulting", "legal", "finance"], "languages": ["en", "fr", "de"]}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"slug": "contact",
|
||||||
|
"title": "Contact",
|
||||||
|
"title_translations": {"en": "Contact Us", "fr": "Contactez-nous", "de": "Kontakt"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"show_in_footer": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Contact Us</h2>\n<p>Schedule a consultation or reach out with any questions.</p>\n<ul>\n<li>Phone: {{phone}}</li>\n<li>Email: {{email}}</li>\n<li>Address: {{address}}</li>\n</ul>",
|
||||||
|
"fr": "<h2>Contactez-nous</h2>\n<p>Planifiez une consultation ou posez-nous vos questions.</p>\n<ul>\n<li>Téléphone : {{phone}}</li>\n<li>Email : {{email}}</li>\n<li>Adresse : {{address}}</li>\n</ul>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"slug": "homepage",
|
||||||
|
"title": "{{business_name}}",
|
||||||
|
"template": "full",
|
||||||
|
"is_published": true,
|
||||||
|
"sections": {
|
||||||
|
"hero": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "{{business_name}}", "fr": "{{business_name}}"}},
|
||||||
|
"subtitle": {"translations": {"en": "Professional expertise you can trust", "fr": "Une expertise professionnelle de confiance"}},
|
||||||
|
"background_type": "gradient",
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Book a Consultation", "fr": "Prendre rendez-vous"}}, "url": "/contact", "style": "primary"},
|
||||||
|
{"label": {"translations": {"en": "Our Expertise", "fr": "Notre expertise"}}, "url": "/services", "style": "secondary"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "Areas of Expertise", "fr": "Domaines d'expertise"}},
|
||||||
|
"items": [
|
||||||
|
{"icon": "briefcase", "title": {"translations": {"en": "Advisory", "fr": "Conseil"}}, "description": {"translations": {"en": "Strategic guidance tailored to your needs", "fr": "Conseils stratégiques adaptés à vos besoins"}}},
|
||||||
|
{"icon": "document-text", "title": {"translations": {"en": "Compliance", "fr": "Conformité"}}, "description": {"translations": {"en": "Ensure regulatory compliance across your operations", "fr": "Assurez la conformité réglementaire de vos opérations"}}},
|
||||||
|
{"icon": "chart-bar", "title": {"translations": {"en": "Analysis", "fr": "Analyse"}}, "description": {"translations": {"en": "Data-driven insights for informed decisions", "fr": "Analyses basées sur les données pour des décisions éclairées"}}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "Need professional guidance?", "fr": "Besoin d'un accompagnement professionnel ?"}},
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Schedule a Meeting", "fr": "Planifier un rendez-vous"}}, "url": "/contact", "style": "primary"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"slug": "services",
|
||||||
|
"title": "Services",
|
||||||
|
"title_translations": {"en": "Our Services", "fr": "Nos Services", "de": "Unsere Leistungen"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Our Services</h2>\n<p>We provide comprehensive professional services to help your business thrive.</p>",
|
||||||
|
"fr": "<h2>Nos Services</h2>\n<p>Nous proposons des services professionnels complets pour aider votre entreprise à prospérer.</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"slug": "team",
|
||||||
|
"title": "Team",
|
||||||
|
"title_translations": {"en": "Our Team", "fr": "Notre Équipe", "de": "Unser Team"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Our Team</h2>\n<p>Meet the professionals behind {{business_name}}.</p>",
|
||||||
|
"fr": "<h2>Notre Équipe</h2>\n<p>Découvrez les professionnels derrière {{business_name}}.</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"theme_name": "modern",
|
||||||
|
"colors": {"primary": "#1e40af", "secondary": "#1e3a8a", "accent": "#3b82f6", "background": "#f8fafc", "text": "#0f172a", "border": "#cbd5e1"},
|
||||||
|
"font_family_heading": "Merriweather",
|
||||||
|
"font_family_body": "Source Sans Pro",
|
||||||
|
"layout_style": "grid",
|
||||||
|
"header_style": "fixed"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"id": "restaurant", "name": "Restaurant & Dining", "description": "Elegant template for restaurants, cafés, bars, and catering", "tags": ["food", "dining", "hospitality"], "languages": ["en", "fr", "de"]}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"slug": "about",
|
||||||
|
"title": "About",
|
||||||
|
"title_translations": {"en": "Our Story", "fr": "Notre Histoire", "de": "Über uns"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Our Story</h2>\n<p>{{about_paragraph}}</p>",
|
||||||
|
"fr": "<h2>Notre Histoire</h2>\n<p>{{about_paragraph}}</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"slug": "contact",
|
||||||
|
"title": "Contact",
|
||||||
|
"title_translations": {"en": "Visit Us", "fr": "Nous Rendre Visite", "de": "Besuchen Sie uns"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"show_in_footer": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Visit Us</h2>\n<p>We look forward to welcoming you.</p>\n<ul>\n<li>Address: {{address}}</li>\n<li>Phone: {{phone}}</li>\n<li>Email: {{email}}</li>\n</ul>",
|
||||||
|
"fr": "<h2>Nous Rendre Visite</h2>\n<p>Nous avons hâte de vous accueillir.</p>\n<ul>\n<li>Adresse : {{address}}</li>\n<li>Téléphone : {{phone}}</li>\n<li>Email : {{email}}</li>\n</ul>"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"slug": "homepage",
|
||||||
|
"title": "{{business_name}}",
|
||||||
|
"template": "full",
|
||||||
|
"is_published": true,
|
||||||
|
"sections": {
|
||||||
|
"hero": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "{{business_name}}", "fr": "{{business_name}}"}},
|
||||||
|
"subtitle": {"translations": {"en": "A culinary experience in {{city}}", "fr": "Une expérience culinaire à {{city}}"}},
|
||||||
|
"background_type": "image",
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Reserve a Table", "fr": "Réserver une table"}}, "url": "/contact", "style": "primary"},
|
||||||
|
{"label": {"translations": {"en": "See Our Menu", "fr": "Voir la carte"}}, "url": "/menu", "style": "secondary"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "Our Specialties", "fr": "Nos Spécialités"}},
|
||||||
|
"items": [
|
||||||
|
{"icon": "fire", "title": {"translations": {"en": "Fresh Ingredients", "fr": "Produits frais"}}, "description": {"translations": {"en": "Locally sourced, seasonal ingredients", "fr": "Produits locaux et de saison"}}},
|
||||||
|
{"icon": "star", "title": {"translations": {"en": "Chef's Selection", "fr": "Sélection du chef"}}, "description": {"translations": {"en": "Carefully crafted dishes by our expert chef", "fr": "Plats élaborés par notre chef expert"}}},
|
||||||
|
{"icon": "heart", "title": {"translations": {"en": "Warm Atmosphere", "fr": "Ambiance chaleureuse"}}, "description": {"translations": {"en": "A welcoming space for every occasion", "fr": "Un espace accueillant pour chaque occasion"}}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"testimonials": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "What Our Guests Say", "fr": "Ce que disent nos clients"}},
|
||||||
|
"items": []
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"enabled": true,
|
||||||
|
"title": {"translations": {"en": "Ready to dine with us?", "fr": "Prêt à nous rendre visite ?"}},
|
||||||
|
"buttons": [
|
||||||
|
{"label": {"translations": {"en": "Make a Reservation", "fr": "Réserver"}}, "url": "/contact", "style": "primary"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"slug": "menu",
|
||||||
|
"title": "Menu",
|
||||||
|
"title_translations": {"en": "Our Menu", "fr": "Notre Carte", "de": "Speisekarte"},
|
||||||
|
"template": "default",
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"content_translations": {
|
||||||
|
"en": "<h2>Our Menu</h2>\n<p>Discover our selection of dishes, prepared with fresh, local ingredients.</p>",
|
||||||
|
"fr": "<h2>Notre Carte</h2>\n<p>Découvrez notre sélection de plats, préparés avec des produits frais et locaux.</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/modules/hosting/templates_library/restaurant/theme.json
Normal file
15
app/modules/hosting/templates_library/restaurant/theme.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"theme_name": "modern",
|
||||||
|
"colors": {
|
||||||
|
"primary": "#b45309",
|
||||||
|
"secondary": "#78350f",
|
||||||
|
"accent": "#f59e0b",
|
||||||
|
"background": "#fffbeb",
|
||||||
|
"text": "#1c1917",
|
||||||
|
"border": "#e7e5e4"
|
||||||
|
},
|
||||||
|
"font_family_heading": "Playfair Display",
|
||||||
|
"font_family_body": "Inter",
|
||||||
|
"layout_style": "grid",
|
||||||
|
"header_style": "transparent"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user