diff --git a/app/api/v1/admin/content_pages.py b/app/api/v1/admin/content_pages.py index 2881973e..28d6c1b8 100644 --- a/app/api/v1/admin/content_pages.py +++ b/app/api/v1/admin/content_pages.py @@ -32,6 +32,7 @@ class ContentPageCreate(BaseModel): title: str = Field(..., max_length=200, description="Page title") content: str = Field(..., description="HTML or Markdown content") 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_keywords: Optional[str] = Field(None, max_length=300, description="SEO keywords") is_published: bool = Field(default=False, description="Publish immediately") @@ -46,6 +47,7 @@ class ContentPageUpdate(BaseModel): title: Optional[str] = Field(None, max_length=200) content: 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_keywords: Optional[str] = Field(None, max_length=300) is_published: Optional[bool] = None @@ -120,6 +122,7 @@ def create_platform_page( content=page_data.content, vendor_id=None, # Platform default content_format=page_data.content_format, + template=page_data.template, meta_description=page_data.meta_description, meta_keywords=page_data.meta_keywords, is_published=page_data.is_published, @@ -202,6 +205,7 @@ def update_page( title=page_data.title, content=page_data.content, content_format=page_data.content_format, + template=page_data.template, meta_description=page_data.meta_description, meta_keywords=page_data.meta_keywords, is_published=page_data.is_published, diff --git a/app/routes/admin_pages.py b/app/routes/admin_pages.py index 0a0034b7..50544baf 100644 --- a/app/routes/admin_pages.py +++ b/app/routes/admin_pages.py @@ -21,6 +21,10 @@ Routes: - GET /users → User management page (auth required) - GET /imports → Import history 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 @@ -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 # ============================================================================ diff --git a/app/services/content_page_service.py b/app/services/content_page_service.py index 94aa7f1b..beb15fe4 100644 --- a/app/services/content_page_service.py +++ b/app/services/content_page_service.py @@ -176,6 +176,7 @@ class ContentPageService: content: str, vendor_id: Optional[int] = None, content_format: str = "html", + template: str = "default", meta_description: Optional[str] = None, meta_keywords: Optional[str] = None, is_published: bool = False, @@ -194,6 +195,7 @@ class ContentPageService: content: HTML or Markdown content vendor_id: Vendor ID (None for platform default) content_format: "html" or "markdown" + template: Template name for homepage/landing pages (default, minimal, modern, etc.) meta_description: SEO description meta_keywords: SEO keywords is_published: Publish immediately @@ -211,6 +213,7 @@ class ContentPageService: title=title, content=content, content_format=content_format, + template=template, meta_description=meta_description, meta_keywords=meta_keywords, is_published=is_published, @@ -236,6 +239,7 @@ class ContentPageService: title: Optional[str] = None, content: Optional[str] = None, content_format: Optional[str] = None, + template: Optional[str] = None, meta_description: Optional[str] = None, meta_keywords: Optional[str] = None, is_published: Optional[bool] = None, @@ -253,6 +257,7 @@ class ContentPageService: title: New title content: New content content_format: New format + template: New template name meta_description: New SEO description meta_keywords: New SEO keywords is_published: New publish status @@ -277,6 +282,8 @@ class ContentPageService: page.content = content if content_format is not None: page.content_format = content_format + if template is not None: + page.template = template if meta_description is not None: page.meta_description = meta_description if meta_keywords is not None: diff --git a/app/templates/admin/content-page-edit.html b/app/templates/admin/content-page-edit.html new file mode 100644 index 00000000..c8ee8e1b --- /dev/null +++ b/app/templates/admin/content-page-edit.html @@ -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 %} + +
+ Create a new platform default or vendor-specific page + Modify an existing content page +
+Loading page...
+Error
+ ++ Manage platform defaults and vendor-specific content pages +
+Loading pages...
+Error loading pages
+ +| Page | +Slug | +Type | +Status | +Navigation | +Updated | +Actions | +
|---|---|---|---|---|---|---|
|
+
+
+
+ + Vendor: + + |
+
+
+
+
+ |
+
+
+ + + | + + ++ + | + + +
+
+ Header
+ Footer
+ —
+
+ |
+
+
+ + + + |
+
+
+
+
+
+
+ |
+
+ No pages match your search: "" +
++ No vendor-specific pages have been created yet. +
+ + + Create First Page + ++ Content Management +
++ Content Management +
++ Manage your platform's main landing page at localhost:8000 +
+Loading homepage...
+Error loading homepage
+ ++ Last updated: {{ page.updated_at.strftime('%B %d, %Y') }} +
++ {% 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 %} +
+ + {% if page.slug == 'about' %} + Contact Sales + {% elif page.slug == 'contact' %} + Send Us a Message + {% endif %} + ++ Connect vendors with customers worldwide. Build your online store and reach millions of shoppers. +
+ {% endif %} + + ++ Everything you need to launch and grow your online business +
++ Optimized for speed and performance. Your store loads in milliseconds. +
++ Enterprise-grade security with 99.9% uptime guarantee. +
++ Brand your store with custom themes, colors, and layouts. +
++ Discover amazing shops from around the world +
++ Join thousands of vendors already selling on our platform +
+ ++ The simplest way to launch your online store and connect with customers worldwide. +
+ {% endif %} + + + Get Started + ++ Lightning-fast performance optimized for conversions +
++ Enterprise-grade security for your peace of mind +
++ Fully customizable to match your brand identity +
++ Launch a stunning online marketplace in minutes. No coding required. Scale effortlessly. +
+ {% endif %} + + + + {# Stats #} ++ Powerful features to help you succeed in the digital marketplace +
++ Optimized for performance with sub-second page loads and instant search results. +
++ Enterprise-grade encryption and security measures to protect your business. +
++ Brand your store with custom themes, colors, fonts, and layouts. +
++ Powerful analytics to track sales, customer behavior, and growth metrics. +
++ Beautiful, responsive design that works perfectly on all devices. +
++ Round-the-clock customer support to help you succeed at every step. +
++ Join thousands of successful vendors on our platform +
+ + + ++ No credit card required · Free 14-day trial · Cancel anytime +
+Your custom content...
", + "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": "...
", + "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="Content...
", + 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="Paragraph text...
+Your custom homepage content...
' +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="...
", + 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="+ Welcome to our multi-vendor marketplace platform. + Connect with thousands of vendors and discover amazing products. +
++ Join our growing community of successful online businesses today. +
+``` + +### About Us Content + +```html +We're democratizing e-commerce...
+ +Contact our sales team...
+ +We offer flexible pricing...
+``` + +--- + +## 🎨 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 %} + + {% 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 diff --git a/main.py b/main.py index 7262407c..4f306a30 100644 --- a/main.py +++ b/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 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) # Log all registered routes diff --git a/mkdocs.yml b/mkdocs.yml index 54e51eb3..b0e3ace2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,6 +21,7 @@ nav: - Complete Setup Guide: getting-started/DATABASE_SETUP_GUIDE.md - Quick Reference: getting-started/DATABASE_QUICK_REFERENCE.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 # ============================================ @@ -149,6 +150,7 @@ nav: - Content Management System: - Overview: features/content-management-system.md - Implementation Guide: features/cms-implementation-guide.md + - Platform Homepage: features/platform-homepage.md - Vendor Landing Pages: features/vendor-landing-pages.md # ============================================ diff --git a/scripts/create_platform_pages.py b/scripts/create_platform_pages.py new file mode 100755 index 00000000..e757beb4 --- /dev/null +++ b/scripts/create_platform_pages.py @@ -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=""" ++ Connect vendors with customers worldwide. Build your online store and reach millions of shoppers. +
++ Our platform empowers entrepreneurs to launch their own branded e-commerce stores + with minimal effort and maximum impact. +
+ """, + 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=""" ++ We're on a mission to democratize e-commerce by providing powerful, + easy-to-use tools for entrepreneurs worldwide. +
+ ++ 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. +
+ ++ Contact our sales team to get started. We'll set up your account and provide + you with everything you need to launch your store. +
+ ++ Most vendors can launch their store in less than 24 hours. Our team will guide + you through the setup process step by step. +
+ ++ 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. +
+ ++ Payments are processed weekly, with funds typically reaching your account + within 2-3 business days. +
+ ++ Yes! Our platform supports full theme customization including colors, fonts, + logos, and layouts. Make your store truly yours. +
+ ++ We offer 24/7 email support for all vendors, with priority phone support + available for enterprise plans. +
+ ++ 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. +
+ ++ Yes, we support integrations with popular payment gateways, shipping providers, + and marketing tools. +
+ """, + 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=""" ++ 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. +
+ +
+ 123 Business Street, Suite 100
+ San Francisco, CA 94102
+ United States
+
+ Interested in launching your store on our platform?
+ Email: sales@marketplace.com
+
+ Need help with your store?
+ Email: support@marketplace.com
+ 24/7 email support for all vendors
+
+ For media inquiries and press releases:
+ Email: press@marketplace.com
+
Last updated: January 1, 2024
+ ++ By accessing and using this marketplace platform, you accept and agree to be + bound by the terms and provisions of this agreement. +
+ ++ Permission is granted to temporarily access the materials on our platform for + personal, non-commercial transitory viewing only. +
+ +You may not use our platform to:
++ We reserve the right to terminate or suspend your account at any time for + violation of these terms. +
+ ++ In no event shall our company be liable for any damages arising out of the + use or inability to use our platform. +
+ ++ We reserve the right to modify these terms at any time. We will notify users + of any changes via email. +
+ ++ If you have any questions about these Terms, please contact us at + legal@marketplace.com. +
+ """, + 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=""" +Last updated: January 1, 2024
+ +We collect information you provide directly to us, including:
+We use the information we collect to:
++ We do not sell your personal information. We may share information with: +
++ We implement appropriate security measures to protect your personal information. + However, no method of transmission over the internet is 100% secure. +
+ +You have the right to:
++ 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. +
+ ++ 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. +
+ ++ If you have questions about this Privacy Policy, please contact us at + privacy@marketplace.com. +
+ """, + 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() diff --git a/static/admin/js/content-page-edit.js b/static/admin/js/content-page-edit.js new file mode 100644 index 00000000..1e769a1e --- /dev/null +++ b/static/admin/js/content-page-edit.js @@ -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; + } + } + }; +} diff --git a/static/admin/js/content-pages.js b/static/admin/js/content-pages.js new file mode 100644 index 00000000..257a3b67 --- /dev/null +++ b/static/admin/js/content-pages.js @@ -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' + }); + } + } + }; +} diff --git a/static/admin/js/platform-homepage.js b/static/admin/js/platform-homepage.js new file mode 100644 index 00000000..14c86f4e --- /dev/null +++ b/static/admin/js/platform-homepage.js @@ -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: 'Connect vendors with customers worldwide. Build your online store and reach millions of shoppers.
', + 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; + } + } + }; +}