- Add routes for vendor content pages list, create, and edit - Create content-pages.html with tabs for Platform Defaults and My Pages - Create content-page-edit.html for creating/editing pages - Add JavaScript for both list and edit views - Add "Content Pages" link to vendor sidebar under new "Shop" section - Add show_in_legal field to vendor content pages API schemas - Platform Defaults tab shows pages that can be overridden - My Pages tab shows vendor's custom pages and overrides Vendors can now: - View platform default pages and override them with custom content - Create entirely new custom pages for their shop - Manage navigation placement (header, footer, legal) - Publish/unpublish pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
284 lines
17 KiB
HTML
284 lines
17 KiB
HTML
{# app/templates/vendor/content-pages.html #}
|
|
{% extends "vendor/base.html" %}
|
|
{% from 'shared/macros/headers.html' import page_header %}
|
|
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
|
{% from 'shared/macros/tabs.html' import tabs_inline, tab_button %}
|
|
|
|
{% block title %}Content Pages{% endblock %}
|
|
|
|
{% block alpine_data %}vendorContentPagesManager(){% endblock %}
|
|
|
|
{% block content %}
|
|
{{ page_header('Content Pages', subtitle='Customize your shop pages or create new ones', action_label='Create Page', action_url='/vendor/' + vendor_code + '/content-pages/create') }}
|
|
|
|
{{ loading_state('Loading pages...') }}
|
|
|
|
{{ error_state('Error loading pages') }}
|
|
|
|
<!-- Tabs and Info -->
|
|
<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 -->
|
|
{% call tabs_inline() %}
|
|
{{ tab_button('platform', 'Platform Defaults', count_var='platformPages.length') }}
|
|
{{ tab_button('custom', 'My Pages', count_var='customPages.length') }}
|
|
{% endcall %}
|
|
|
|
<!-- 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>
|
|
|
|
<!-- Platform Defaults Tab -->
|
|
<div x-show="!loading && activeTab === 'platform'" class="space-y-4">
|
|
<!-- Info Banner -->
|
|
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
|
<div class="flex">
|
|
<span x-html="$icon('information-circle', 'w-5 h-5 text-blue-500 mr-3 flex-shrink-0 mt-0.5')"></span>
|
|
<div>
|
|
<h4 class="text-sm font-medium text-blue-800 dark:text-blue-200">Platform Default Pages</h4>
|
|
<p class="text-sm text-blue-700 dark:text-blue-300 mt-1">
|
|
These pages are provided by the platform. You can override any of them with your own custom content.
|
|
Your overridden version will be shown to your customers instead of the default.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Platform Pages Table -->
|
|
<div x-show="filteredPlatformPages.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">URL</th>
|
|
<th class="px-4 py-3">Navigation</th>
|
|
<th class="px-4 py-3">Status</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 filteredPlatformPages" :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-500 dark:text-gray-400">Platform Default</p>
|
|
</div>
|
|
</td>
|
|
|
|
<!-- URL/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>
|
|
|
|
<!-- Navigation -->
|
|
<td class="px-4 py-3 text-xs">
|
|
<div class="flex gap-1 flex-wrap">
|
|
<span x-show="page.show_in_header" class="px-2 py-0.5 font-medium bg-indigo-100 text-indigo-800 dark:bg-indigo-900/50 dark:text-indigo-300 rounded-full">Header</span>
|
|
<span x-show="page.show_in_footer" class="px-2 py-0.5 font-medium bg-teal-100 text-teal-800 dark:bg-teal-900/50 dark:text-teal-300 rounded-full">Footer</span>
|
|
<span x-show="page.show_in_legal" class="px-2 py-0.5 font-medium bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-300 rounded-full">Legal</span>
|
|
<span x-show="!page.show_in_header && !page.show_in_footer && !page.show_in_legal" class="text-gray-400">—</span>
|
|
</div>
|
|
</td>
|
|
|
|
<!-- Status -->
|
|
<td class="px-4 py-3 text-sm">
|
|
<span x-show="hasOverride(page.slug)" class="px-2 py-1 text-xs font-semibold bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200 rounded-full">
|
|
Overridden
|
|
</span>
|
|
<span x-show="!hasOverride(page.slug)" class="px-2 py-1 text-xs font-semibold bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 rounded-full">
|
|
Using Default
|
|
</span>
|
|
</td>
|
|
|
|
<!-- Actions -->
|
|
<td class="px-4 py-3">
|
|
<div class="flex items-center space-x-2 text-sm">
|
|
<!-- Override / Edit Override button -->
|
|
<template x-if="hasOverride(page.slug)">
|
|
<a
|
|
:href="`/vendor/${vendorCode}/content-pages/${getOverrideId(page.slug)}/edit`"
|
|
class="flex items-center justify-center px-3 py-1.5 text-sm font-medium text-purple-600 bg-purple-50 rounded-lg hover:bg-purple-100 dark:text-purple-400 dark:bg-purple-900/30 dark:hover:bg-purple-900/50 transition-colors"
|
|
>
|
|
<span x-html="$icon('edit', 'w-4 h-4 mr-1')"></span>
|
|
Edit Override
|
|
</a>
|
|
</template>
|
|
<template x-if="!hasOverride(page.slug)">
|
|
<button
|
|
@click="createOverride(page)"
|
|
class="flex items-center justify-center px-3 py-1.5 text-sm font-medium text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 dark:text-blue-400 dark:bg-blue-900/30 dark:hover:bg-blue-900/50 transition-colors"
|
|
>
|
|
<span x-html="$icon('plus', 'w-4 h-4 mr-1')"></span>
|
|
Override
|
|
</button>
|
|
</template>
|
|
<!-- Preview button -->
|
|
<a
|
|
:href="`/vendors/${vendorCode}/shop/${page.slug}`"
|
|
target="_blank"
|
|
class="flex items-center justify-center p-2 text-gray-500 rounded-lg hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 transition-colors"
|
|
title="Preview"
|
|
>
|
|
<span x-html="$icon('eye', 'w-5 h-5')"></span>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div x-show="filteredPlatformPages.length === 0 && searchQuery" class="text-center py-12 bg-white dark:bg-gray-800 rounded-lg shadow-md">
|
|
<span x-html="$icon('search', 'inline w-12 h-12 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">
|
|
No platform pages match "<span x-text="searchQuery"></span>"
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Custom Pages Tab -->
|
|
<div x-show="!loading && activeTab === 'custom'" class="space-y-4">
|
|
<!-- Info Banner -->
|
|
<div class="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4">
|
|
<div class="flex">
|
|
<span x-html="$icon('plus-circle', 'w-5 h-5 text-green-500 mr-3 flex-shrink-0 mt-0.5')"></span>
|
|
<div>
|
|
<h4 class="text-sm font-medium text-green-800 dark:text-green-200">Your Custom Pages</h4>
|
|
<p class="text-sm text-green-700 dark:text-green-300 mt-1">
|
|
Create unique pages for your shop like promotions, brand story, or special information.
|
|
These pages are exclusive to your store.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Custom Pages Table -->
|
|
<div x-show="filteredCustomPages.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">URL</th>
|
|
<th class="px-4 py-3">Navigation</th>
|
|
<th class="px-4 py-3">Status</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 filteredCustomPages" :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 x-show="page.is_vendor_override" class="text-xs text-purple-600 dark:text-purple-400">Override of platform default</p>
|
|
<p x-show="!page.is_vendor_override" class="text-xs text-green-600 dark:text-green-400">Custom page</p>
|
|
</div>
|
|
</td>
|
|
|
|
<!-- URL/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>
|
|
|
|
<!-- Navigation -->
|
|
<td class="px-4 py-3 text-xs">
|
|
<div class="flex gap-1 flex-wrap">
|
|
<span x-show="page.show_in_header" class="px-2 py-0.5 font-medium bg-indigo-100 text-indigo-800 dark:bg-indigo-900/50 dark:text-indigo-300 rounded-full">Header</span>
|
|
<span x-show="page.show_in_footer" class="px-2 py-0.5 font-medium bg-teal-100 text-teal-800 dark:bg-teal-900/50 dark:text-teal-300 rounded-full">Footer</span>
|
|
<span x-show="page.show_in_legal" class="px-2 py-0.5 font-medium bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-300 rounded-full">Legal</span>
|
|
<span x-show="!page.show_in_header && !page.show_in_footer && !page.show_in_legal" class="text-gray-400">—</span>
|
|
</div>
|
|
</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>
|
|
|
|
<!-- 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="`/vendor/${vendorCode}/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('edit', 'w-5 h-5')"></span>
|
|
</a>
|
|
<a
|
|
:href="`/vendors/${vendorCode}/shop/${page.slug}`"
|
|
target="_blank"
|
|
class="flex items-center justify-center p-2 text-gray-500 rounded-lg hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 transition-colors"
|
|
title="Preview"
|
|
>
|
|
<span x-html="$icon('eye', '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('delete', 'w-5 h-5')"></span>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div x-show="filteredCustomPages.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" x-text="searchQuery ? 'No pages found' : 'No custom pages yet'"></h3>
|
|
<p class="text-gray-500 dark:text-gray-400 mb-4" x-show="searchQuery">
|
|
No custom pages match "<span x-text="searchQuery"></span>"
|
|
</p>
|
|
<p class="text-gray-500 dark:text-gray-400 mb-4" x-show="!searchQuery">
|
|
Create your first custom page or override a platform default.
|
|
</p>
|
|
<a
|
|
:href="`/vendor/${vendorCode}/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 Page
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script src="{{ url_for('static', path='vendor/js/content-pages.js') }}"></script>
|
|
{% endblock %}
|