feat: add platform homepage and content management system with improved UI
Implemented a comprehensive CMS for managing platform homepage and content pages: - Platform homepage manager with template selection (default, minimal, modern) - Content pages CRUD with platform defaults and vendor overrides - Sidebar navigation for Content Management section - Dedicated API endpoints for creating, updating, deleting pages - Template support for customizable homepage layouts - Header/footer navigation integration for content pages - Comprehensive documentation for platform homepage setup - Migration script for creating initial platform pages UI improvements: - Fixed action buttons styling in content pages table to match design system - Added proper hover states, rounded corners, and better contrast - Increased button size and padding for better usability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ class ContentPageCreate(BaseModel):
|
|||||||
title: str = Field(..., max_length=200, description="Page title")
|
title: str = Field(..., max_length=200, description="Page title")
|
||||||
content: str = Field(..., description="HTML or Markdown content")
|
content: str = Field(..., description="HTML or Markdown content")
|
||||||
content_format: str = Field(default="html", description="Content format: html or markdown")
|
content_format: str = Field(default="html", description="Content format: html or markdown")
|
||||||
|
template: str = Field(default="default", max_length=50, description="Template name (default, minimal, modern)")
|
||||||
meta_description: Optional[str] = Field(None, max_length=300, description="SEO meta description")
|
meta_description: Optional[str] = Field(None, max_length=300, description="SEO meta description")
|
||||||
meta_keywords: Optional[str] = Field(None, max_length=300, description="SEO keywords")
|
meta_keywords: Optional[str] = Field(None, max_length=300, description="SEO keywords")
|
||||||
is_published: bool = Field(default=False, description="Publish immediately")
|
is_published: bool = Field(default=False, description="Publish immediately")
|
||||||
@@ -46,6 +47,7 @@ class ContentPageUpdate(BaseModel):
|
|||||||
title: Optional[str] = Field(None, max_length=200)
|
title: Optional[str] = Field(None, max_length=200)
|
||||||
content: Optional[str] = None
|
content: Optional[str] = None
|
||||||
content_format: Optional[str] = None
|
content_format: Optional[str] = None
|
||||||
|
template: Optional[str] = Field(None, max_length=50)
|
||||||
meta_description: Optional[str] = Field(None, max_length=300)
|
meta_description: Optional[str] = Field(None, max_length=300)
|
||||||
meta_keywords: Optional[str] = Field(None, max_length=300)
|
meta_keywords: Optional[str] = Field(None, max_length=300)
|
||||||
is_published: Optional[bool] = None
|
is_published: Optional[bool] = None
|
||||||
@@ -120,6 +122,7 @@ def create_platform_page(
|
|||||||
content=page_data.content,
|
content=page_data.content,
|
||||||
vendor_id=None, # Platform default
|
vendor_id=None, # Platform default
|
||||||
content_format=page_data.content_format,
|
content_format=page_data.content_format,
|
||||||
|
template=page_data.template,
|
||||||
meta_description=page_data.meta_description,
|
meta_description=page_data.meta_description,
|
||||||
meta_keywords=page_data.meta_keywords,
|
meta_keywords=page_data.meta_keywords,
|
||||||
is_published=page_data.is_published,
|
is_published=page_data.is_published,
|
||||||
@@ -202,6 +205,7 @@ def update_page(
|
|||||||
title=page_data.title,
|
title=page_data.title,
|
||||||
content=page_data.content,
|
content=page_data.content,
|
||||||
content_format=page_data.content_format,
|
content_format=page_data.content_format,
|
||||||
|
template=page_data.template,
|
||||||
meta_description=page_data.meta_description,
|
meta_description=page_data.meta_description,
|
||||||
meta_keywords=page_data.meta_keywords,
|
meta_keywords=page_data.meta_keywords,
|
||||||
is_published=page_data.is_published,
|
is_published=page_data.is_published,
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ Routes:
|
|||||||
- GET /users → User management page (auth required)
|
- GET /users → User management page (auth required)
|
||||||
- GET /imports → Import history page (auth required)
|
- GET /imports → Import history page (auth required)
|
||||||
- GET /settings → Settings page (auth required)
|
- GET /settings → Settings page (auth required)
|
||||||
|
- GET /platform-homepage → Platform homepage manager (auth required)
|
||||||
|
- GET /content-pages → Content pages list (auth required)
|
||||||
|
- GET /content-pages/create → Create content page (auth required)
|
||||||
|
- GET /content-pages/{page_id}/edit → Edit content page (auth required)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, Depends, Path
|
from fastapi import APIRouter, Request, Depends, Path
|
||||||
@@ -306,6 +310,89 @@ async def admin_settings_page(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CONTENT MANAGEMENT SYSTEM (CMS) ROUTES
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
@router.get("/platform-homepage", response_class=HTMLResponse, include_in_schema=False)
|
||||||
|
async def admin_platform_homepage_manager(
|
||||||
|
request: Request,
|
||||||
|
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Render platform homepage manager.
|
||||||
|
Allows editing the main platform homepage with template selection.
|
||||||
|
"""
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"admin/platform-homepage.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"user": current_user,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/content-pages", response_class=HTMLResponse, include_in_schema=False)
|
||||||
|
async def admin_content_pages_list(
|
||||||
|
request: Request,
|
||||||
|
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Render content pages list.
|
||||||
|
Shows all platform defaults and vendor overrides with filtering.
|
||||||
|
"""
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"admin/content-pages.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"user": current_user,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/content-pages/create", response_class=HTMLResponse, include_in_schema=False)
|
||||||
|
async def admin_content_page_create(
|
||||||
|
request: Request,
|
||||||
|
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Render create content page form.
|
||||||
|
Allows creating new platform defaults or vendor-specific pages.
|
||||||
|
"""
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"admin/content-page-edit.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"user": current_user,
|
||||||
|
"page_id": None, # Indicates this is a create operation
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/content-pages/{page_id}/edit", response_class=HTMLResponse, include_in_schema=False)
|
||||||
|
async def admin_content_page_edit(
|
||||||
|
request: Request,
|
||||||
|
page_id: int = Path(..., description="Content page ID"),
|
||||||
|
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Render edit content page form.
|
||||||
|
Allows editing existing platform or vendor content pages.
|
||||||
|
"""
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"admin/content-page-edit.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"user": current_user,
|
||||||
|
"page_id": page_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# DEVELOPER TOOLS - COMPONENTS & TESTING
|
# DEVELOPER TOOLS - COMPONENTS & TESTING
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ class ContentPageService:
|
|||||||
content: str,
|
content: str,
|
||||||
vendor_id: Optional[int] = None,
|
vendor_id: Optional[int] = None,
|
||||||
content_format: str = "html",
|
content_format: str = "html",
|
||||||
|
template: str = "default",
|
||||||
meta_description: Optional[str] = None,
|
meta_description: Optional[str] = None,
|
||||||
meta_keywords: Optional[str] = None,
|
meta_keywords: Optional[str] = None,
|
||||||
is_published: bool = False,
|
is_published: bool = False,
|
||||||
@@ -194,6 +195,7 @@ class ContentPageService:
|
|||||||
content: HTML or Markdown content
|
content: HTML or Markdown content
|
||||||
vendor_id: Vendor ID (None for platform default)
|
vendor_id: Vendor ID (None for platform default)
|
||||||
content_format: "html" or "markdown"
|
content_format: "html" or "markdown"
|
||||||
|
template: Template name for homepage/landing pages (default, minimal, modern, etc.)
|
||||||
meta_description: SEO description
|
meta_description: SEO description
|
||||||
meta_keywords: SEO keywords
|
meta_keywords: SEO keywords
|
||||||
is_published: Publish immediately
|
is_published: Publish immediately
|
||||||
@@ -211,6 +213,7 @@ class ContentPageService:
|
|||||||
title=title,
|
title=title,
|
||||||
content=content,
|
content=content,
|
||||||
content_format=content_format,
|
content_format=content_format,
|
||||||
|
template=template,
|
||||||
meta_description=meta_description,
|
meta_description=meta_description,
|
||||||
meta_keywords=meta_keywords,
|
meta_keywords=meta_keywords,
|
||||||
is_published=is_published,
|
is_published=is_published,
|
||||||
@@ -236,6 +239,7 @@ class ContentPageService:
|
|||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
content: Optional[str] = None,
|
content: Optional[str] = None,
|
||||||
content_format: Optional[str] = None,
|
content_format: Optional[str] = None,
|
||||||
|
template: Optional[str] = None,
|
||||||
meta_description: Optional[str] = None,
|
meta_description: Optional[str] = None,
|
||||||
meta_keywords: Optional[str] = None,
|
meta_keywords: Optional[str] = None,
|
||||||
is_published: Optional[bool] = None,
|
is_published: Optional[bool] = None,
|
||||||
@@ -253,6 +257,7 @@ class ContentPageService:
|
|||||||
title: New title
|
title: New title
|
||||||
content: New content
|
content: New content
|
||||||
content_format: New format
|
content_format: New format
|
||||||
|
template: New template name
|
||||||
meta_description: New SEO description
|
meta_description: New SEO description
|
||||||
meta_keywords: New SEO keywords
|
meta_keywords: New SEO keywords
|
||||||
is_published: New publish status
|
is_published: New publish status
|
||||||
@@ -277,6 +282,8 @@ class ContentPageService:
|
|||||||
page.content = content
|
page.content = content
|
||||||
if content_format is not None:
|
if content_format is not None:
|
||||||
page.content_format = content_format
|
page.content_format = content_format
|
||||||
|
if template is not None:
|
||||||
|
page.template = template
|
||||||
if meta_description is not None:
|
if meta_description is not None:
|
||||||
page.meta_description = meta_description
|
page.meta_description = meta_description
|
||||||
if meta_keywords is not None:
|
if meta_keywords is not None:
|
||||||
|
|||||||
301
app/templates/admin/content-page-edit.html
Normal file
301
app/templates/admin/content-page-edit.html
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
{# app/templates/admin/content-page-edit.html #}
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{% if page_id %}Edit{% else %}Create{% endif %} Content Page{% endblock %}
|
||||||
|
|
||||||
|
{% block alpine_data %}contentPageEditor({{ page_id if page_id else 'null' }}){% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="flex items-center justify-between my-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
|
||||||
|
<span x-text="pageId ? 'Edit Content Page' : 'Create Content Page'"></span>
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
<span x-show="!pageId">Create a new platform default or vendor-specific page</span>
|
||||||
|
<span x-show="pageId">Modify an existing content page</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<a
|
||||||
|
href="/admin/content-pages"
|
||||||
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-200 transition-colors duration-150 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:border-gray-400 dark:hover:border-gray-500 focus:outline-none focus:shadow-outline-purple"
|
||||||
|
>
|
||||||
|
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||||
|
Back to List
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
@click="savePage()"
|
||||||
|
:disabled="saving"
|
||||||
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<span x-show="!saving" x-html="$icon('check', 'w-4 h-4 mr-2')"></span>
|
||||||
|
<span x-show="saving" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
||||||
|
<span x-text="saving ? 'Saving...' : (pageId ? 'Update Page' : 'Create Page')"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div x-show="loading" class="text-center py-12">
|
||||||
|
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
|
||||||
|
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading page...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error State -->
|
||||||
|
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
|
||||||
|
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold">Error</p>
|
||||||
|
<p class="text-sm" x-text="error"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Success Message -->
|
||||||
|
<div x-show="successMessage" x-transition class="mb-6 p-4 bg-green-100 border border-green-400 text-green-700 rounded-lg flex items-start">
|
||||||
|
<span x-html="$icon('check-circle', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold" x-text="successMessage"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Form -->
|
||||||
|
<div x-show="!loading" class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
|
||||||
|
<form @submit.prevent="savePage()">
|
||||||
|
<!-- Basic Information -->
|
||||||
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
Basic Information
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<!-- Page Title -->
|
||||||
|
<div class="md:col-span-2">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Page Title <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="form.title"
|
||||||
|
required
|
||||||
|
maxlength="200"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
placeholder="About Us"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slug -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Slug <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="form.slug"
|
||||||
|
required
|
||||||
|
maxlength="100"
|
||||||
|
pattern="[a-z0-9_-]+"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
placeholder="about"
|
||||||
|
>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
URL-safe identifier (lowercase, numbers, hyphens, underscores only)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vendor ID (Platform vs Vendor-specific) -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Page Type
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
x-model="form.vendor_id"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
>
|
||||||
|
<option :value="null">Platform Default</option>
|
||||||
|
<!-- TODO: Load vendors dynamically if needed -->
|
||||||
|
</select>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Platform defaults are shown to all vendors
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
Page Content
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Content Format -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Content Format
|
||||||
|
</label>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<label class="flex items-center cursor-pointer">
|
||||||
|
<input type="radio" x-model="form.content_format" value="html" class="mr-2">
|
||||||
|
<span class="text-sm">HTML</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center cursor-pointer">
|
||||||
|
<input type="radio" x-model="form.content_format" value="markdown" class="mr-2">
|
||||||
|
<span class="text-sm">Markdown</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content Editor -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Content <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
x-model="form.content"
|
||||||
|
required
|
||||||
|
rows="12"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700 font-mono text-sm"
|
||||||
|
placeholder="<h2>Your content here...</h2>"
|
||||||
|
></textarea>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
<span x-show="form.content_format === 'html'">Enter HTML content. Basic HTML tags are supported.</span>
|
||||||
|
<span x-show="form.content_format === 'markdown'">Enter Markdown content. Will be converted to HTML.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SEO Settings -->
|
||||||
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
SEO & Metadata
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Meta Description -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Meta Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
x-model="form.meta_description"
|
||||||
|
rows="2"
|
||||||
|
maxlength="300"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
placeholder="A brief description for search engines"
|
||||||
|
></textarea>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
<span x-text="(form.meta_description || '').length"></span>/300 characters (150-160 recommended)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Meta Keywords -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Meta Keywords
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="form.meta_keywords"
|
||||||
|
maxlength="300"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
placeholder="keyword1, keyword2, keyword3"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation & Display Settings -->
|
||||||
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
Navigation & Display
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<!-- Display Order -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Display Order
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
x-model.number="form.display_order"
|
||||||
|
min="0"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Lower = first</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Show in Header -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
<label class="flex items-center cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
x-model="form.show_in_header"
|
||||||
|
class="w-5 h-5 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
|
||||||
|
>
|
||||||
|
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">
|
||||||
|
Show in Header
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Show in Footer -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
<label class="flex items-center cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
x-model="form.show_in_footer"
|
||||||
|
class="w-5 h-5 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
|
||||||
|
>
|
||||||
|
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">
|
||||||
|
Show in Footer
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Publishing Settings -->
|
||||||
|
<div class="p-6 bg-gray-50 dark:bg-gray-700/50">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<label class="flex items-center cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
x-model="form.is_published"
|
||||||
|
class="w-5 h-5 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
|
||||||
|
>
|
||||||
|
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">
|
||||||
|
Published
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<p class="ml-8 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Make this page visible to the public
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<a
|
||||||
|
href="/admin/content-pages"
|
||||||
|
class="px-6 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-200 transition-colors duration-150 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:border-gray-400 dark:hover:border-gray-500"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="saving"
|
||||||
|
class="px-6 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<span x-text="saving ? 'Saving...' : (pageId ? 'Update Page' : 'Create Page')"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_scripts %}
|
||||||
|
<script src="{{ url_for('static', path='admin/js/content-page-edit.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
200
app/templates/admin/content-pages.html
Normal file
200
app/templates/admin/content-pages.html
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
{# app/templates/admin/content-pages.html #}
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Content Pages{% endblock %}
|
||||||
|
|
||||||
|
{% block alpine_data %}contentPagesManager(){% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="flex items-center justify-between my-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
|
||||||
|
Content Pages
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
Manage platform defaults and vendor-specific content pages
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="/admin/content-pages/create"
|
||||||
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"
|
||||||
|
>
|
||||||
|
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
|
||||||
|
Create Page
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div x-show="loading" class="text-center py-12">
|
||||||
|
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
|
||||||
|
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading pages...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error State -->
|
||||||
|
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
|
||||||
|
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold">Error loading pages</p>
|
||||||
|
<p class="text-sm" x-text="error"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs and Filters -->
|
||||||
|
<div x-show="!loading" class="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-md p-4">
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="flex space-x-2 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<button
|
||||||
|
@click="activeTab = 'all'"
|
||||||
|
class="px-4 py-2 text-sm font-medium transition-colors"
|
||||||
|
:class="activeTab === 'all' ? 'text-purple-600 border-b-2 border-purple-600' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'"
|
||||||
|
>
|
||||||
|
All Pages
|
||||||
|
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-gray-700" x-text="allPages.length"></span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="activeTab = 'platform'"
|
||||||
|
class="px-4 py-2 text-sm font-medium transition-colors"
|
||||||
|
:class="activeTab === 'platform' ? 'text-purple-600 border-b-2 border-purple-600' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'"
|
||||||
|
>
|
||||||
|
Platform Defaults
|
||||||
|
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-gray-700" x-text="platformPages.length"></span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="activeTab = 'vendor'"
|
||||||
|
class="px-4 py-2 text-sm font-medium transition-colors"
|
||||||
|
:class="activeTab === 'vendor' ? 'text-purple-600 border-b-2 border-purple-600' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'"
|
||||||
|
>
|
||||||
|
Vendor Overrides
|
||||||
|
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-gray-700" x-text="vendorPages.length"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search -->
|
||||||
|
<div class="relative">
|
||||||
|
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
|
||||||
|
<span x-html="$icon('search', 'w-5 h-5 text-gray-400')"></span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="searchQuery"
|
||||||
|
placeholder="Search pages..."
|
||||||
|
class="pl-10 pr-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pages Table -->
|
||||||
|
<div x-show="!loading && filteredPages.length > 0" class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full whitespace-nowrap">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50 dark:text-gray-400">
|
||||||
|
<th class="px-4 py-3">Page</th>
|
||||||
|
<th class="px-4 py-3">Slug</th>
|
||||||
|
<th class="px-4 py-3">Type</th>
|
||||||
|
<th class="px-4 py-3">Status</th>
|
||||||
|
<th class="px-4 py-3">Navigation</th>
|
||||||
|
<th class="px-4 py-3">Updated</th>
|
||||||
|
<th class="px-4 py-3">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white dark:bg-gray-800 divide-y dark:divide-gray-700">
|
||||||
|
<template x-for="page in filteredPages" :key="page.id">
|
||||||
|
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
||||||
|
<!-- Page Title -->
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold text-gray-900 dark:text-white" x-text="page.title"></p>
|
||||||
|
<p class="text-xs text-gray-600 dark:text-gray-400" x-show="page.vendor_name">
|
||||||
|
Vendor: <span x-text="page.vendor_name"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Slug -->
|
||||||
|
<td class="px-4 py-3 text-sm">
|
||||||
|
<code class="px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 rounded" x-text="'/' + page.slug"></code>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Type -->
|
||||||
|
<td class="px-4 py-3 text-sm">
|
||||||
|
<span
|
||||||
|
class="px-2 py-1 text-xs font-semibold rounded-full"
|
||||||
|
:class="page.is_platform_default ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'"
|
||||||
|
x-text="page.is_platform_default ? 'Platform' : 'Vendor'"
|
||||||
|
></span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<td class="px-4 py-3 text-sm">
|
||||||
|
<span
|
||||||
|
class="px-2 py-1 text-xs font-semibold rounded-full"
|
||||||
|
:class="page.is_published ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'"
|
||||||
|
x-text="page.is_published ? 'Published' : 'Draft'"
|
||||||
|
></span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<td class="px-4 py-3 text-xs">
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<span x-show="page.show_in_header" class="px-1.5 py-0.5 bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200 rounded">Header</span>
|
||||||
|
<span x-show="page.show_in_footer" class="px-1.5 py-0.5 bg-teal-100 text-teal-800 dark:bg-teal-900 dark:text-teal-200 rounded">Footer</span>
|
||||||
|
<span x-show="!page.show_in_header && !page.show_in_footer" class="text-gray-400">—</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Updated -->
|
||||||
|
<td class="px-4 py-3 text-xs" x-text="formatDate(page.updated_at)"></td>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<div class="flex items-center space-x-2 text-sm">
|
||||||
|
<a
|
||||||
|
:href="`/admin/content-pages/${page.id}/edit`"
|
||||||
|
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
||||||
|
title="Edit"
|
||||||
|
>
|
||||||
|
<span x-html="$icon('pencil', 'w-5 h-5')"></span>
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
@click="deletePage(page)"
|
||||||
|
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
||||||
|
title="Delete"
|
||||||
|
>
|
||||||
|
<span x-html="$icon('trash', 'w-5 h-5')"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div x-show="!loading && filteredPages.length === 0" class="text-center py-12 bg-white dark:bg-gray-800 rounded-lg shadow-md">
|
||||||
|
<span x-html="$icon('document-text', 'inline w-16 h-16 text-gray-400 mb-4')"></span>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-2">No pages found</h3>
|
||||||
|
<p class="text-gray-500 dark:text-gray-400 mb-4" x-show="searchQuery">
|
||||||
|
No pages match your search: "<span x-text="searchQuery"></span>"
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-500 dark:text-gray-400 mb-4" x-show="!searchQuery && activeTab === 'vendor'">
|
||||||
|
No vendor-specific pages have been created yet.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="/admin/content-pages/create"
|
||||||
|
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700"
|
||||||
|
>
|
||||||
|
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
|
||||||
|
Create First Page
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_scripts %}
|
||||||
|
<script src="{{ url_for('static', path='admin/js/content-pages.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
@@ -54,6 +54,37 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- Content Management Section -->
|
||||||
|
<div class="px-6 my-6">
|
||||||
|
<hr class="border-gray-200 dark:border-gray-700" />
|
||||||
|
</div>
|
||||||
|
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Content Management
|
||||||
|
</p>
|
||||||
|
<ul class="mt-3">
|
||||||
|
<!-- Platform Homepage -->
|
||||||
|
<li class="relative px-6 py-3">
|
||||||
|
<span x-show="currentPage === 'platform-homepage'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||||
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||||
|
:class="currentPage === 'platform-homepage' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||||
|
href="/admin/platform-homepage">
|
||||||
|
<span x-html="$icon('home')"></span>
|
||||||
|
<span class="ml-4">Platform Homepage</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Content Pages -->
|
||||||
|
<li class="relative px-6 py-3">
|
||||||
|
<span x-show="currentPage === 'content-pages'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||||
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||||
|
:class="currentPage === 'content-pages' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||||
|
href="/admin/content-pages">
|
||||||
|
<span x-html="$icon('document-text')"></span>
|
||||||
|
<span class="ml-4">Content Pages</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<!-- Developer Tools Section -->
|
<!-- Developer Tools Section -->
|
||||||
<div class="px-6 my-6">
|
<div class="px-6 my-6">
|
||||||
<hr class="border-gray-200 dark:border-gray-700" />
|
<hr class="border-gray-200 dark:border-gray-700" />
|
||||||
@@ -188,6 +219,37 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- Content Management Section -->
|
||||||
|
<div class="px-6 my-6">
|
||||||
|
<hr class="border-gray-200 dark:border-gray-700" />
|
||||||
|
</div>
|
||||||
|
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Content Management
|
||||||
|
</p>
|
||||||
|
<ul class="mt-3">
|
||||||
|
<!-- Platform Homepage -->
|
||||||
|
<li class="relative px-6 py-3">
|
||||||
|
<span x-show="currentPage === 'platform-homepage'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||||
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||||
|
:class="currentPage === 'platform-homepage' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||||
|
href="/admin/platform-homepage">
|
||||||
|
<span x-html="$icon('home')"></span>
|
||||||
|
<span class="ml-4">Platform Homepage</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Content Pages -->
|
||||||
|
<li class="relative px-6 py-3">
|
||||||
|
<span x-show="currentPage === 'content-pages'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||||
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||||
|
:class="currentPage === 'content-pages' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||||
|
href="/admin/content-pages">
|
||||||
|
<span x-html="$icon('document-text')"></span>
|
||||||
|
<span class="ml-4">Content Pages</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<!-- Developer Tools Section -->
|
<!-- Developer Tools Section -->
|
||||||
<div class="px-6 my-6">
|
<div class="px-6 my-6">
|
||||||
<hr class="border-gray-200 dark:border-gray-700" />
|
<hr class="border-gray-200 dark:border-gray-700" />
|
||||||
|
|||||||
248
app/templates/admin/platform-homepage.html
Normal file
248
app/templates/admin/platform-homepage.html
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
{# app/templates/admin/platform-homepage.html #}
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Platform Homepage Manager{% endblock %}
|
||||||
|
|
||||||
|
{% block alpine_data %}platformHomepageManager(){% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="flex items-center justify-between my-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
|
||||||
|
Platform Homepage
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
Manage your platform's main landing page at <a href="/" target="_blank" class="text-purple-600 hover:underline">localhost:8000</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
target="_blank"
|
||||||
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-200 transition-colors duration-150 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:border-gray-400 dark:hover:border-gray-500 focus:outline-none focus:shadow-outline-purple"
|
||||||
|
>
|
||||||
|
<span x-html="$icon('eye', 'w-4 h-4 mr-2')"></span>
|
||||||
|
Preview
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
@click="savePage()"
|
||||||
|
:disabled="saving"
|
||||||
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<span x-show="!saving" x-html="$icon('check', 'w-4 h-4 mr-2')"></span>
|
||||||
|
<span x-show="saving" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
||||||
|
<span x-text="saving ? 'Saving...' : 'Save Changes'"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div x-show="loading" class="text-center py-12">
|
||||||
|
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
|
||||||
|
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading homepage...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error State -->
|
||||||
|
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
|
||||||
|
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold">Error loading homepage</p>
|
||||||
|
<p class="text-sm" x-text="error"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Success Message -->
|
||||||
|
<div x-show="successMessage" x-transition class="mb-6 p-4 bg-green-100 border border-green-400 text-green-700 rounded-lg flex items-start">
|
||||||
|
<span x-html="$icon('check-circle', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold" x-text="successMessage"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Form -->
|
||||||
|
<div x-show="!loading && page" class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
|
||||||
|
<form @submit.prevent="savePage()">
|
||||||
|
<!-- Template Selection -->
|
||||||
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
Template Selection
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
Choose the visual style for your homepage. Each template offers a different layout and design.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<!-- Default Template -->
|
||||||
|
<label class="cursor-pointer">
|
||||||
|
<input type="radio" name="template" value="default" x-model="page.template" class="sr-only">
|
||||||
|
<div
|
||||||
|
class="border-2 rounded-lg p-4 transition-all"
|
||||||
|
:class="page.template === 'default' ? 'border-purple-600 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-300 dark:border-gray-600 hover:border-gray-400'"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<span class="font-semibold text-gray-900 dark:text-white">Default</span>
|
||||||
|
<span x-show="page.template === 'default'" x-html="$icon('check-circle', 'w-5 h-5 text-purple-600')"></span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
Full-featured with hero section, features grid, vendor cards, and call-to-action.
|
||||||
|
</p>
|
||||||
|
<div class="mt-3 h-24 bg-gradient-to-r from-purple-400 to-pink-400 rounded"></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Minimal Template -->
|
||||||
|
<label class="cursor-pointer">
|
||||||
|
<input type="radio" name="template" value="minimal" x-model="page.template" class="sr-only">
|
||||||
|
<div
|
||||||
|
class="border-2 rounded-lg p-4 transition-all"
|
||||||
|
:class="page.template === 'minimal' ? 'border-purple-600 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-300 dark:border-gray-600 hover:border-gray-400'"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<span class="font-semibold text-gray-900 dark:text-white">Minimal</span>
|
||||||
|
<span x-show="page.template === 'minimal'" x-html="$icon('check-circle', 'w-5 h-5 text-purple-600')"></span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
Clean and simple design with emoji icons and essential information only.
|
||||||
|
</p>
|
||||||
|
<div class="mt-3 h-24 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded"></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Modern Template -->
|
||||||
|
<label class="cursor-pointer">
|
||||||
|
<input type="radio" name="template" value="modern" x-model="page.template" class="sr-only">
|
||||||
|
<div
|
||||||
|
class="border-2 rounded-lg p-4 transition-all"
|
||||||
|
:class="page.template === 'modern' ? 'border-purple-600 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-300 dark:border-gray-600 hover:border-gray-400'"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<span class="font-semibold text-gray-900 dark:text-white">Modern</span>
|
||||||
|
<span x-show="page.template === 'modern'" x-html="$icon('check-circle', 'w-5 h-5 text-purple-600')"></span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
Trendy design with animated gradients, stats, and modern UI elements.
|
||||||
|
</p>
|
||||||
|
<div class="mt-3 h-24 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 rounded"></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Page Content -->
|
||||||
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
Page Content
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Page Title
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="page.title"
|
||||||
|
required
|
||||||
|
maxlength="200"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
placeholder="Welcome to Our Multi-Vendor Marketplace"
|
||||||
|
>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Main heading displayed on the homepage
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content (HTML) -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Content (HTML)
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
x-model="page.content"
|
||||||
|
rows="6"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700 font-mono text-sm"
|
||||||
|
placeholder="<p>Your platform description here...</p>"
|
||||||
|
></textarea>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
HTML content shown below the title. Basic HTML tags are supported.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SEO Settings -->
|
||||||
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
SEO Settings
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Meta Description -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Meta Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
x-model="page.meta_description"
|
||||||
|
rows="2"
|
||||||
|
maxlength="300"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
placeholder="A brief description for search engines (150-160 characters recommended)"
|
||||||
|
></textarea>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
<span x-text="(page.meta_description || '').length"></span>/300 characters
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Meta Keywords -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Meta Keywords
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="page.meta_keywords"
|
||||||
|
maxlength="300"
|
||||||
|
class="w-full px-3 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-purple-500 dark:bg-gray-700"
|
||||||
|
placeholder="marketplace, multi-vendor, e-commerce"
|
||||||
|
>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Comma-separated keywords (optional)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Publishing Settings -->
|
||||||
|
<div class="p-6 bg-gray-50 dark:bg-gray-700/50">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<label class="flex items-center cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
x-model="page.is_published"
|
||||||
|
class="w-5 h-5 text-purple-600 border-gray-300 rounded focus:ring-purple-500"
|
||||||
|
>
|
||||||
|
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">
|
||||||
|
Published
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<p class="ml-8 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Make this homepage visible to the public
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="saving"
|
||||||
|
class="px-6 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<span x-text="saving ? 'Saving...' : 'Save Changes'"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_scripts %}
|
||||||
|
<script src="{{ url_for('static', path='admin/js/platform-homepage.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
299
app/templates/platform/base.html
Normal file
299
app/templates/platform/base.html
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
{# app/templates/platform/base.html #}
|
||||||
|
{# Base template for platform public pages (homepage, about, faq, etc.) #}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" x-data="platformLayoutData()" x-bind:class="{ 'dark': dark }">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
{# Dynamic page title #}
|
||||||
|
<title>{% block title %}Multi-Vendor Marketplace Platform{% endblock %}</title>
|
||||||
|
|
||||||
|
{# SEO Meta Tags #}
|
||||||
|
<meta name="description" content="{% block meta_description %}Leading multi-vendor marketplace platform connecting vendors with customers worldwide.{% endblock %}">
|
||||||
|
<meta name="keywords" content="{% block meta_keywords %}marketplace, multi-vendor, e-commerce, online shopping{% endblock %}">
|
||||||
|
|
||||||
|
{# Favicon #}
|
||||||
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', path='favicon.ico') }}">
|
||||||
|
|
||||||
|
{# Platform color scheme #}
|
||||||
|
<style id="platform-theme-variables">
|
||||||
|
:root {
|
||||||
|
/* Platform Colors */
|
||||||
|
--color-primary: #6366f1; /* Indigo */
|
||||||
|
--color-secondary: #8b5cf6; /* Purple */
|
||||||
|
--color-accent: #ec4899; /* Pink */
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-heading: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode colors */
|
||||||
|
.dark {
|
||||||
|
--color-primary: #818cf8;
|
||||||
|
--color-secondary: #a78bfa;
|
||||||
|
--color-accent: #f472b6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{# Fonts: Local fallback + Google Fonts #}
|
||||||
|
<link href="/static/shared/fonts/inter.css" rel="stylesheet" />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
|
{# Tailwind CSS with local fallback #}
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
|
||||||
|
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/tailwind.min.css') }}';">
|
||||||
|
|
||||||
|
{# Platform-specific styles #}
|
||||||
|
<style>
|
||||||
|
/* Smooth scrolling */
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom gradients */
|
||||||
|
.gradient-primary {
|
||||||
|
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-accent {
|
||||||
|
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-accent) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button styles */
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card hover effect */
|
||||||
|
.card-hover {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% block extra_head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200">
|
||||||
|
|
||||||
|
{# Header / Navigation #}
|
||||||
|
<header class="sticky top-0 z-50 bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between items-center h-16">
|
||||||
|
|
||||||
|
{# Logo / Brand #}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<a href="/" class="flex items-center space-x-3">
|
||||||
|
<div class="w-8 h-8 rounded-lg gradient-primary flex items-center justify-center">
|
||||||
|
<span class="text-white font-bold text-xl">M</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-xl font-bold text-gray-900 dark:text-white">
|
||||||
|
Marketplace
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Desktop Navigation #}
|
||||||
|
<div class="hidden md:flex items-center space-x-8">
|
||||||
|
{# Dynamic header navigation from CMS #}
|
||||||
|
{% if header_pages %}
|
||||||
|
{% for page in header_pages %}
|
||||||
|
<a href="/{{ page.slug }}"
|
||||||
|
class="text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-primary font-medium transition-colors">
|
||||||
|
{{ page.title }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Dark mode toggle #}
|
||||||
|
<button
|
||||||
|
@click="toggleDarkMode()"
|
||||||
|
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||||
|
aria-label="Toggle dark mode"
|
||||||
|
>
|
||||||
|
<svg x-show="!dark" class="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
|
||||||
|
</svg>
|
||||||
|
<svg x-show="dark" class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Mobile menu button #}
|
||||||
|
<div class="md:hidden">
|
||||||
|
<button
|
||||||
|
@click="mobileMenuOpen = !mobileMenuOpen"
|
||||||
|
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
|
<svg x-show="!mobileMenuOpen" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||||
|
</svg>
|
||||||
|
<svg x-show="mobileMenuOpen" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Mobile menu #}
|
||||||
|
<div x-show="mobileMenuOpen" x-cloak class="md:hidden py-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
{% if header_pages %}
|
||||||
|
{% for page in header_pages %}
|
||||||
|
<a href="/{{ page.slug }}"
|
||||||
|
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg">
|
||||||
|
{{ page.title }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{# Main Content #}
|
||||||
|
<main class="min-h-screen">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{# Footer #}
|
||||||
|
<footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-16">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||||
|
|
||||||
|
{# Brand Column #}
|
||||||
|
<div class="col-span-1">
|
||||||
|
<div class="flex items-center space-x-3 mb-4">
|
||||||
|
<div class="w-8 h-8 rounded-lg gradient-primary flex items-center justify-center">
|
||||||
|
<span class="text-white font-bold text-xl">M</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-xl font-bold text-gray-900 dark:text-white">
|
||||||
|
Marketplace
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 text-sm">
|
||||||
|
Connecting vendors with customers worldwide. Build your online store today.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Quick Links #}
|
||||||
|
<div>
|
||||||
|
<h4 class="font-semibold text-gray-900 dark:text-white mb-4">Quick Links</h4>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
{% if footer_pages %}
|
||||||
|
{% for page in footer_pages %}
|
||||||
|
<li>
|
||||||
|
<a href="/{{ page.slug }}"
|
||||||
|
class="text-gray-600 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors">
|
||||||
|
{{ page.title }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Platform Links #}
|
||||||
|
<div>
|
||||||
|
<h4 class="font-semibold text-gray-900 dark:text-white mb-4">Platform</h4>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
<li>
|
||||||
|
<a href="/admin/login" class="text-gray-600 dark:text-gray-400 hover:text-primary transition-colors">
|
||||||
|
Admin Login
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/vendor/wizamart/login" class="text-gray-600 dark:text-gray-400 hover:text-primary transition-colors">
|
||||||
|
Vendor Login
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Contact Info #}
|
||||||
|
<div>
|
||||||
|
<h4 class="font-semibold text-gray-900 dark:text-white mb-4">Contact</h4>
|
||||||
|
<ul class="space-y-2 text-gray-600 dark:text-gray-400 text-sm">
|
||||||
|
<li>support@marketplace.com</li>
|
||||||
|
<li>+1 (555) 123-4567</li>
|
||||||
|
<li>123 Business St, Suite 100</li>
|
||||||
|
<li>San Francisco, CA 94102</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Bottom Bar #}
|
||||||
|
<div class="mt-12 pt-8 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center">
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 text-sm">
|
||||||
|
© 2024 Marketplace Platform. All rights reserved.
|
||||||
|
</p>
|
||||||
|
<div class="flex space-x-6 mt-4 md:mt-0">
|
||||||
|
<a href="/privacy" class="text-gray-600 dark:text-gray-400 hover:text-primary text-sm transition-colors">
|
||||||
|
Privacy Policy
|
||||||
|
</a>
|
||||||
|
<a href="/terms" class="text-gray-600 dark:text-gray-400 hover:text-primary text-sm transition-colors">
|
||||||
|
Terms of Service
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
{# Scripts #}
|
||||||
|
<!-- Alpine.js for interactivity -->
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Platform layout data -->
|
||||||
|
<script>
|
||||||
|
function platformLayoutData() {
|
||||||
|
return {
|
||||||
|
// Dark mode
|
||||||
|
dark: localStorage.getItem('darkMode') === 'true',
|
||||||
|
|
||||||
|
// Mobile menu
|
||||||
|
mobileMenuOpen: false,
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
init() {
|
||||||
|
// Apply dark mode on load
|
||||||
|
if (this.dark) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Toggle dark mode
|
||||||
|
toggleDarkMode() {
|
||||||
|
this.dark = !this.dark;
|
||||||
|
localStorage.setItem('darkMode', this.dark);
|
||||||
|
|
||||||
|
if (this.dark) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% block extra_scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
246
app/templates/platform/content-page.html
Normal file
246
app/templates/platform/content-page.html
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
{# app/templates/platform/content-page.html #}
|
||||||
|
{# Generic template for platform content pages (About, FAQ, Terms, Contact, etc.) #}
|
||||||
|
{% extends "platform/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ page.title }} - Marketplace{% endblock %}
|
||||||
|
|
||||||
|
{% block meta_description %}
|
||||||
|
{% if page.meta_description %}
|
||||||
|
{{ page.meta_description }}
|
||||||
|
{% else %}
|
||||||
|
{{ page.title }} - Multi-Vendor Marketplace Platform
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block meta_keywords %}
|
||||||
|
{% if page.meta_keywords %}
|
||||||
|
{{ page.meta_keywords }}
|
||||||
|
{% else %}
|
||||||
|
{{ page.title }}, marketplace, platform
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
|
|
||||||
|
{# Breadcrumbs #}
|
||||||
|
<nav class="flex mb-8 text-sm" aria-label="Breadcrumb">
|
||||||
|
<ol class="inline-flex items-center space-x-2">
|
||||||
|
<li class="inline-flex items-center">
|
||||||
|
<a href="/" class="text-gray-600 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors">
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path>
|
||||||
|
</svg>
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 text-gray-400 mx-2" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="text-gray-700 dark:text-gray-300 font-medium">{{ page.title }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{# Page Header #}
|
||||||
|
<div class="mb-12">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
{{ page.title }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{# Published date (if available) #}
|
||||||
|
{% if page.published_at %}
|
||||||
|
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Published {{ page.published_at.strftime('%B %d, %Y') }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Page Content #}
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-sm p-8 md:p-12">
|
||||||
|
<div class="prose prose-lg dark:prose-invert max-w-none">
|
||||||
|
{% if page.content_format == 'markdown' %}
|
||||||
|
{# Future enhancement: Render with markdown library #}
|
||||||
|
<div class="markdown-content">
|
||||||
|
{{ page.content | safe }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{# HTML content (default) #}
|
||||||
|
{{ page.content | safe }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Last updated timestamp #}
|
||||||
|
{% if page.updated_at %}
|
||||||
|
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 text-center">
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
Last updated: {{ page.updated_at.strftime('%B %d, %Y') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Call-to-action section (for specific pages) #}
|
||||||
|
{% if page.slug in ['about', 'contact'] %}
|
||||||
|
<div class="mt-12 bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded-2xl p-8 text-center">
|
||||||
|
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
{% if page.slug == 'about' %}
|
||||||
|
Ready to Get Started?
|
||||||
|
{% elif page.slug == 'contact' %}
|
||||||
|
Have Questions?
|
||||||
|
{% endif %}
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-6">
|
||||||
|
{% if page.slug == 'about' %}
|
||||||
|
Join thousands of vendors already selling on our platform
|
||||||
|
{% elif page.slug == 'contact' %}
|
||||||
|
Our team is here to help you succeed
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<a href="/contact" class="inline-block bg-gray-900 dark:bg-white text-white dark:text-gray-900 px-8 py-3 rounded-lg font-semibold hover:opacity-90 transition">
|
||||||
|
{% if page.slug == 'about' %}
|
||||||
|
Contact Sales
|
||||||
|
{% elif page.slug == 'contact' %}
|
||||||
|
Send Us a Message
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Additional styling for prose content #}
|
||||||
|
<style>
|
||||||
|
/* Enhanced prose styling for content pages */
|
||||||
|
.prose {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 2em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h2 {
|
||||||
|
font-size: 1.875rem;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
border-bottom: 2px solid var(--color-primary);
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose p {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
line-height: 1.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose ul, .prose ol {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
padding-left: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose li {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose a {
|
||||||
|
color: var(--color-primary);
|
||||||
|
text-decoration: underline;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose a:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose strong {
|
||||||
|
font-weight: 600;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose code {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose code {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose pre {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose pre {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose blockquote {
|
||||||
|
border-left: 4px solid var(--color-primary);
|
||||||
|
padding-left: 1em;
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose th, .prose td {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 0.75em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose th, .dark .prose td {
|
||||||
|
border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose th {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose th {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose hr {
|
||||||
|
border: 0;
|
||||||
|
border-top: 2px solid rgba(0, 0, 0, 0.1);
|
||||||
|
margin: 3em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose hr {
|
||||||
|
border-top-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose img {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin: 2em auto;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
228
app/templates/platform/homepage-default.html
Normal file
228
app/templates/platform/homepage-default.html
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
{# app/templates/platform/homepage-default.html #}
|
||||||
|
{# Default platform homepage template #}
|
||||||
|
{% extends "platform/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if page %}{{ page.title }}{% else %}Home{% endif %} - Multi-Vendor Marketplace
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block meta_description %}
|
||||||
|
{% if page and page.meta_description %}
|
||||||
|
{{ page.meta_description }}
|
||||||
|
{% else %}
|
||||||
|
Leading multi-vendor marketplace platform. Connect with thousands of vendors and discover millions of products.
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- HERO SECTION -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section class="gradient-primary text-white py-20">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="text-center">
|
||||||
|
{% if page %}
|
||||||
|
{# CMS-driven content #}
|
||||||
|
<h1 class="text-4xl md:text-6xl font-bold mb-6">
|
||||||
|
{{ page.title }}
|
||||||
|
</h1>
|
||||||
|
<div class="text-xl md:text-2xl mb-8 opacity-90 max-w-3xl mx-auto">
|
||||||
|
{{ page.content | safe }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{# Default fallback content #}
|
||||||
|
<h1 class="text-4xl md:text-6xl font-bold mb-6">
|
||||||
|
Welcome to Our Marketplace
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl md:text-2xl mb-8 opacity-90 max-w-3xl mx-auto">
|
||||||
|
Connect vendors with customers worldwide. Build your online store and reach millions of shoppers.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||||
|
<a href="/vendors"
|
||||||
|
class="btn-primary inline-flex items-center space-x-2">
|
||||||
|
<span>Browse Vendors</span>
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="/contact"
|
||||||
|
class="bg-white text-gray-900 px-6 py-3 rounded-lg font-semibold hover:bg-gray-100 transition">
|
||||||
|
Get Started
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- FEATURES SECTION -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section class="py-16 bg-white dark:bg-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Why Choose Our Platform?
|
||||||
|
</h2>
|
||||||
|
<p class="text-lg text-gray-600 dark:text-gray-400 max-w-2xl mx-auto">
|
||||||
|
Everything you need to launch and grow your online business
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<!-- Feature 1 -->
|
||||||
|
<div class="card-hover bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full gradient-primary flex items-center justify-center">
|
||||||
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||||
|
Lightning Fast
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
Optimized for speed and performance. Your store loads in milliseconds.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feature 2 -->
|
||||||
|
<div class="card-hover bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full gradient-primary flex items-center justify-center">
|
||||||
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||||
|
Secure & Reliable
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
Enterprise-grade security with 99.9% uptime guarantee.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feature 3 -->
|
||||||
|
<div class="card-hover bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full gradient-primary flex items-center justify-center">
|
||||||
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||||
|
Fully Customizable
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
Brand your store with custom themes, colors, and layouts.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- FEATURED VENDORS SECTION -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section class="py-16 bg-gray-50 dark:bg-gray-900">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Featured Vendors
|
||||||
|
</h2>
|
||||||
|
<p class="text-lg text-gray-600 dark:text-gray-400">
|
||||||
|
Discover amazing shops from around the world
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<!-- Vendor Card 1 - Placeholder -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
|
||||||
|
<div class="h-48 bg-gradient-to-r from-purple-400 to-pink-400"></div>
|
||||||
|
<div class="p-6">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
Sample Vendor 1
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
Premium products and exceptional service
|
||||||
|
</p>
|
||||||
|
<a href="/vendors/vendor1/shop"
|
||||||
|
class="text-primary hover:underline font-medium inline-flex items-center">
|
||||||
|
Visit Store
|
||||||
|
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vendor Card 2 - Placeholder -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
|
||||||
|
<div class="h-48 bg-gradient-to-r from-blue-400 to-cyan-400"></div>
|
||||||
|
<div class="p-6">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
Sample Vendor 2
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
Quality craftsmanship meets modern design
|
||||||
|
</p>
|
||||||
|
<a href="/vendors/vendor2/shop"
|
||||||
|
class="text-primary hover:underline font-medium inline-flex items-center">
|
||||||
|
Visit Store
|
||||||
|
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vendor Card 3 - Placeholder -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
|
||||||
|
<div class="h-48 bg-gradient-to-r from-green-400 to-teal-400"></div>
|
||||||
|
<div class="p-6">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
Sample Vendor 3
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
Eco-friendly products for sustainable living
|
||||||
|
</p>
|
||||||
|
<a href="/vendors/vendor3/shop"
|
||||||
|
class="text-primary hover:underline font-medium inline-flex items-center">
|
||||||
|
Visit Store
|
||||||
|
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-12">
|
||||||
|
<a href="/vendors" class="btn-primary inline-block">
|
||||||
|
View All Vendors
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- CTA SECTION -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section class="py-16 bg-white dark:bg-gray-800">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Ready to Get Started?
|
||||||
|
</h2>
|
||||||
|
<p class="text-lg text-gray-600 dark:text-gray-400 mb-8">
|
||||||
|
Join thousands of vendors already selling on our platform
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<a href="/contact" class="btn-primary">
|
||||||
|
Contact Sales
|
||||||
|
</a>
|
||||||
|
<a href="/about"
|
||||||
|
class="bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white px-6 py-3 rounded-lg font-semibold hover:bg-gray-200 dark:hover:bg-gray-600 transition">
|
||||||
|
Learn More
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
100
app/templates/platform/homepage-minimal.html
Normal file
100
app/templates/platform/homepage-minimal.html
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{# app/templates/platform/homepage-minimal.html #}
|
||||||
|
{# Minimal/clean platform homepage template #}
|
||||||
|
{% extends "platform/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if page %}{{ page.title }}{% else %}Home{% endif %} - Marketplace
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- MINIMAL HERO -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section class="py-32 bg-white dark:bg-gray-800">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
{% if page %}
|
||||||
|
<h1 class="text-5xl md:text-7xl font-bold text-gray-900 dark:text-white mb-8 leading-tight">
|
||||||
|
{{ page.title }}
|
||||||
|
</h1>
|
||||||
|
<div class="text-xl text-gray-600 dark:text-gray-400 mb-12 max-w-2xl mx-auto">
|
||||||
|
{{ page.content | safe }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<h1 class="text-5xl md:text-7xl font-bold text-gray-900 dark:text-white mb-8 leading-tight">
|
||||||
|
Multi-Vendor<br>Marketplace
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl text-gray-600 dark:text-gray-400 mb-12 max-w-2xl mx-auto">
|
||||||
|
The simplest way to launch your online store and connect with customers worldwide.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a href="/contact"
|
||||||
|
class="inline-block bg-gray-900 dark:bg-white text-white dark:text-gray-900 px-8 py-4 rounded-lg font-semibold hover:opacity-90 transition text-lg">
|
||||||
|
Get Started
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- MINIMAL FEATURES -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section class="py-24 bg-gray-50 dark:bg-gray-900">
|
||||||
|
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-4xl mb-4">⚡</div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
Fast
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 text-sm">
|
||||||
|
Lightning-fast performance optimized for conversions
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-4xl mb-4">🔒</div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
Secure
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 text-sm">
|
||||||
|
Enterprise-grade security for your peace of mind
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-4xl mb-4">🎨</div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
Custom
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 text-sm">
|
||||||
|
Fully customizable to match your brand identity
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- MINIMAL CTA -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section class="py-24 bg-white dark:bg-gray-800">
|
||||||
|
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-6">
|
||||||
|
Ready to launch?
|
||||||
|
</h2>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-8">
|
||||||
|
Join our marketplace today
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<a href="/contact"
|
||||||
|
class="inline-block bg-gray-900 dark:bg-white text-white dark:text-gray-900 px-6 py-3 rounded-lg font-semibold hover:opacity-90 transition">
|
||||||
|
Contact Us
|
||||||
|
</a>
|
||||||
|
<a href="/about"
|
||||||
|
class="inline-block border-2 border-gray-900 dark:border-white text-gray-900 dark:text-white px-6 py-3 rounded-lg font-semibold hover:bg-gray-50 dark:hover:bg-gray-700 transition">
|
||||||
|
Learn More
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
258
app/templates/platform/homepage-modern.html
Normal file
258
app/templates/platform/homepage-modern.html
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
{# app/templates/platform/homepage-modern.html #}
|
||||||
|
{# Modern/trendy platform homepage template with animations #}
|
||||||
|
{% extends "platform/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if page %}{{ page.title }}{% else %}Home{% endif %} - Marketplace
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_head %}
|
||||||
|
<style>
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0px); }
|
||||||
|
50% { transform: translateY(-20px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-animation {
|
||||||
|
animation: float 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradient {
|
||||||
|
0% { background-position: 0% 50%; }
|
||||||
|
50% { background-position: 100% 50%; }
|
||||||
|
100% { background-position: 0% 50%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animated-gradient {
|
||||||
|
background: linear-gradient(270deg, #6366f1, #8b5cf6, #ec4899, #f43f5e);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradient 15s ease infinite;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- MODERN HERO WITH GRADIENT -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section class="relative overflow-hidden animated-gradient text-white py-24 md:py-32">
|
||||||
|
{# Decorative elements #}
|
||||||
|
<div class="absolute top-0 right-0 w-1/3 h-full opacity-20">
|
||||||
|
<div class="absolute top-20 right-20 w-72 h-72 rounded-full bg-white blur-3xl"></div>
|
||||||
|
<div class="absolute bottom-20 right-40 w-96 h-96 rounded-full bg-white blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||||
|
{# Left column - Content #}
|
||||||
|
<div>
|
||||||
|
{% if page %}
|
||||||
|
<h1 class="text-5xl md:text-7xl font-black mb-6 leading-tight">
|
||||||
|
{{ page.title }}
|
||||||
|
</h1>
|
||||||
|
<div class="text-xl md:text-2xl mb-8 opacity-95">
|
||||||
|
{{ page.content | safe }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="inline-block px-4 py-2 bg-white/20 backdrop-blur-sm rounded-full text-sm font-semibold mb-6">
|
||||||
|
✨ The Future of E-Commerce
|
||||||
|
</div>
|
||||||
|
<h1 class="text-5xl md:text-7xl font-black mb-6 leading-tight">
|
||||||
|
Build Your<br>
|
||||||
|
<span class="text-transparent bg-clip-text bg-white">
|
||||||
|
Dream Store
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl md:text-2xl mb-8 opacity-95">
|
||||||
|
Launch a stunning online marketplace in minutes. No coding required. Scale effortlessly.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<a href="/contact"
|
||||||
|
class="inline-flex items-center justify-center bg-white text-gray-900 px-8 py-4 rounded-xl font-bold hover:shadow-2xl transform hover:scale-105 transition-all duration-200">
|
||||||
|
<span>Start Free Trial</span>
|
||||||
|
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="#features"
|
||||||
|
class="inline-flex items-center justify-center border-2 border-white text-white px-8 py-4 rounded-xl font-bold hover:bg-white/10 backdrop-blur-sm transition-all duration-200">
|
||||||
|
Learn More
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Stats #}
|
||||||
|
<div class="grid grid-cols-3 gap-6 mt-12 pt-12 border-t border-white/20">
|
||||||
|
<div>
|
||||||
|
<div class="text-3xl font-bold mb-1">10K+</div>
|
||||||
|
<div class="text-sm opacity-80">Active Vendors</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-3xl font-bold mb-1">50M+</div>
|
||||||
|
<div class="text-sm opacity-80">Products Sold</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-3xl font-bold mb-1">99.9%</div>
|
||||||
|
<div class="text-sm opacity-80">Uptime</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Right column - Visual element #}
|
||||||
|
<div class="hidden lg:block float-animation">
|
||||||
|
<div class="relative">
|
||||||
|
<div class="w-full h-96 bg-white/10 backdrop-blur-xl rounded-3xl shadow-2xl p-8">
|
||||||
|
<div class="w-full h-full bg-gradient-to-br from-white/20 to-transparent rounded-2xl flex items-center justify-center">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-8xl mb-4">🚀</div>
|
||||||
|
<div class="text-2xl font-bold">Launch Today</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- FEATURES WITH CARDS -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section id="features" class="py-24 bg-gray-50 dark:bg-gray-900">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="text-center mb-16">
|
||||||
|
<div class="inline-block px-4 py-2 bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 rounded-full text-sm font-semibold mb-4">
|
||||||
|
✨ Features
|
||||||
|
</div>
|
||||||
|
<h2 class="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Everything You Need
|
||||||
|
</h2>
|
||||||
|
<p class="text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto">
|
||||||
|
Powerful features to help you succeed in the digital marketplace
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
{# Feature cards with hover effects #}
|
||||||
|
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
|
||||||
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
|
||||||
|
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
|
||||||
|
Blazing Fast
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
Optimized for performance with sub-second page loads and instant search results.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
|
||||||
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-blue-500 to-cyan-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
|
||||||
|
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
|
||||||
|
Bank-Level Security
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
Enterprise-grade encryption and security measures to protect your business.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
|
||||||
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-green-500 to-teal-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
|
||||||
|
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
|
||||||
|
Fully Customizable
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
Brand your store with custom themes, colors, fonts, and layouts.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
|
||||||
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-orange-500 to-red-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
|
||||||
|
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
|
||||||
|
Analytics & Insights
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
Powerful analytics to track sales, customer behavior, and growth metrics.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
|
||||||
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
|
||||||
|
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
|
||||||
|
Mobile-First Design
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
Beautiful, responsive design that works perfectly on all devices.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2">
|
||||||
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-pink-500 to-rose-500 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
|
||||||
|
<svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
|
||||||
|
24/7 Support
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">
|
||||||
|
Round-the-clock customer support to help you succeed at every step.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<!-- MODERN CTA WITH GRADIENT -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||||
|
<section class="py-24 relative overflow-hidden">
|
||||||
|
<div class="absolute inset-0 gradient-accent opacity-90"></div>
|
||||||
|
|
||||||
|
<div class="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-white">
|
||||||
|
<h2 class="text-4xl md:text-5xl font-bold mb-6">
|
||||||
|
Start Your Journey Today
|
||||||
|
</h2>
|
||||||
|
<p class="text-xl mb-10 opacity-90">
|
||||||
|
Join thousands of successful vendors on our platform
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<a href="/contact"
|
||||||
|
class="inline-flex items-center justify-center bg-white text-gray-900 px-8 py-4 rounded-xl font-bold hover:shadow-2xl transform hover:scale-105 transition-all duration-200">
|
||||||
|
<span>Get Started Free</span>
|
||||||
|
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="/about"
|
||||||
|
class="inline-flex items-center justify-center border-2 border-white text-white px-8 py-4 rounded-xl font-bold hover:bg-white/10 backdrop-blur-sm transition-all duration-200">
|
||||||
|
Learn More About Us
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-8 text-sm opacity-75">
|
||||||
|
No credit card required · Free 14-day trial · Cancel anytime
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
710
docs/features/platform-homepage.md
Normal file
710
docs/features/platform-homepage.md
Normal file
@@ -0,0 +1,710 @@
|
|||||||
|
# Platform Homepage & Content Pages
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Platform Homepage feature provides a customizable, CMS-driven public homepage and content pages for your multi-vendor marketplace platform at the root domain (e.g., `localhost:8000` or `platform.com`).
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- ✅ CMS-driven customizable homepage with multiple templates
|
||||||
|
- ✅ Multiple content pages (About, FAQ, Contact, Terms, Privacy)
|
||||||
|
- ✅ Dynamic header and footer navigation
|
||||||
|
- ✅ Responsive design with dark mode support
|
||||||
|
- ✅ SEO-optimized with meta tags
|
||||||
|
- ✅ Template selection system (default, minimal, modern)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### URL Structure
|
||||||
|
|
||||||
|
| URL | Purpose | CMS Slug | Template |
|
||||||
|
|-----|---------|----------|----------|
|
||||||
|
| `/` | Platform Homepage | `platform_homepage` | Customizable (default/minimal/modern) |
|
||||||
|
| `/about` | About Us | `about` | `platform/content-page.html` |
|
||||||
|
| `/faq` | FAQ | `faq` | `platform/content-page.html` |
|
||||||
|
| `/contact` | Contact Us | `contact` | `platform/content-page.html` |
|
||||||
|
| `/terms` | Terms of Service | `terms` | `platform/content-page.html` |
|
||||||
|
| `/privacy` | Privacy Policy | `privacy` | `platform/content-page.html` |
|
||||||
|
|
||||||
|
### Database Model
|
||||||
|
|
||||||
|
All platform pages are stored in the `content_pages` table with `vendor_id = NULL`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Platform homepage
|
||||||
|
INSERT INTO content_pages (vendor_id, slug, title, template, ...)
|
||||||
|
VALUES (NULL, 'platform_homepage', 'Welcome', 'modern', ...);
|
||||||
|
|
||||||
|
-- Content pages
|
||||||
|
INSERT INTO content_pages (vendor_id, slug, title, ...)
|
||||||
|
VALUES (NULL, 'about', 'About Us', ...);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation System
|
||||||
|
|
||||||
|
Pages are configured to appear in header/footer navigation using flags:
|
||||||
|
|
||||||
|
| Field | Purpose | Example |
|
||||||
|
|-------|---------|---------|
|
||||||
|
| `show_in_header` | Display in top navigation | About, FAQ, Contact |
|
||||||
|
| `show_in_footer` | Display in footer links | All pages |
|
||||||
|
| `display_order` | Sort order in menus | 1, 2, 3, ... |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
├── templates/platform/
|
||||||
|
│ ├── base.html # Base layout with nav/footer
|
||||||
|
│ ├── homepage-default.html # Default homepage template
|
||||||
|
│ ├── homepage-minimal.html # Minimal/clean template
|
||||||
|
│ ├── homepage-modern.html # Modern template with animations
|
||||||
|
│ └── content-page.html # Generic content page template
|
||||||
|
├── services/
|
||||||
|
│ └── content_page_service.py # CMS business logic
|
||||||
|
└── main.py # Platform routes (lines 284-404)
|
||||||
|
|
||||||
|
scripts/
|
||||||
|
└── create_platform_pages.py # Seeder script
|
||||||
|
|
||||||
|
docs/features/
|
||||||
|
└── platform-homepage.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
### 1. Homepage Templates
|
||||||
|
|
||||||
|
Three homepage templates are available:
|
||||||
|
|
||||||
|
#### **Default Template** (`platform/homepage-default.html`)
|
||||||
|
- **Style:** Professional, feature-rich
|
||||||
|
- **Sections:** Hero, Features, Featured Vendors, CTA
|
||||||
|
- **Best for:** Comprehensive platform showcase
|
||||||
|
|
||||||
|
#### **Minimal Template** (`platform/homepage-minimal.html`)
|
||||||
|
- **Style:** Clean, minimalist
|
||||||
|
- **Sections:** Simple hero, minimal features, simple CTA
|
||||||
|
- **Best for:** Focus on simplicity
|
||||||
|
|
||||||
|
#### **Modern Template** (`platform/homepage-modern.html`)
|
||||||
|
- **Style:** Trendy, animated, gradient-heavy
|
||||||
|
- **Sections:** Animated hero, feature cards with hover effects, stats
|
||||||
|
- **Best for:** Modern, tech-forward branding
|
||||||
|
|
||||||
|
### 2. Content Page Template
|
||||||
|
|
||||||
|
**Template:** `platform/content-page.html`
|
||||||
|
|
||||||
|
Used for all content pages (About, FAQ, Contact, etc.)
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Breadcrumb navigation
|
||||||
|
- Responsive design
|
||||||
|
- Enhanced prose styling for content
|
||||||
|
- Context-aware CTAs (for about/contact pages)
|
||||||
|
- Last updated timestamp
|
||||||
|
- Dark mode support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Admin CMS Interface (Recommended)
|
||||||
|
|
||||||
|
#### Platform Homepage Manager (`/admin/platform-homepage`)
|
||||||
|
|
||||||
|
The easiest way to manage your platform homepage is through the admin interface:
|
||||||
|
|
||||||
|
1. **Login to Admin Panel:**
|
||||||
|
```
|
||||||
|
http://localhost:8000/admin/login
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Navigate to Platform Homepage:**
|
||||||
|
- Click "Content Management" in the sidebar
|
||||||
|
- Click "Platform Homepage"
|
||||||
|
|
||||||
|
3. **Customize Homepage:**
|
||||||
|
- **Template Selection:** Choose between Default, Minimal, or Modern templates with visual previews
|
||||||
|
- **Page Title:** Edit the main heading
|
||||||
|
- **Content:** Add HTML content for the hero section
|
||||||
|
- **SEO Settings:** Set meta description and keywords
|
||||||
|
- **Publishing:** Toggle published status
|
||||||
|
- **Preview:** Click "Preview" button to see live homepage
|
||||||
|
- **Save:** Click "Save Changes" to update
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- ✅ Visual template selector with previews
|
||||||
|
- ✅ Real-time save with success/error feedback
|
||||||
|
- ✅ Character counter for SEO fields
|
||||||
|
- ✅ One-click preview
|
||||||
|
- ✅ Dark mode support
|
||||||
|
|
||||||
|
#### Content Pages Manager (`/admin/content-pages`)
|
||||||
|
|
||||||
|
Manage all platform content pages from a single interface:
|
||||||
|
|
||||||
|
1. **Navigate to Content Pages:**
|
||||||
|
- Click "Content Management" in the sidebar
|
||||||
|
- Click "Content Pages"
|
||||||
|
|
||||||
|
2. **View All Pages:**
|
||||||
|
- **Tabs:** Switch between All Pages, Platform Defaults, or Vendor Overrides
|
||||||
|
- **Search:** Type to filter by title, slug, or vendor name
|
||||||
|
- **Table View:** See status, navigation settings, and last updated
|
||||||
|
|
||||||
|
3. **Edit Existing Page:**
|
||||||
|
- Click the edit icon (pencil) next to any page
|
||||||
|
- Modify content, SEO, navigation settings
|
||||||
|
- Click "Update Page" to save
|
||||||
|
|
||||||
|
4. **Create New Page:**
|
||||||
|
- Click "Create Page" button
|
||||||
|
- Fill in:
|
||||||
|
- Page title and URL slug
|
||||||
|
- Content format (HTML or Markdown)
|
||||||
|
- Content (large textarea)
|
||||||
|
- SEO metadata
|
||||||
|
- Navigation settings (header/footer visibility)
|
||||||
|
- Display order
|
||||||
|
- Click "Create Page"
|
||||||
|
|
||||||
|
5. **Delete Page:**
|
||||||
|
- Click the delete icon (trash) next to any page
|
||||||
|
- Confirm deletion
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- ✅ Tabbed interface for easy filtering
|
||||||
|
- ✅ Real-time search
|
||||||
|
- ✅ Status badges (Published/Draft, Platform/Vendor)
|
||||||
|
- ✅ Navigation badges (Header/Footer)
|
||||||
|
- ✅ Quick edit and delete actions
|
||||||
|
- ✅ Empty state with helpful CTAs
|
||||||
|
|
||||||
|
### Creating Platform Pages
|
||||||
|
|
||||||
|
#### Method 1: Using the Seeder Script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create all default platform pages
|
||||||
|
python scripts/create_platform_pages.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates:
|
||||||
|
- Platform Homepage (with modern template)
|
||||||
|
- About Us
|
||||||
|
- FAQ
|
||||||
|
- Contact Us
|
||||||
|
- Terms of Service
|
||||||
|
- Privacy Policy
|
||||||
|
|
||||||
|
#### Method 2: Via API
|
||||||
|
|
||||||
|
**Create Platform Homepage:**
|
||||||
|
```bash
|
||||||
|
POST /api/v1/admin/content-pages/platform
|
||||||
|
{
|
||||||
|
"slug": "platform_homepage",
|
||||||
|
"title": "Welcome to Our Marketplace",
|
||||||
|
"content": "<p>Your custom content...</p>",
|
||||||
|
"template": "modern",
|
||||||
|
"vendor_id": null,
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": false,
|
||||||
|
"show_in_footer": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create Content Page:**
|
||||||
|
```bash
|
||||||
|
POST /api/v1/admin/content-pages/platform
|
||||||
|
{
|
||||||
|
"slug": "about",
|
||||||
|
"title": "About Us",
|
||||||
|
"content": "<h2>Our Story</h2><p>...</p>",
|
||||||
|
"vendor_id": null,
|
||||||
|
"is_published": true,
|
||||||
|
"show_in_header": true,
|
||||||
|
"show_in_footer": true,
|
||||||
|
"display_order": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Method 3: Programmatically
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.core.database import SessionLocal
|
||||||
|
from app.services.content_page_service import content_page_service
|
||||||
|
|
||||||
|
db = SessionLocal()
|
||||||
|
|
||||||
|
# Create homepage
|
||||||
|
homepage = content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="platform_homepage",
|
||||||
|
title="Welcome",
|
||||||
|
content="<p>Content...</p>",
|
||||||
|
template="modern", # default, minimal, or modern
|
||||||
|
vendor_id=None,
|
||||||
|
is_published=True,
|
||||||
|
show_in_header=False,
|
||||||
|
show_in_footer=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create content page
|
||||||
|
about = content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="about",
|
||||||
|
title="About Us",
|
||||||
|
content="<h2>Our Story</h2>",
|
||||||
|
vendor_id=None,
|
||||||
|
is_published=True,
|
||||||
|
show_in_header=True, # Show in top nav
|
||||||
|
show_in_footer=True, # Show in footer
|
||||||
|
display_order=1
|
||||||
|
)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Changing Homepage Template
|
||||||
|
|
||||||
|
Update the `template` field in the database:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET template = 'minimal' -- or 'default', 'modern'
|
||||||
|
WHERE slug = 'platform_homepage' AND vendor_id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via API:
|
||||||
|
```bash
|
||||||
|
PUT /api/v1/admin/content-pages/{id}
|
||||||
|
{
|
||||||
|
"template": "minimal"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customizing Navigation
|
||||||
|
|
||||||
|
**Add page to header menu:**
|
||||||
|
```python
|
||||||
|
content_page_service.update_page(
|
||||||
|
db,
|
||||||
|
page_id=page.id,
|
||||||
|
show_in_header=True,
|
||||||
|
display_order=2 # Position in menu
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Remove page from footer:**
|
||||||
|
```python
|
||||||
|
content_page_service.update_page(
|
||||||
|
db,
|
||||||
|
page_id=page.id,
|
||||||
|
show_in_footer=False
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updating Page Content
|
||||||
|
|
||||||
|
```python
|
||||||
|
content_page_service.update_page(
|
||||||
|
db,
|
||||||
|
page_id=page.id,
|
||||||
|
title="New Title",
|
||||||
|
content="<h1>Updated Content</h1>",
|
||||||
|
meta_description="New SEO description",
|
||||||
|
template="modern" # Change template
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### 1. Request Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
User visits: http://localhost:8000/
|
||||||
|
↓
|
||||||
|
VendorContextMiddleware (no vendor detected, platform mode)
|
||||||
|
↓
|
||||||
|
ContextMiddleware (context = unknown)
|
||||||
|
↓
|
||||||
|
Route: platform_homepage() in main.py:284
|
||||||
|
↓
|
||||||
|
Load page from CMS (slug='platform_homepage', vendor_id=NULL)
|
||||||
|
↓
|
||||||
|
Load header/footer navigation pages
|
||||||
|
↓
|
||||||
|
Render template: platform/homepage-{template}.html
|
||||||
|
↓
|
||||||
|
Response with navigation menu
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. CMS Lookup Logic
|
||||||
|
|
||||||
|
**For Homepage (`/`):**
|
||||||
|
1. Query: `SELECT * FROM content_pages WHERE slug='platform_homepage' AND vendor_id IS NULL`
|
||||||
|
2. If found → Render with selected template (default/minimal/modern)
|
||||||
|
3. If not found → Render fallback static template
|
||||||
|
|
||||||
|
**For Content Pages (`/about`, `/faq`, etc.):**
|
||||||
|
1. Query: `SELECT * FROM content_pages WHERE slug='{slug}' AND vendor_id IS NULL`
|
||||||
|
2. If found → Render `platform/content-page.html`
|
||||||
|
3. If not found → Return 404
|
||||||
|
|
||||||
|
**Navigation Loading:**
|
||||||
|
```python
|
||||||
|
# Header pages (show_in_header=True)
|
||||||
|
header_pages = content_page_service.list_pages_for_vendor(
|
||||||
|
db, vendor_id=None, header_only=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Footer pages (show_in_footer=True)
|
||||||
|
footer_pages = content_page_service.list_pages_for_vendor(
|
||||||
|
db, vendor_id=None, footer_only=True
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Template Rendering
|
||||||
|
|
||||||
|
**Homepage:**
|
||||||
|
```python
|
||||||
|
# In main.py:284
|
||||||
|
if homepage:
|
||||||
|
template_name = homepage.template or "default" # Get from CMS
|
||||||
|
template_path = f"platform/homepage-{template_name}.html"
|
||||||
|
return templates.TemplateResponse(template_path, context)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Content Page:**
|
||||||
|
```python
|
||||||
|
# In main.py:349
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"platform/content-page.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"page": page,
|
||||||
|
"header_pages": header_pages,
|
||||||
|
"footer_pages": footer_pages
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Navigation Menu Example
|
||||||
|
|
||||||
|
Given these pages in the database:
|
||||||
|
|
||||||
|
| Slug | Title | show_in_header | show_in_footer | display_order |
|
||||||
|
|------|-------|----------------|----------------|---------------|
|
||||||
|
| `about` | About Us | ✅ | ✅ | 1 |
|
||||||
|
| `faq` | FAQ | ✅ | ✅ | 2 |
|
||||||
|
| `contact` | Contact Us | ✅ | ✅ | 3 |
|
||||||
|
| `terms` | Terms of Service | ❌ | ✅ | 10 |
|
||||||
|
| `privacy` | Privacy Policy | ❌ | ✅ | 11 |
|
||||||
|
|
||||||
|
**Header Navigation:**
|
||||||
|
```
|
||||||
|
Marketplace | About Us | FAQ | Contact Us | 🌙
|
||||||
|
```
|
||||||
|
|
||||||
|
**Footer Navigation:**
|
||||||
|
```
|
||||||
|
Quick Links
|
||||||
|
- About Us
|
||||||
|
- FAQ
|
||||||
|
- Contact Us
|
||||||
|
- Terms of Service
|
||||||
|
- Privacy Policy
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SEO Optimization
|
||||||
|
|
||||||
|
All pages support SEO meta tags:
|
||||||
|
|
||||||
|
```python
|
||||||
|
content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="about",
|
||||||
|
title="About Our Platform",
|
||||||
|
meta_description="Learn about our mission to democratize e-commerce...",
|
||||||
|
meta_keywords="about us, mission, vision, values, company",
|
||||||
|
# ...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rendered in template:**
|
||||||
|
```html
|
||||||
|
<title>About Our Platform - Marketplace</title>
|
||||||
|
<meta name="description" content="Learn about our mission...">
|
||||||
|
<meta name="keywords" content="about us, mission, vision...">
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
|
||||||
|
1. **Visit homepage:**
|
||||||
|
```
|
||||||
|
http://localhost:8000/
|
||||||
|
```
|
||||||
|
Should show platform homepage with selected template.
|
||||||
|
|
||||||
|
2. **Visit content pages:**
|
||||||
|
```
|
||||||
|
http://localhost:8000/about
|
||||||
|
http://localhost:8000/faq
|
||||||
|
http://localhost:8000/contact
|
||||||
|
http://localhost:8000/terms
|
||||||
|
http://localhost:8000/privacy
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Check navigation:**
|
||||||
|
- Header should show: About Us, FAQ, Contact Us
|
||||||
|
- Footer should show all pages
|
||||||
|
|
||||||
|
4. **Test dark mode:**
|
||||||
|
- Click moon icon in header
|
||||||
|
- Verify dark mode works on all pages
|
||||||
|
|
||||||
|
5. **Test responsive design:**
|
||||||
|
- Resize browser window
|
||||||
|
- Verify mobile menu works
|
||||||
|
- Check layouts on different screen sizes
|
||||||
|
|
||||||
|
### API Testing
|
||||||
|
|
||||||
|
**Get page content:**
|
||||||
|
```bash
|
||||||
|
GET /api/v1/shop/content-pages/about
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get navigation:**
|
||||||
|
```bash
|
||||||
|
GET /api/v1/shop/content-pages/navigation
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Multiple Homepage Templates
|
||||||
|
|
||||||
|
Create custom homepage templates:
|
||||||
|
|
||||||
|
1. Create template file:
|
||||||
|
```
|
||||||
|
app/templates/platform/homepage-custom.html
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update CMS page:
|
||||||
|
```python
|
||||||
|
content_page_service.update_page(
|
||||||
|
db,
|
||||||
|
page_id=homepage.id,
|
||||||
|
template="custom"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Visit homepage → Renders `platform/homepage-custom.html`
|
||||||
|
|
||||||
|
### Dynamic Content Loading
|
||||||
|
|
||||||
|
Homepage templates can load dynamic data:
|
||||||
|
|
||||||
|
```jinja2
|
||||||
|
{# In template #}
|
||||||
|
{% if featured_vendors %}
|
||||||
|
{% for vendor in featured_vendors %}
|
||||||
|
<div>{{ vendor.name }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass data in route handler:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# In main.py
|
||||||
|
featured_vendors = db.query(Vendor).filter(Vendor.is_featured==True).all()
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
template_path,
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"page": homepage,
|
||||||
|
"header_pages": header_pages,
|
||||||
|
"footer_pages": footer_pages,
|
||||||
|
"featured_vendors": featured_vendors, # Additional data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Homepage shows 404
|
||||||
|
|
||||||
|
**Problem:** Platform homepage not rendering
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Run seeder script:
|
||||||
|
```bash
|
||||||
|
python scripts/create_platform_pages.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Verify page exists:
|
||||||
|
```sql
|
||||||
|
SELECT * FROM content_pages
|
||||||
|
WHERE slug='platform_homepage' AND vendor_id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Check page is published:
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET is_published=true
|
||||||
|
WHERE slug='platform_homepage';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation menu is empty
|
||||||
|
|
||||||
|
**Problem:** No pages showing in header/footer
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Check navigation flags:
|
||||||
|
```sql
|
||||||
|
SELECT slug, title, show_in_header, show_in_footer
|
||||||
|
FROM content_pages
|
||||||
|
WHERE vendor_id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update flags:
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET show_in_header=true, show_in_footer=true
|
||||||
|
WHERE slug='about' AND vendor_id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content page returns 404
|
||||||
|
|
||||||
|
**Problem:** `/about` or other pages not found
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Verify slug:
|
||||||
|
```sql
|
||||||
|
SELECT slug FROM content_pages WHERE vendor_id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Check published status:
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET is_published=true
|
||||||
|
WHERE slug='about';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wrong template loads
|
||||||
|
|
||||||
|
**Problem:** Homepage uses wrong template
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET template='modern'
|
||||||
|
WHERE slug='platform_homepage' AND vendor_id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Always set SEO meta tags:**
|
||||||
|
```python
|
||||||
|
meta_description="Clear 150-160 char description",
|
||||||
|
meta_keywords="relevant, keywords, comma, separated"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Use semantic HTML in content:**
|
||||||
|
```html
|
||||||
|
<h2>Main Heading</h2>
|
||||||
|
<p>Paragraph text...</p>
|
||||||
|
<ul><li>List item</li></ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Set appropriate display_order:**
|
||||||
|
```python
|
||||||
|
display_order=1 # First in menu
|
||||||
|
display_order=2 # Second in menu
|
||||||
|
display_order=10 # Legal pages (lower priority)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Use navigation flags wisely:**
|
||||||
|
- `show_in_header`: Only 3-5 main pages
|
||||||
|
- `show_in_footer`: Can include more pages
|
||||||
|
|
||||||
|
5. **Keep homepage content concise:**
|
||||||
|
- Use templates for structure
|
||||||
|
- Add key messaging only
|
||||||
|
- Link to detailed content pages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Admin Endpoints
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/admin/content-pages/platform # Create platform page
|
||||||
|
GET /api/v1/admin/content-pages/platform # List platform pages
|
||||||
|
GET /api/v1/admin/content-pages/{id} # Get specific page
|
||||||
|
PUT /api/v1/admin/content-pages/{id} # Update page
|
||||||
|
DELETE /api/v1/admin/content-pages/{id} # Delete page
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shop (Public) Endpoints
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/shop/content-pages/navigation # Get nav links
|
||||||
|
GET /api/v1/shop/content-pages/{slug} # Get page content
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Content Management System](./content-management-system.md) - Full CMS documentation
|
||||||
|
- [CMS Implementation Guide](./cms-implementation-guide.md) - Technical implementation
|
||||||
|
- [Admin Frontend Guide](../frontend/admin/page-templates.md) - Admin UI patterns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The Platform Homepage system provides:
|
||||||
|
|
||||||
|
✅ **Customizable homepage** with 3 template options
|
||||||
|
✅ **Multiple content pages** (About, FAQ, Contact, Terms, Privacy)
|
||||||
|
✅ **CMS-driven** with full CRUD support
|
||||||
|
✅ **Dynamic navigation** loaded from database
|
||||||
|
✅ **SEO-optimized** with meta tags
|
||||||
|
✅ **Responsive** with dark mode support
|
||||||
|
✅ **Easy to extend** with custom templates
|
||||||
|
|
||||||
|
All pages are managed through the existing CMS system, allowing admins to customize content without touching code.
|
||||||
316
docs/getting-started/platform-homepage-quick-start.md
Normal file
316
docs/getting-started/platform-homepage-quick-start.md
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
# Platform Homepage - Quick Start Guide
|
||||||
|
|
||||||
|
Quick reference for setting up and customizing the platform homepage and content pages.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Setup
|
||||||
|
|
||||||
|
### Step 1: Create Platform Pages
|
||||||
|
|
||||||
|
Run the seeder script to create all default platform pages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/create_platform_pages.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates:
|
||||||
|
- ✅ Platform Homepage (modern template)
|
||||||
|
- ✅ About Us
|
||||||
|
- ✅ FAQ
|
||||||
|
- ✅ Contact Us
|
||||||
|
- ✅ Terms of Service
|
||||||
|
- ✅ Privacy Policy
|
||||||
|
|
||||||
|
### Step 2: Verify Pages
|
||||||
|
|
||||||
|
Visit your platform homepage:
|
||||||
|
```
|
||||||
|
http://localhost:8000/
|
||||||
|
```
|
||||||
|
|
||||||
|
Visit content pages:
|
||||||
|
```
|
||||||
|
http://localhost:8000/about
|
||||||
|
http://localhost:8000/faq
|
||||||
|
http://localhost:8000/contact
|
||||||
|
http://localhost:8000/terms
|
||||||
|
http://localhost:8000/privacy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Customize (Optional)
|
||||||
|
|
||||||
|
**Change homepage template:**
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET template = 'minimal' -- or 'default', 'modern'
|
||||||
|
WHERE slug = 'platform_homepage' AND vendor_id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Update homepage content:**
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET content = '<p>Your custom homepage content...</p>'
|
||||||
|
WHERE slug = 'platform_homepage';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Available Templates
|
||||||
|
|
||||||
|
### Homepage Templates
|
||||||
|
|
||||||
|
| Template | Style | Best For |
|
||||||
|
|----------|-------|----------|
|
||||||
|
| `default` | Professional, feature-rich | Comprehensive showcase |
|
||||||
|
| `minimal` | Clean, simple | Minimalist branding |
|
||||||
|
| `modern` | Animated, gradient-heavy | Tech-forward platforms |
|
||||||
|
|
||||||
|
### Content Page Template
|
||||||
|
|
||||||
|
All content pages use `platform/content-page.html`:
|
||||||
|
- Breadcrumb navigation
|
||||||
|
- Responsive design
|
||||||
|
- Enhanced prose styling
|
||||||
|
- Dark mode support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Common Tasks
|
||||||
|
|
||||||
|
### Add New Content Page
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.core.database import SessionLocal
|
||||||
|
from app.services.content_page_service import content_page_service
|
||||||
|
|
||||||
|
db = SessionLocal()
|
||||||
|
|
||||||
|
page = content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="careers",
|
||||||
|
title="Careers",
|
||||||
|
content="<h1>Join Our Team</h1><p>...</p>",
|
||||||
|
vendor_id=None, # Platform page
|
||||||
|
is_published=True,
|
||||||
|
show_in_header=True,
|
||||||
|
show_in_footer=True,
|
||||||
|
display_order=4
|
||||||
|
)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Page Content
|
||||||
|
|
||||||
|
```python
|
||||||
|
content_page_service.update_page(
|
||||||
|
db,
|
||||||
|
page_id=page.id,
|
||||||
|
title="New Title",
|
||||||
|
content="<h1>Updated Content</h1>",
|
||||||
|
meta_description="New description"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Navigation Visibility
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Show in header menu
|
||||||
|
content_page_service.update_page(
|
||||||
|
db,
|
||||||
|
page_id=page.id,
|
||||||
|
show_in_header=True,
|
||||||
|
display_order=2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove from footer
|
||||||
|
content_page_service.update_page(
|
||||||
|
db,
|
||||||
|
page_id=page.id,
|
||||||
|
show_in_footer=False
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Verification
|
||||||
|
|
||||||
|
### Check Database
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- List all platform pages
|
||||||
|
SELECT id, slug, title, template, is_published, show_in_header, show_in_footer
|
||||||
|
FROM content_pages
|
||||||
|
WHERE vendor_id IS NULL
|
||||||
|
ORDER BY display_order;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Navigation
|
||||||
|
|
||||||
|
1. Visit homepage: `http://localhost:8000/`
|
||||||
|
2. Check header menu (should show: About, FAQ, Contact)
|
||||||
|
3. Scroll to footer (should show all pages)
|
||||||
|
4. Click links to verify they work
|
||||||
|
|
||||||
|
### Test Dark Mode
|
||||||
|
|
||||||
|
1. Click moon icon in header
|
||||||
|
2. Verify all pages render correctly in dark mode
|
||||||
|
3. Toggle back to light mode
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Page Content Examples
|
||||||
|
|
||||||
|
### Homepage Content
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="lead">
|
||||||
|
Welcome to our multi-vendor marketplace platform.
|
||||||
|
Connect with thousands of vendors and discover amazing products.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Join our growing community of successful online businesses today.
|
||||||
|
</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### About Us Content
|
||||||
|
|
||||||
|
```html
|
||||||
|
<h2>Our Mission</h2>
|
||||||
|
<p>We're democratizing e-commerce...</p>
|
||||||
|
|
||||||
|
<h2>Our Values</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Innovation:</strong> Constantly improving</li>
|
||||||
|
<li><strong>Transparency:</strong> No hidden fees</li>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
### FAQ Content
|
||||||
|
|
||||||
|
```html
|
||||||
|
<h2>Getting Started</h2>
|
||||||
|
|
||||||
|
<h3>How do I create an account?</h3>
|
||||||
|
<p>Contact our sales team...</p>
|
||||||
|
|
||||||
|
<h3>What are your pricing plans?</h3>
|
||||||
|
<p>We offer flexible pricing...</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Customization Tips
|
||||||
|
|
||||||
|
### Custom Homepage Template
|
||||||
|
|
||||||
|
1. Create new template:
|
||||||
|
```
|
||||||
|
app/templates/platform/homepage-custom.html
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Extend base template:
|
||||||
|
```jinja2
|
||||||
|
{% extends "platform/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Your custom layout -->
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Update database:
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET template = 'custom'
|
||||||
|
WHERE slug = 'platform_homepage';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize Navigation Order
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET display_order = 1
|
||||||
|
WHERE slug = 'about';
|
||||||
|
|
||||||
|
UPDATE content_pages
|
||||||
|
SET display_order = 2
|
||||||
|
WHERE slug = 'faq';
|
||||||
|
|
||||||
|
UPDATE content_pages
|
||||||
|
SET display_order = 3
|
||||||
|
WHERE slug = 'contact';
|
||||||
|
```
|
||||||
|
|
||||||
|
Result in header: `About | FAQ | Contact`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Homepage shows 404
|
||||||
|
|
||||||
|
**Fix:** Run seeder script
|
||||||
|
```bash
|
||||||
|
python scripts/create_platform_pages.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation menu is empty
|
||||||
|
|
||||||
|
**Fix:** Update navigation flags
|
||||||
|
```sql
|
||||||
|
UPDATE content_pages
|
||||||
|
SET show_in_header = true, show_in_footer = true
|
||||||
|
WHERE slug IN ('about', 'faq', 'contact')
|
||||||
|
AND vendor_id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content not updating
|
||||||
|
|
||||||
|
**Fix:** Clear any caching and restart server
|
||||||
|
```bash
|
||||||
|
# If using uvicorn with reload
|
||||||
|
# Changes should auto-reload
|
||||||
|
|
||||||
|
# If manual restart needed
|
||||||
|
pkill -f uvicorn
|
||||||
|
uvicorn main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Full Documentation
|
||||||
|
|
||||||
|
For complete details, see:
|
||||||
|
- [Platform Homepage Documentation](../features/platform-homepage.md)
|
||||||
|
- [CMS Feature Documentation](../features/content-management-system.md)
|
||||||
|
- [CMS Implementation Guide](../features/cms-implementation-guide.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Checklist
|
||||||
|
|
||||||
|
After setup, verify:
|
||||||
|
|
||||||
|
- [ ] Homepage loads at `http://localhost:8000/`
|
||||||
|
- [ ] All content pages are accessible
|
||||||
|
- [ ] Header navigation shows correct pages
|
||||||
|
- [ ] Footer navigation shows all pages
|
||||||
|
- [ ] Dark mode works
|
||||||
|
- [ ] Mobile responsive design works
|
||||||
|
- [ ] SEO meta tags are present
|
||||||
|
- [ ] Links in navigation work correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 You're Done!
|
||||||
|
|
||||||
|
Your platform homepage and content pages are now set up and ready to customize!
|
||||||
|
|
||||||
|
**Next Steps:**
|
||||||
|
1. Customize homepage content via admin panel at `/admin/platform-homepage`
|
||||||
|
2. Manage all content pages at `/admin/content-pages`
|
||||||
|
3. Add your own branding and colors
|
||||||
|
4. Create additional content pages as needed
|
||||||
|
5. Set up analytics tracking
|
||||||
132
main.py
132
main.py
@@ -272,6 +272,138 @@ async def vendor_root_path(vendor_code: str, request: Request, db: Session = Dep
|
|||||||
# No landing page - redirect to shop
|
# No landing page - redirect to shop
|
||||||
return RedirectResponse(url=f"/vendors/{vendor_code}/shop/", status_code=302)
|
return RedirectResponse(url=f"/vendors/{vendor_code}/shop/", status_code=302)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# PLATFORM PUBLIC PAGES (Platform Homepage, About, FAQ, etc.)
|
||||||
|
# ============================================================================
|
||||||
|
logger.info("Registering platform public page routes:")
|
||||||
|
logger.info(" - / (platform homepage)")
|
||||||
|
logger.info(" - /{slug} (platform content pages: /about, /faq, /terms, /contact)")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
||||||
|
async def platform_homepage(request: Request, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
Platform homepage at localhost:8000 or platform.com
|
||||||
|
|
||||||
|
Looks for CMS page with slug='platform_homepage' (vendor_id=NULL)
|
||||||
|
Falls back to default static template if not found.
|
||||||
|
"""
|
||||||
|
from app.services.content_page_service import content_page_service
|
||||||
|
|
||||||
|
logger.debug("[PLATFORM] Homepage requested")
|
||||||
|
|
||||||
|
# Try to load platform homepage from CMS
|
||||||
|
homepage = content_page_service.get_page_for_vendor(
|
||||||
|
db,
|
||||||
|
slug="platform_homepage",
|
||||||
|
vendor_id=None, # Platform-level page
|
||||||
|
include_unpublished=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load header and footer navigation
|
||||||
|
header_pages = content_page_service.list_pages_for_vendor(
|
||||||
|
db,
|
||||||
|
vendor_id=None,
|
||||||
|
header_only=True,
|
||||||
|
include_unpublished=False
|
||||||
|
)
|
||||||
|
|
||||||
|
footer_pages = content_page_service.list_pages_for_vendor(
|
||||||
|
db,
|
||||||
|
vendor_id=None,
|
||||||
|
footer_only=True,
|
||||||
|
include_unpublished=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if homepage:
|
||||||
|
# Use template selection from CMS
|
||||||
|
template_name = homepage.template or "default"
|
||||||
|
template_path = f"platform/homepage-{template_name}.html"
|
||||||
|
|
||||||
|
logger.info(f"[PLATFORM] Rendering CMS homepage with template: {template_path}")
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
template_path,
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"page": homepage,
|
||||||
|
"header_pages": header_pages,
|
||||||
|
"footer_pages": footer_pages,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Fallback to default static template
|
||||||
|
logger.info("[PLATFORM] No CMS homepage found, using default template")
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"platform/homepage-default.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"header_pages": header_pages,
|
||||||
|
"footer_pages": footer_pages,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/{slug}", response_class=HTMLResponse, include_in_schema=False)
|
||||||
|
async def platform_content_page(
|
||||||
|
request: Request,
|
||||||
|
slug: str,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Platform content pages: /about, /faq, /terms, /contact, etc.
|
||||||
|
|
||||||
|
Loads content from CMS with slug (vendor_id=NULL for platform pages).
|
||||||
|
Returns 404 if page not found.
|
||||||
|
|
||||||
|
This route MUST be defined LAST to avoid conflicts with other routes.
|
||||||
|
"""
|
||||||
|
from app.services.content_page_service import content_page_service
|
||||||
|
|
||||||
|
logger.debug(f"[PLATFORM] Content page requested: /{slug}")
|
||||||
|
|
||||||
|
# Load page from CMS
|
||||||
|
page = content_page_service.get_page_for_vendor(
|
||||||
|
db,
|
||||||
|
slug=slug,
|
||||||
|
vendor_id=None, # Platform pages only
|
||||||
|
include_unpublished=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if not page:
|
||||||
|
logger.warning(f"[PLATFORM] Content page not found: {slug}")
|
||||||
|
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
||||||
|
|
||||||
|
# Load header and footer navigation
|
||||||
|
header_pages = content_page_service.list_pages_for_vendor(
|
||||||
|
db,
|
||||||
|
vendor_id=None,
|
||||||
|
header_only=True,
|
||||||
|
include_unpublished=False
|
||||||
|
)
|
||||||
|
|
||||||
|
footer_pages = content_page_service.list_pages_for_vendor(
|
||||||
|
db,
|
||||||
|
vendor_id=None,
|
||||||
|
footer_only=True,
|
||||||
|
include_unpublished=False
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"[PLATFORM] Rendering content page: {page.title} (/{slug})")
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"platform/content-page.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"page": page,
|
||||||
|
"header_pages": header_pages,
|
||||||
|
"footer_pages": footer_pages,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
logger.info("=" * 80)
|
logger.info("=" * 80)
|
||||||
|
|
||||||
# Log all registered routes
|
# Log all registered routes
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ nav:
|
|||||||
- Complete Setup Guide: getting-started/DATABASE_SETUP_GUIDE.md
|
- Complete Setup Guide: getting-started/DATABASE_SETUP_GUIDE.md
|
||||||
- Quick Reference: getting-started/DATABASE_QUICK_REFERENCE.md
|
- Quick Reference: getting-started/DATABASE_QUICK_REFERENCE.md
|
||||||
- CMS Quick Start: getting-started/cms-quick-start.md
|
- CMS Quick Start: getting-started/cms-quick-start.md
|
||||||
|
- Platform Homepage Quick Start: getting-started/platform-homepage-quick-start.md
|
||||||
- Configuration: getting-started/configuration.md
|
- Configuration: getting-started/configuration.md
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
@@ -149,6 +150,7 @@ nav:
|
|||||||
- Content Management System:
|
- Content Management System:
|
||||||
- Overview: features/content-management-system.md
|
- Overview: features/content-management-system.md
|
||||||
- Implementation Guide: features/cms-implementation-guide.md
|
- Implementation Guide: features/cms-implementation-guide.md
|
||||||
|
- Platform Homepage: features/platform-homepage.md
|
||||||
- Vendor Landing Pages: features/vendor-landing-pages.md
|
- Vendor Landing Pages: features/vendor-landing-pages.md
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
491
scripts/create_platform_pages.py
Executable file
491
scripts/create_platform_pages.py
Executable file
@@ -0,0 +1,491 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Create Platform Content Pages
|
||||||
|
|
||||||
|
This script creates default platform-level content pages:
|
||||||
|
- Platform Homepage (slug='platform_homepage')
|
||||||
|
- About Us (slug='about')
|
||||||
|
- FAQ (slug='faq')
|
||||||
|
- Terms of Service (slug='terms')
|
||||||
|
- Privacy Policy (slug='privacy')
|
||||||
|
- Contact Us (slug='contact')
|
||||||
|
|
||||||
|
All pages are created with vendor_id=NULL (platform-level defaults).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python scripts/create_platform_pages.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add project root to path
|
||||||
|
project_root = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
|
from app.core.database import SessionLocal
|
||||||
|
from app.services.content_page_service import content_page_service
|
||||||
|
|
||||||
|
|
||||||
|
def create_platform_pages():
|
||||||
|
"""Create default platform content pages."""
|
||||||
|
db = SessionLocal()
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("=" * 80)
|
||||||
|
print("CREATING PLATFORM CONTENT PAGES")
|
||||||
|
print("=" * 80)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Import ContentPage for checking existing pages
|
||||||
|
from models.database.content_page import ContentPage
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# 1. PLATFORM HOMEPAGE
|
||||||
|
# ========================================================================
|
||||||
|
print("1. Creating Platform Homepage...")
|
||||||
|
|
||||||
|
# Check if already exists
|
||||||
|
existing = db.query(ContentPage).filter_by(vendor_id=None, slug="platform_homepage").first()
|
||||||
|
if existing:
|
||||||
|
print(f" ⚠️ Skipped: Platform Homepage - already exists (ID: {existing.id})")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
homepage = content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="platform_homepage",
|
||||||
|
title="Welcome to Our Multi-Vendor Marketplace",
|
||||||
|
content="""
|
||||||
|
<p class="lead">
|
||||||
|
Connect vendors with customers worldwide. Build your online store and reach millions of shoppers.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Our platform empowers entrepreneurs to launch their own branded e-commerce stores
|
||||||
|
with minimal effort and maximum impact.
|
||||||
|
</p>
|
||||||
|
""",
|
||||||
|
template="modern", # Uses platform/homepage-modern.html
|
||||||
|
vendor_id=None, # Platform-level page
|
||||||
|
is_published=True,
|
||||||
|
show_in_header=False, # Homepage is not in menu (it's the root)
|
||||||
|
show_in_footer=False,
|
||||||
|
display_order=0,
|
||||||
|
meta_description="Leading multi-vendor marketplace platform. Connect with thousands of vendors and discover millions of products.",
|
||||||
|
meta_keywords="marketplace, multi-vendor, e-commerce, online shopping, platform"
|
||||||
|
)
|
||||||
|
print(f" ✅ Created: {homepage.title} (/{homepage.slug})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ Error: Platform Homepage - {str(e)}")
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# 2. ABOUT US
|
||||||
|
# ========================================================================
|
||||||
|
print("2. Creating About Us page...")
|
||||||
|
|
||||||
|
existing = db.query(ContentPage).filter_by(vendor_id=None, slug="about").first()
|
||||||
|
if existing:
|
||||||
|
print(f" ⚠️ Skipped: About Us - already exists (ID: {existing.id})")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
about = content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="about",
|
||||||
|
title="About Us",
|
||||||
|
content="""
|
||||||
|
<h2>Our Mission</h2>
|
||||||
|
<p>
|
||||||
|
We're on a mission to democratize e-commerce by providing powerful,
|
||||||
|
easy-to-use tools for entrepreneurs worldwide.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Our Story</h2>
|
||||||
|
<p>
|
||||||
|
Founded in 2024, our platform has grown to serve over 10,000 active vendors
|
||||||
|
and millions of customers around the globe. We believe that everyone should
|
||||||
|
have the opportunity to build and grow their own online business.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Why Choose Us?</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Easy to Start:</strong> Launch your store in minutes, not months</li>
|
||||||
|
<li><strong>Powerful Tools:</strong> Everything you need to succeed in one platform</li>
|
||||||
|
<li><strong>Scalable:</strong> Grow from startup to enterprise seamlessly</li>
|
||||||
|
<li><strong>Reliable:</strong> 99.9% uptime guarantee with 24/7 support</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Our Values</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Innovation:</strong> We constantly improve and evolve our platform</li>
|
||||||
|
<li><strong>Transparency:</strong> No hidden fees, no surprises</li>
|
||||||
|
<li><strong>Community:</strong> We succeed when our vendors succeed</li>
|
||||||
|
<li><strong>Excellence:</strong> We strive for the highest quality in everything we do</li>
|
||||||
|
</ul>
|
||||||
|
""",
|
||||||
|
vendor_id=None,
|
||||||
|
is_published=True,
|
||||||
|
show_in_header=True, # Show in header navigation
|
||||||
|
show_in_footer=True, # Show in footer
|
||||||
|
display_order=1,
|
||||||
|
meta_description="Learn about our mission to democratize e-commerce and empower entrepreneurs worldwide.",
|
||||||
|
meta_keywords="about us, mission, vision, values, company"
|
||||||
|
)
|
||||||
|
print(f" ✅ Created: {about.title} (/{about.slug})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ Error: About Us - {str(e)}")
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# 3. FAQ
|
||||||
|
# ========================================================================
|
||||||
|
print("3. Creating FAQ page...")
|
||||||
|
|
||||||
|
existing = db.query(ContentPage).filter_by(vendor_id=None, slug="faq").first()
|
||||||
|
if existing:
|
||||||
|
print(f" ⚠️ Skipped: FAQ - already exists (ID: {existing.id})")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
faq = content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="faq",
|
||||||
|
title="Frequently Asked Questions",
|
||||||
|
content="""
|
||||||
|
<h2>Getting Started</h2>
|
||||||
|
|
||||||
|
<h3>How do I create a vendor account?</h3>
|
||||||
|
<p>
|
||||||
|
Contact our sales team to get started. We'll set up your account and provide
|
||||||
|
you with everything you need to launch your store.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>How long does it take to set up my store?</h3>
|
||||||
|
<p>
|
||||||
|
Most vendors can launch their store in less than 24 hours. Our team will guide
|
||||||
|
you through the setup process step by step.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Pricing & Payment</h2>
|
||||||
|
|
||||||
|
<h3>What are your pricing plans?</h3>
|
||||||
|
<p>
|
||||||
|
We offer flexible pricing plans based on your business needs. Contact us for
|
||||||
|
detailed pricing information and to find the plan that's right for you.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>When do I get paid?</h3>
|
||||||
|
<p>
|
||||||
|
Payments are processed weekly, with funds typically reaching your account
|
||||||
|
within 2-3 business days.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Features & Support</h2>
|
||||||
|
|
||||||
|
<h3>Can I customize my store's appearance?</h3>
|
||||||
|
<p>
|
||||||
|
Yes! Our platform supports full theme customization including colors, fonts,
|
||||||
|
logos, and layouts. Make your store truly yours.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>What kind of support do you provide?</h3>
|
||||||
|
<p>
|
||||||
|
We offer 24/7 email support for all vendors, with priority phone support
|
||||||
|
available for enterprise plans.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Technical Questions</h2>
|
||||||
|
|
||||||
|
<h3>Do I need technical knowledge to use the platform?</h3>
|
||||||
|
<p>
|
||||||
|
No! Our platform is designed to be user-friendly for everyone. However, if you
|
||||||
|
want to customize advanced features, our documentation and support team are here to help.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Can I integrate with other tools?</h3>
|
||||||
|
<p>
|
||||||
|
Yes, we support integrations with popular payment gateways, shipping providers,
|
||||||
|
and marketing tools.
|
||||||
|
</p>
|
||||||
|
""",
|
||||||
|
vendor_id=None,
|
||||||
|
is_published=True,
|
||||||
|
show_in_header=True, # Show in header navigation
|
||||||
|
show_in_footer=True,
|
||||||
|
display_order=2,
|
||||||
|
meta_description="Find answers to common questions about our marketplace platform.",
|
||||||
|
meta_keywords="faq, frequently asked questions, help, support"
|
||||||
|
)
|
||||||
|
print(f" ✅ Created: {faq.title} (/{faq.slug})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ Error: FAQ - {str(e)}")
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# 4. CONTACT US
|
||||||
|
# ========================================================================
|
||||||
|
print("4. Creating Contact Us page...")
|
||||||
|
|
||||||
|
existing = db.query(ContentPage).filter_by(vendor_id=None, slug="contact").first()
|
||||||
|
if existing:
|
||||||
|
print(f" ⚠️ Skipped: Contact Us - already exists (ID: {existing.id})")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
contact = content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="contact",
|
||||||
|
title="Contact Us",
|
||||||
|
content="""
|
||||||
|
<h2>Get in Touch</h2>
|
||||||
|
<p>
|
||||||
|
We'd love to hear from you! Whether you have questions about our platform,
|
||||||
|
need technical support, or want to discuss partnership opportunities, our
|
||||||
|
team is here to help.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Contact Information</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Email:</strong> support@marketplace.com</li>
|
||||||
|
<li><strong>Phone:</strong> +1 (555) 123-4567</li>
|
||||||
|
<li><strong>Hours:</strong> Monday - Friday, 9 AM - 6 PM PST</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Office Address</h2>
|
||||||
|
<p>
|
||||||
|
123 Business Street, Suite 100<br>
|
||||||
|
San Francisco, CA 94102<br>
|
||||||
|
United States
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Sales Inquiries</h2>
|
||||||
|
<p>
|
||||||
|
Interested in launching your store on our platform?<br>
|
||||||
|
Email: <a href="mailto:sales@marketplace.com">sales@marketplace.com</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Technical Support</h2>
|
||||||
|
<p>
|
||||||
|
Need help with your store?<br>
|
||||||
|
Email: <a href="mailto:support@marketplace.com">support@marketplace.com</a><br>
|
||||||
|
24/7 email support for all vendors
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Media & Press</h2>
|
||||||
|
<p>
|
||||||
|
For media inquiries and press releases:<br>
|
||||||
|
Email: <a href="mailto:press@marketplace.com">press@marketplace.com</a>
|
||||||
|
</p>
|
||||||
|
""",
|
||||||
|
vendor_id=None,
|
||||||
|
is_published=True,
|
||||||
|
show_in_header=True, # Show in header navigation
|
||||||
|
show_in_footer=True,
|
||||||
|
display_order=3,
|
||||||
|
meta_description="Get in touch with our team. We're here to help you succeed.",
|
||||||
|
meta_keywords="contact, support, email, phone, address"
|
||||||
|
)
|
||||||
|
print(f" ✅ Created: {contact.title} (/{contact.slug})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ Error: Contact Us - {str(e)}")
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# 5. TERMS OF SERVICE
|
||||||
|
# ========================================================================
|
||||||
|
print("5. Creating Terms of Service page...")
|
||||||
|
|
||||||
|
existing = db.query(ContentPage).filter_by(vendor_id=None, slug="terms").first()
|
||||||
|
if existing:
|
||||||
|
print(f" ⚠️ Skipped: Terms of Service - already exists (ID: {existing.id})")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
terms = content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="terms",
|
||||||
|
title="Terms of Service",
|
||||||
|
content="""
|
||||||
|
<p><em>Last updated: January 1, 2024</em></p>
|
||||||
|
|
||||||
|
<h2>1. Acceptance of Terms</h2>
|
||||||
|
<p>
|
||||||
|
By accessing and using this marketplace platform, you accept and agree to be
|
||||||
|
bound by the terms and provisions of this agreement.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>2. Use License</h2>
|
||||||
|
<p>
|
||||||
|
Permission is granted to temporarily access the materials on our platform for
|
||||||
|
personal, non-commercial transitory viewing only.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>3. Account Terms</h2>
|
||||||
|
<ul>
|
||||||
|
<li>You must be at least 18 years old to use our platform</li>
|
||||||
|
<li>You must provide accurate and complete registration information</li>
|
||||||
|
<li>You are responsible for maintaining the security of your account</li>
|
||||||
|
<li>You are responsible for all activities under your account</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>4. Vendor Responsibilities</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Provide accurate product information and pricing</li>
|
||||||
|
<li>Honor all orders and commitments made through the platform</li>
|
||||||
|
<li>Comply with all applicable laws and regulations</li>
|
||||||
|
<li>Maintain appropriate customer service standards</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>5. Prohibited Activities</h2>
|
||||||
|
<p>You may not use our platform to:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Engage in any fraudulent or illegal activities</li>
|
||||||
|
<li>Violate any intellectual property rights</li>
|
||||||
|
<li>Transmit harmful code or malware</li>
|
||||||
|
<li>Interfere with platform operations</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>6. Termination</h2>
|
||||||
|
<p>
|
||||||
|
We reserve the right to terminate or suspend your account at any time for
|
||||||
|
violation of these terms.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>7. Limitation of Liability</h2>
|
||||||
|
<p>
|
||||||
|
In no event shall our company be liable for any damages arising out of the
|
||||||
|
use or inability to use our platform.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>8. Changes to Terms</h2>
|
||||||
|
<p>
|
||||||
|
We reserve the right to modify these terms at any time. We will notify users
|
||||||
|
of any changes via email.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>9. Contact</h2>
|
||||||
|
<p>
|
||||||
|
If you have any questions about these Terms, please contact us at
|
||||||
|
<a href="mailto:legal@marketplace.com">legal@marketplace.com</a>.
|
||||||
|
</p>
|
||||||
|
""",
|
||||||
|
vendor_id=None,
|
||||||
|
is_published=True,
|
||||||
|
show_in_header=False, # Too legal for header
|
||||||
|
show_in_footer=True, # Show in footer
|
||||||
|
display_order=10,
|
||||||
|
meta_description="Read our terms of service and platform usage policies.",
|
||||||
|
meta_keywords="terms of service, terms, legal, policy, agreement"
|
||||||
|
)
|
||||||
|
print(f" ✅ Created: {terms.title} (/{terms.slug})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ Error: Terms of Service - {str(e)}")
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# 6. PRIVACY POLICY
|
||||||
|
# ========================================================================
|
||||||
|
print("6. Creating Privacy Policy page...")
|
||||||
|
|
||||||
|
existing = db.query(ContentPage).filter_by(vendor_id=None, slug="privacy").first()
|
||||||
|
if existing:
|
||||||
|
print(f" ⚠️ Skipped: Privacy Policy - already exists (ID: {existing.id})")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
privacy = content_page_service.create_page(
|
||||||
|
db,
|
||||||
|
slug="privacy",
|
||||||
|
title="Privacy Policy",
|
||||||
|
content="""
|
||||||
|
<p><em>Last updated: January 1, 2024</em></p>
|
||||||
|
|
||||||
|
<h2>1. Information We Collect</h2>
|
||||||
|
<p>We collect information you provide directly to us, including:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Name, email address, and contact information</li>
|
||||||
|
<li>Payment and billing information</li>
|
||||||
|
<li>Store and product information</li>
|
||||||
|
<li>Communications with us</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>2. How We Use Your Information</h2>
|
||||||
|
<p>We use the information we collect to:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Provide, maintain, and improve our services</li>
|
||||||
|
<li>Process transactions and send related information</li>
|
||||||
|
<li>Send technical notices and support messages</li>
|
||||||
|
<li>Respond to your comments and questions</li>
|
||||||
|
<li>Monitor and analyze trends and usage</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>3. Information Sharing</h2>
|
||||||
|
<p>
|
||||||
|
We do not sell your personal information. We may share information with:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Service providers who help us operate our platform</li>
|
||||||
|
<li>Law enforcement when required by law</li>
|
||||||
|
<li>Other parties with your consent</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>4. Data Security</h2>
|
||||||
|
<p>
|
||||||
|
We implement appropriate security measures to protect your personal information.
|
||||||
|
However, no method of transmission over the internet is 100% secure.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>5. Your Rights</h2>
|
||||||
|
<p>You have the right to:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Access your personal information</li>
|
||||||
|
<li>Correct inaccurate information</li>
|
||||||
|
<li>Request deletion of your information</li>
|
||||||
|
<li>Opt-out of marketing communications</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>6. Cookies</h2>
|
||||||
|
<p>
|
||||||
|
We use cookies and similar tracking technologies to track activity on our
|
||||||
|
platform and hold certain information. You can instruct your browser to
|
||||||
|
refuse cookies.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>7. Changes to This Policy</h2>
|
||||||
|
<p>
|
||||||
|
We may update this privacy policy from time to time. We will notify you of
|
||||||
|
any changes by posting the new policy on this page.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>8. Contact Us</h2>
|
||||||
|
<p>
|
||||||
|
If you have questions about this Privacy Policy, please contact us at
|
||||||
|
<a href="mailto:privacy@marketplace.com">privacy@marketplace.com</a>.
|
||||||
|
</p>
|
||||||
|
""",
|
||||||
|
vendor_id=None,
|
||||||
|
is_published=True,
|
||||||
|
show_in_header=False, # Too legal for header
|
||||||
|
show_in_footer=True, # Show in footer
|
||||||
|
display_order=11,
|
||||||
|
meta_description="Learn how we collect, use, and protect your personal information.",
|
||||||
|
meta_keywords="privacy policy, privacy, data protection, gdpr, personal information"
|
||||||
|
)
|
||||||
|
print(f" ✅ Created: {privacy.title} (/{privacy.slug})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ Error: Privacy Policy - {str(e)}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 80)
|
||||||
|
print("✅ Platform pages creation completed successfully!")
|
||||||
|
print("=" * 80)
|
||||||
|
print()
|
||||||
|
print("Created pages:")
|
||||||
|
print(" - Platform Homepage: http://localhost:8000/")
|
||||||
|
print(" - About Us: http://localhost:8000/about")
|
||||||
|
print(" - FAQ: http://localhost:8000/faq")
|
||||||
|
print(" - Contact Us: http://localhost:8000/contact")
|
||||||
|
print(" - Terms of Service: http://localhost:8000/terms")
|
||||||
|
print(" - Privacy Policy: http://localhost:8000/privacy")
|
||||||
|
print()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ Error: {e}")
|
||||||
|
db.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
create_platform_pages()
|
||||||
171
static/admin/js/content-page-edit.js
Normal file
171
static/admin/js/content-page-edit.js
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
// static/admin/js/content-page-edit.js
|
||||||
|
|
||||||
|
// Use centralized logger
|
||||||
|
const contentPageEditLog = window.LogConfig.loggers.contentPageEdit || window.LogConfig.createLogger('contentPageEdit');
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// CONTENT PAGE EDITOR FUNCTION
|
||||||
|
// ============================================
|
||||||
|
function contentPageEditor(pageId) {
|
||||||
|
return {
|
||||||
|
// Inherit base layout functionality from init-alpine.js
|
||||||
|
...data(),
|
||||||
|
|
||||||
|
// Page identifier for sidebar active state
|
||||||
|
currentPage: 'content-pages',
|
||||||
|
|
||||||
|
// Editor state
|
||||||
|
pageId: pageId,
|
||||||
|
form: {
|
||||||
|
slug: '',
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
content_format: 'html',
|
||||||
|
template: 'default',
|
||||||
|
meta_description: '',
|
||||||
|
meta_keywords: '',
|
||||||
|
is_published: false,
|
||||||
|
show_in_header: false,
|
||||||
|
show_in_footer: true,
|
||||||
|
display_order: 0,
|
||||||
|
vendor_id: null
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
saving: false,
|
||||||
|
error: null,
|
||||||
|
successMessage: null,
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
async init() {
|
||||||
|
contentPageEditLog.info('=== CONTENT PAGE EDITOR INITIALIZING ===');
|
||||||
|
contentPageEditLog.info('Page ID:', this.pageId);
|
||||||
|
|
||||||
|
// Prevent multiple initializations
|
||||||
|
if (window._contentPageEditInitialized) {
|
||||||
|
contentPageEditLog.warn('Content page editor already initialized, skipping...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window._contentPageEditInitialized = true;
|
||||||
|
|
||||||
|
if (this.pageId) {
|
||||||
|
// Edit mode - load existing page
|
||||||
|
contentPageEditLog.group('Loading page for editing');
|
||||||
|
await this.loadPage();
|
||||||
|
contentPageEditLog.groupEnd();
|
||||||
|
} else {
|
||||||
|
// Create mode - use default values
|
||||||
|
contentPageEditLog.info('Create mode - using default form values');
|
||||||
|
}
|
||||||
|
|
||||||
|
contentPageEditLog.info('=== CONTENT PAGE EDITOR INITIALIZATION COMPLETE ===');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Load existing page
|
||||||
|
async loadPage() {
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
contentPageEditLog.info(`Fetching page ${this.pageId}...`);
|
||||||
|
|
||||||
|
const response = await apiClient.get(`/admin/content-pages/${this.pageId}`);
|
||||||
|
|
||||||
|
contentPageEditLog.debug('API Response:', response);
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
throw new Error('Invalid API response');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle response - API returns object directly
|
||||||
|
const page = response.data || response;
|
||||||
|
this.form = {
|
||||||
|
slug: page.slug || '',
|
||||||
|
title: page.title || '',
|
||||||
|
content: page.content || '',
|
||||||
|
content_format: page.content_format || 'html',
|
||||||
|
template: page.template || 'default',
|
||||||
|
meta_description: page.meta_description || '',
|
||||||
|
meta_keywords: page.meta_keywords || '',
|
||||||
|
is_published: page.is_published || false,
|
||||||
|
show_in_header: page.show_in_header || false,
|
||||||
|
show_in_footer: page.show_in_footer !== undefined ? page.show_in_footer : true,
|
||||||
|
display_order: page.display_order || 0,
|
||||||
|
vendor_id: page.vendor_id
|
||||||
|
};
|
||||||
|
|
||||||
|
contentPageEditLog.info('Page loaded successfully');
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
contentPageEditLog.error('Error loading page:', err);
|
||||||
|
this.error = err.message || 'Failed to load page';
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Save page (create or update)
|
||||||
|
async savePage() {
|
||||||
|
if (this.saving) return;
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
this.error = null;
|
||||||
|
this.successMessage = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
contentPageEditLog.info(this.pageId ? 'Updating page...' : 'Creating page...');
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
slug: this.form.slug,
|
||||||
|
title: this.form.title,
|
||||||
|
content: this.form.content,
|
||||||
|
content_format: this.form.content_format,
|
||||||
|
template: this.form.template,
|
||||||
|
meta_description: this.form.meta_description,
|
||||||
|
meta_keywords: this.form.meta_keywords,
|
||||||
|
is_published: this.form.is_published,
|
||||||
|
show_in_header: this.form.show_in_header,
|
||||||
|
show_in_footer: this.form.show_in_footer,
|
||||||
|
display_order: this.form.display_order,
|
||||||
|
vendor_id: this.form.vendor_id
|
||||||
|
};
|
||||||
|
|
||||||
|
contentPageEditLog.debug('Payload:', payload);
|
||||||
|
|
||||||
|
let response;
|
||||||
|
if (this.pageId) {
|
||||||
|
// Update existing page
|
||||||
|
response = await apiClient.put(`/admin/content-pages/${this.pageId}`, payload);
|
||||||
|
this.successMessage = 'Page updated successfully!';
|
||||||
|
contentPageEditLog.info('Page updated');
|
||||||
|
} else {
|
||||||
|
// Create new page
|
||||||
|
response = await apiClient.post('/admin/content-pages/platform', payload);
|
||||||
|
this.successMessage = 'Page created successfully!';
|
||||||
|
contentPageEditLog.info('Page created');
|
||||||
|
|
||||||
|
// Redirect to edit page after creation
|
||||||
|
const pageData = response.data || response;
|
||||||
|
if (pageData && pageData.id) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = `/admin/content-pages/${pageData.id}/edit`;
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear success message after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
contentPageEditLog.error('Error saving page:', err);
|
||||||
|
this.error = err.message || 'Failed to save page';
|
||||||
|
|
||||||
|
// Scroll to top to show error
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
161
static/admin/js/content-pages.js
Normal file
161
static/admin/js/content-pages.js
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// static/admin/js/content-pages.js
|
||||||
|
|
||||||
|
// Use centralized logger
|
||||||
|
const contentPagesLog = window.LogConfig.loggers.contentPages || window.LogConfig.createLogger('contentPages');
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// CONTENT PAGES MANAGER FUNCTION
|
||||||
|
// ============================================
|
||||||
|
function contentPagesManager() {
|
||||||
|
return {
|
||||||
|
// Inherit base layout functionality from init-alpine.js
|
||||||
|
...data(),
|
||||||
|
|
||||||
|
// Page identifier for sidebar active state
|
||||||
|
currentPage: 'content-pages',
|
||||||
|
|
||||||
|
// Content pages specific state
|
||||||
|
allPages: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
|
||||||
|
// Tabs and filters
|
||||||
|
activeTab: 'all', // all, platform, vendor
|
||||||
|
searchQuery: '',
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
async init() {
|
||||||
|
contentPagesLog.info('=== CONTENT PAGES MANAGER INITIALIZING ===');
|
||||||
|
|
||||||
|
// Prevent multiple initializations
|
||||||
|
if (window._contentPagesInitialized) {
|
||||||
|
contentPagesLog.warn('Content pages manager already initialized, skipping...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window._contentPagesInitialized = true;
|
||||||
|
|
||||||
|
contentPagesLog.group('Loading content pages');
|
||||||
|
await this.loadPages();
|
||||||
|
contentPagesLog.groupEnd();
|
||||||
|
|
||||||
|
contentPagesLog.info('=== CONTENT PAGES MANAGER INITIALIZATION COMPLETE ===');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Computed: Platform pages
|
||||||
|
get platformPages() {
|
||||||
|
return this.allPages.filter(page => page.is_platform_default);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Computed: Vendor pages
|
||||||
|
get vendorPages() {
|
||||||
|
return this.allPages.filter(page => page.is_vendor_override);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Computed: Filtered pages based on active tab and search
|
||||||
|
get filteredPages() {
|
||||||
|
let pages = [];
|
||||||
|
|
||||||
|
// Filter by tab
|
||||||
|
if (this.activeTab === 'platform') {
|
||||||
|
pages = this.platformPages;
|
||||||
|
} else if (this.activeTab === 'vendor') {
|
||||||
|
pages = this.vendorPages;
|
||||||
|
} else {
|
||||||
|
pages = this.allPages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by search query
|
||||||
|
if (this.searchQuery) {
|
||||||
|
const query = this.searchQuery.toLowerCase();
|
||||||
|
pages = pages.filter(page =>
|
||||||
|
page.title.toLowerCase().includes(query) ||
|
||||||
|
page.slug.toLowerCase().includes(query) ||
|
||||||
|
(page.vendor_name && page.vendor_name.toLowerCase().includes(query))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by display_order, then title
|
||||||
|
return pages.sort((a, b) => {
|
||||||
|
if (a.display_order !== b.display_order) {
|
||||||
|
return a.display_order - b.display_order;
|
||||||
|
}
|
||||||
|
return a.title.localeCompare(b.title);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Load all content pages
|
||||||
|
async loadPages() {
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
contentPagesLog.info('Fetching all content pages...');
|
||||||
|
|
||||||
|
// Fetch all pages (platform + vendor, published + unpublished)
|
||||||
|
const response = await apiClient.get('/admin/content-pages/?include_unpublished=true');
|
||||||
|
|
||||||
|
contentPagesLog.debug('API Response:', response);
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
throw new Error('Invalid API response');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle response - API returns array directly
|
||||||
|
this.allPages = Array.isArray(response) ? response : (response.data || response.items || []);
|
||||||
|
contentPagesLog.info(`Loaded ${this.allPages.length} pages`);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
contentPagesLog.error('Error loading content pages:', err);
|
||||||
|
this.error = err.message || 'Failed to load content pages';
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delete a page
|
||||||
|
async deletePage(page) {
|
||||||
|
if (!confirm(`Are you sure you want to delete "${page.title}"?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
contentPagesLog.info(`Deleting page: ${page.id}`);
|
||||||
|
|
||||||
|
await apiClient.delete(`/admin/content-pages/${page.id}`);
|
||||||
|
|
||||||
|
// Remove from local array
|
||||||
|
this.allPages = this.allPages.filter(p => p.id !== page.id);
|
||||||
|
|
||||||
|
contentPagesLog.info('Page deleted successfully');
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
contentPagesLog.error('Error deleting page:', err);
|
||||||
|
alert(`Failed to delete page: ${err.message}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Format date helper
|
||||||
|
formatDate(dateString) {
|
||||||
|
if (!dateString) return '—';
|
||||||
|
|
||||||
|
const date = new Date(dateString);
|
||||||
|
const now = new Date();
|
||||||
|
const diffMs = now - date;
|
||||||
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (diffDays === 0) {
|
||||||
|
return 'Today';
|
||||||
|
} else if (diffDays === 1) {
|
||||||
|
return 'Yesterday';
|
||||||
|
} else if (diffDays < 7) {
|
||||||
|
return `${diffDays} days ago`;
|
||||||
|
} else {
|
||||||
|
return date.toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
154
static/admin/js/platform-homepage.js
Normal file
154
static/admin/js/platform-homepage.js
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
// static/admin/js/platform-homepage.js
|
||||||
|
|
||||||
|
// Use centralized logger
|
||||||
|
const platformHomepageLog = window.LogConfig.loggers.platformHomepage || window.LogConfig.createLogger('platformHomepage');
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// PLATFORM HOMEPAGE MANAGER FUNCTION
|
||||||
|
// ============================================
|
||||||
|
function platformHomepageManager() {
|
||||||
|
return {
|
||||||
|
// Inherit base layout functionality from init-alpine.js
|
||||||
|
...data(),
|
||||||
|
|
||||||
|
// Page identifier for sidebar active state
|
||||||
|
currentPage: 'platform-homepage',
|
||||||
|
|
||||||
|
// Platform homepage specific state
|
||||||
|
page: null,
|
||||||
|
loading: false,
|
||||||
|
saving: false,
|
||||||
|
error: null,
|
||||||
|
successMessage: null,
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
async init() {
|
||||||
|
platformHomepageLog.info('=== PLATFORM HOMEPAGE MANAGER INITIALIZING ===');
|
||||||
|
|
||||||
|
// Prevent multiple initializations
|
||||||
|
if (window._platformHomepageInitialized) {
|
||||||
|
platformHomepageLog.warn('Platform homepage manager already initialized, skipping...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window._platformHomepageInitialized = true;
|
||||||
|
|
||||||
|
platformHomepageLog.group('Loading platform homepage');
|
||||||
|
await this.loadPlatformHomepage();
|
||||||
|
platformHomepageLog.groupEnd();
|
||||||
|
|
||||||
|
platformHomepageLog.info('=== PLATFORM HOMEPAGE MANAGER INITIALIZATION COMPLETE ===');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Load platform homepage from API
|
||||||
|
async loadPlatformHomepage() {
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
platformHomepageLog.info('Fetching platform homepage...');
|
||||||
|
|
||||||
|
// Fetch all platform pages
|
||||||
|
const response = await apiClient.get('/admin/content-pages/platform?include_unpublished=true');
|
||||||
|
|
||||||
|
platformHomepageLog.debug('API Response:', response);
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
throw new Error('Invalid API response');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle response - API returns array directly
|
||||||
|
const pages = Array.isArray(response) ? response : (response.data || response.items || []);
|
||||||
|
|
||||||
|
// Find the platform_homepage page
|
||||||
|
const homepage = pages.find(page => page.slug === 'platform_homepage');
|
||||||
|
|
||||||
|
if (!homepage) {
|
||||||
|
platformHomepageLog.warn('Platform homepage not found, creating default...');
|
||||||
|
// Initialize with default values
|
||||||
|
this.page = {
|
||||||
|
id: null,
|
||||||
|
slug: 'platform_homepage',
|
||||||
|
title: 'Welcome to Our Multi-Vendor Marketplace',
|
||||||
|
content: '<p>Connect vendors with customers worldwide. Build your online store and reach millions of shoppers.</p>',
|
||||||
|
template: 'default',
|
||||||
|
content_format: 'html',
|
||||||
|
meta_description: 'Leading multi-vendor marketplace platform. Connect with thousands of vendors and discover millions of products.',
|
||||||
|
meta_keywords: 'marketplace, multi-vendor, e-commerce, online shopping',
|
||||||
|
is_published: false,
|
||||||
|
show_in_header: false,
|
||||||
|
show_in_footer: false,
|
||||||
|
display_order: 0
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.page = { ...homepage };
|
||||||
|
platformHomepageLog.info('Platform homepage loaded:', this.page);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
platformHomepageLog.error('Error loading platform homepage:', err);
|
||||||
|
this.error = err.message || 'Failed to load platform homepage';
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Save platform homepage
|
||||||
|
async savePage() {
|
||||||
|
if (this.saving) return;
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
this.error = null;
|
||||||
|
this.successMessage = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
platformHomepageLog.info('Saving platform homepage...');
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
slug: 'platform_homepage',
|
||||||
|
title: this.page.title,
|
||||||
|
content: this.page.content,
|
||||||
|
content_format: this.page.content_format || 'html',
|
||||||
|
template: this.page.template,
|
||||||
|
meta_description: this.page.meta_description,
|
||||||
|
meta_keywords: this.page.meta_keywords,
|
||||||
|
is_published: this.page.is_published,
|
||||||
|
show_in_header: false, // Homepage never in header
|
||||||
|
show_in_footer: false, // Homepage never in footer
|
||||||
|
display_order: 0,
|
||||||
|
vendor_id: null // Platform default
|
||||||
|
};
|
||||||
|
|
||||||
|
platformHomepageLog.debug('Payload:', payload);
|
||||||
|
|
||||||
|
let response;
|
||||||
|
if (this.page.id) {
|
||||||
|
// Update existing page
|
||||||
|
response = await apiClient.put(`/admin/content-pages/${this.page.id}`, payload);
|
||||||
|
platformHomepageLog.info('Platform homepage updated');
|
||||||
|
} else {
|
||||||
|
// Create new page
|
||||||
|
response = await apiClient.post('/admin/content-pages/platform', payload);
|
||||||
|
platformHomepageLog.info('Platform homepage created');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
// Handle response - API returns object directly
|
||||||
|
const pageData = response.data || response;
|
||||||
|
this.page = { ...pageData };
|
||||||
|
this.successMessage = 'Platform homepage saved successfully!';
|
||||||
|
|
||||||
|
// Clear success message after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
platformHomepageLog.error('Error saving platform homepage:', err);
|
||||||
|
this.error = err.message || 'Failed to save platform homepage';
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user