Files
orion/app/templates/admin/vendor-theme.html
Samir Boulahtit bb2e5fd260 refactor: standardize admin templates with shared macros
Migrate all admin templates to use standardized components:
- Use tabs macros (tabs_nav, tab_button) for tab navigation
- Use number_stepper for quantity/numeric inputs
- Use headers macros for consistent page layouts
- Use modals macros for dialog components

Affected pages: dashboard, settings, logs, content-pages, companies,
vendors, users, imports, marketplace, code-quality, and more.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 17:04:43 +01:00

450 lines
27 KiB
HTML

{# app/templates/admin/vendor-theme.html #}
{% extends "admin/base.html" %}
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
{% from 'shared/macros/headers.html' import page_header_flex %}
{% block title %}Theme Editor - {{ vendor_code }}{% endblock %}
{# ✅ CRITICAL: Binds to adminVendorTheme() function in vendor-theme.js #}
{% block alpine_data %}adminVendorTheme(){% endblock %}
{% block content %}
{% call page_header_flex(title='Theme Editor', subtitle_var="'Customize appearance for ' + (vendor?.name || '...')") %}
<a :href="`/admin/vendors/${vendorCode}`"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-300 transition-colors duration-150 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:shadow-outline-gray">
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
Back to Vendor
</a>
{% endcall %}
{{ loading_state('Loading theme...') }}
{{ error_state('Error', show_condition='error && !loading') }}
<!-- Main Content -->
<div x-show="!loading" class="grid gap-6 mb-8 md:grid-cols-3">
<!-- Theme Configuration Form (2 columns) -->
<div class="md:col-span-2 space-y-6">
<!-- Theme Presets -->
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('palette', 'inline w-5 h-5 mr-2')"></span>
Choose a Preset
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
Start with a pre-designed theme, then customize it to match your brand.
</p>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
<!-- Default Preset -->
<button @click="applyPreset('default')"
:disabled="saving"
:class="themeData.theme_name === 'default' ? 'ring-2 ring-purple-500' : ''"
class="p-3 text-sm font-medium bg-white border-2 border-gray-200 rounded-lg hover:border-purple-300 transition-all disabled:opacity-50 dark:bg-gray-900 dark:border-gray-700">
<div class="flex items-center justify-center space-x-2 mb-2">
<div class="w-4 h-4 rounded bg-indigo-500"></div>
<div class="w-4 h-4 rounded bg-purple-500"></div>
<div class="w-4 h-4 rounded bg-pink-500"></div>
</div>
<p class="text-gray-700 dark:text-gray-300">Default</p>
</button>
<!-- Modern Preset -->
<button @click="applyPreset('modern')"
:disabled="saving"
:class="themeData.theme_name === 'modern' ? 'ring-2 ring-purple-500' : ''"
class="p-3 text-sm font-medium bg-white border-2 border-gray-200 rounded-lg hover:border-purple-300 transition-all disabled:opacity-50 dark:bg-gray-900 dark:border-gray-700">
<div class="flex items-center justify-center space-x-2 mb-2">
<div class="w-4 h-4 rounded bg-indigo-500"></div>
<div class="w-4 h-4 rounded bg-purple-600"></div>
<div class="w-4 h-4 rounded bg-pink-500"></div>
</div>
<p class="text-gray-700 dark:text-gray-300">Modern</p>
</button>
<!-- Classic Preset -->
<button @click="applyPreset('classic')"
:disabled="saving"
:class="themeData.theme_name === 'classic' ? 'ring-2 ring-purple-500' : ''"
class="p-3 text-sm font-medium bg-white border-2 border-gray-200 rounded-lg hover:border-blue-300 transition-all disabled:opacity-50 dark:bg-gray-900 dark:border-gray-700">
<div class="flex items-center justify-center space-x-2 mb-2">
<div class="w-4 h-4 rounded bg-blue-800"></div>
<div class="w-4 h-4 rounded bg-purple-700"></div>
<div class="w-4 h-4 rounded bg-red-600"></div>
</div>
<p class="text-gray-700 dark:text-gray-300">Classic</p>
</button>
<!-- Minimal Preset -->
<button @click="applyPreset('minimal')"
:disabled="saving"
:class="themeData.theme_name === 'minimal' ? 'ring-2 ring-purple-500' : ''"
class="p-3 text-sm font-medium bg-white border-2 border-gray-200 rounded-lg hover:border-gray-400 transition-all disabled:opacity-50 dark:bg-gray-900 dark:border-gray-700">
<div class="flex items-center justify-center space-x-2 mb-2">
<div class="w-4 h-4 rounded bg-black"></div>
<div class="w-4 h-4 rounded bg-gray-600"></div>
<div class="w-4 h-4 rounded bg-gray-400"></div>
</div>
<p class="text-gray-700 dark:text-gray-300">Minimal</p>
</button>
<!-- Vibrant Preset -->
<button @click="applyPreset('vibrant')"
:disabled="saving"
:class="themeData.theme_name === 'vibrant' ? 'ring-2 ring-purple-500' : ''"
class="p-3 text-sm font-medium bg-white border-2 border-gray-200 rounded-lg hover:border-orange-300 transition-all disabled:opacity-50 dark:bg-gray-900 dark:border-gray-700">
<div class="flex items-center justify-center space-x-2 mb-2">
<div class="w-4 h-4 rounded bg-orange-500"></div>
<div class="w-4 h-4 rounded bg-red-500"></div>
<div class="w-4 h-4 rounded bg-purple-600"></div>
</div>
<p class="text-gray-700 dark:text-gray-300">Vibrant</p>
</button>
<!-- Elegant Preset -->
<button @click="applyPreset('elegant')"
:disabled="saving"
:class="themeData.theme_name === 'elegant' ? 'ring-2 ring-purple-500' : ''"
class="p-3 text-sm font-medium bg-white border-2 border-gray-200 rounded-lg hover:border-gray-400 transition-all disabled:opacity-50 dark:bg-gray-900 dark:border-gray-700">
<div class="flex items-center justify-center space-x-2 mb-2">
<div class="w-4 h-4 rounded bg-gray-500"></div>
<div class="w-4 h-4 rounded bg-gray-700"></div>
<div class="w-4 h-4 rounded bg-amber-600"></div>
</div>
<p class="text-gray-700 dark:text-gray-300">Elegant</p>
</button>
<!-- Nature Preset -->
<button @click="applyPreset('nature')"
:disabled="saving"
:class="themeData.theme_name === 'nature' ? 'ring-2 ring-purple-500' : ''"
class="p-3 text-sm font-medium bg-white border-2 border-gray-200 rounded-lg hover:border-green-300 transition-all disabled:opacity-50 dark:bg-gray-900 dark:border-gray-700">
<div class="flex items-center justify-center space-x-2 mb-2">
<div class="w-4 h-4 rounded bg-green-600"></div>
<div class="w-4 h-4 rounded bg-emerald-500"></div>
<div class="w-4 h-4 rounded bg-amber-500"></div>
</div>
<p class="text-gray-700 dark:text-gray-300">Nature</p>
</button>
</div>
</div>
<!-- Colors Section -->
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('color-swatch', 'inline w-5 h-5 mr-2')"></span>
Colors
</h3>
<div class="grid gap-4 md:grid-cols-2">
<!-- Primary Color -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Primary Color</span>
<p class="text-xs text-gray-500 mb-2">Main brand color for buttons and links</p>
<div class="flex items-center mt-1 space-x-2">
<input type="color"
x-model="themeData.colors.primary"
class="h-10 w-20 border border-gray-300 rounded cursor-pointer dark:border-gray-600">
<input type="text"
x-model="themeData.colors.primary"
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input rounded">
</div>
</label>
<!-- Secondary Color -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Secondary Color</span>
<p class="text-xs text-gray-500 mb-2">Supporting color for accents</p>
<div class="flex items-center mt-1 space-x-2">
<input type="color"
x-model="themeData.colors.secondary"
class="h-10 w-20 border border-gray-300 rounded cursor-pointer dark:border-gray-600">
<input type="text"
x-model="themeData.colors.secondary"
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input rounded">
</div>
</label>
<!-- Accent Color -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Accent Color</span>
<p class="text-xs text-gray-500 mb-2">Call-to-action and highlights</p>
<div class="flex items-center mt-1 space-x-2">
<input type="color"
x-model="themeData.colors.accent"
class="h-10 w-20 border border-gray-300 rounded cursor-pointer dark:border-gray-600">
<input type="text"
x-model="themeData.colors.accent"
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input rounded">
</div>
</label>
<!-- Background Color -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Background Color</span>
<p class="text-xs text-gray-500 mb-2">Page background</p>
<div class="flex items-center mt-1 space-x-2">
<input type="color"
x-model="themeData.colors.background"
class="h-10 w-20 border border-gray-300 rounded cursor-pointer dark:border-gray-600">
<input type="text"
x-model="themeData.colors.background"
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input rounded">
</div>
</label>
<!-- Text Color -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Text Color</span>
<p class="text-xs text-gray-500 mb-2">Primary text color</p>
<div class="flex items-center mt-1 space-x-2">
<input type="color"
x-model="themeData.colors.text"
class="h-10 w-20 border border-gray-300 rounded cursor-pointer dark:border-gray-600">
<input type="text"
x-model="themeData.colors.text"
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input rounded">
</div>
</label>
<!-- Border Color -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Border Color</span>
<p class="text-xs text-gray-500 mb-2">Borders and dividers</p>
<div class="flex items-center mt-1 space-x-2">
<input type="color"
x-model="themeData.colors.border"
class="h-10 w-20 border border-gray-300 rounded cursor-pointer dark:border-gray-600">
<input type="text"
x-model="themeData.colors.border"
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input rounded">
</div>
</label>
</div>
</div>
<!-- Typography Section -->
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('document', 'inline w-5 h-5 mr-2')"></span>
Typography
</h3>
<div class="grid gap-4 md:grid-cols-2">
<!-- Heading Font -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Heading Font</span>
<p class="text-xs text-gray-500 mb-2">For titles and headings</p>
<select x-model="themeData.fonts.heading"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-select rounded">
<option value="Inter, sans-serif">Inter (Modern)</option>
<option value="Roboto, sans-serif">Roboto (Clean)</option>
<option value="Poppins, sans-serif">Poppins (Friendly)</option>
<option value="Playfair Display, serif">Playfair Display (Elegant)</option>
<option value="Merriweather, serif">Merriweather (Classic)</option>
<option value="Georgia, serif">Georgia (Traditional)</option>
<option value="Helvetica, sans-serif">Helvetica (Minimal)</option>
<option value="Montserrat, sans-serif">Montserrat (Bold)</option>
</select>
</label>
<!-- Body Font -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Body Font</span>
<p class="text-xs text-gray-500 mb-2">For body text and content</p>
<select x-model="themeData.fonts.body"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-select rounded">
<option value="Inter, sans-serif">Inter (Modern)</option>
<option value="Roboto, sans-serif">Roboto (Clean)</option>
<option value="Open Sans, sans-serif">Open Sans (Readable)</option>
<option value="Lato, sans-serif">Lato (Friendly)</option>
<option value="Arial, sans-serif">Arial (Universal)</option>
<option value="Georgia, serif">Georgia (Traditional)</option>
<option value="Helvetica, sans-serif">Helvetica (Minimal)</option>
</select>
</label>
</div>
</div>
<!-- Layout Section -->
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('view-grid', 'inline w-5 h-5 mr-2')"></span>
Layout
</h3>
<div class="grid gap-4 md:grid-cols-3">
<!-- Product Layout Style -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Product Layout</span>
<select x-model="themeData.layout.style"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-select rounded">
<option value="grid">Grid</option>
<option value="list">List</option>
<option value="masonry">Masonry</option>
</select>
</label>
<!-- Header Style -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Header Style</span>
<select x-model="themeData.layout.header"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-select rounded">
<option value="fixed">Fixed</option>
<option value="static">Static</option>
<option value="transparent">Transparent</option>
</select>
</label>
<!-- Product Card Style -->
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400 font-medium">Product Card</span>
<select x-model="themeData.layout.product_card"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-select rounded">
<option value="modern">Modern</option>
<option value="classic">Classic</option>
<option value="minimal">Minimal</option>
</select>
</label>
</div>
</div>
<!-- Custom CSS Section -->
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('code', 'inline w-5 h-5 mr-2')"></span>
Advanced: Custom CSS
</h3>
<label class="block text-sm">
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">
Add custom CSS rules for advanced styling (use with caution)
</p>
<textarea x-model="themeData.custom_css"
rows="6"
placeholder=".my-custom-class {
color: red;
font-weight: bold;
}"
class="block w-full mt-1 text-sm font-mono dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-textarea rounded"></textarea>
</label>
</div>
<!-- Action Buttons -->
<div class="flex justify-between items-center">
<button @click="resetToDefault()"
:disabled="saving"
class="px-4 py-2 text-sm font-medium leading-5 text-red-700 transition-colors duration-150 bg-white border border-red-300 rounded-lg hover:bg-red-50 focus:outline-none disabled:opacity-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-600">
<span x-html="$icon('refresh', 'inline w-4 h-4 mr-2')"></span>
Reset to Default
</button>
<button @click="saveTheme()"
:disabled="saving"
class="flex items-center 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 disabled:opacity-50">
<span x-show="!saving">
<span x-html="$icon('save', 'inline w-4 h-4 mr-2')"></span>
Save Theme
</span>
<span x-show="saving" class="flex items-center">
<span x-html="$icon('spinner', 'inline w-4 h-4 mr-2 animate-spin')"></span>
Saving...
</span>
</button>
</div>
</div>
<!-- Preview Panel (1 column) -->
<div class="md:col-span-1">
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800 sticky top-6">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('eye', 'inline w-5 h-5 mr-2')"></span>
Preview
</h3>
<!-- Theme Preview -->
<div class="space-y-4">
<!-- Current Theme Name -->
<div class="p-3 bg-purple-50 dark:bg-purple-900 dark:bg-opacity-20 rounded-lg">
<p class="text-xs font-semibold text-purple-800 dark:text-purple-200 mb-1">ACTIVE THEME</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300 capitalize" x-text="themeData.theme_name"></p>
</div>
<!-- Colors Preview -->
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 mb-2">COLORS</p>
<div class="grid grid-cols-3 gap-2">
<div class="text-center">
<div class="h-12 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"
:style="`background-color: ${themeData.colors.primary}`"></div>
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">Primary</p>
</div>
<div class="text-center">
<div class="h-12 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"
:style="`background-color: ${themeData.colors.secondary}`"></div>
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">Secondary</p>
</div>
<div class="text-center">
<div class="h-12 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"
:style="`background-color: ${themeData.colors.accent}`"></div>
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">Accent</p>
</div>
</div>
</div>
<!-- Typography Preview -->
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 mb-2">TYPOGRAPHY</p>
<div class="space-y-2 p-3 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-lg font-bold" :style="`font-family: ${themeData.fonts.heading}`">
Heading Font
</p>
<p class="text-sm" :style="`font-family: ${themeData.fonts.body}`">
This is body text font example. It will be used for paragraphs and descriptions.
</p>
</div>
</div>
<!-- Button Preview -->
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 mb-2">BUTTONS</p>
<button class="px-4 py-2 text-sm font-medium text-white rounded-lg w-full shadow-sm"
:style="`background-color: ${themeData.colors.primary}`">
Primary Button
</button>
</div>
<!-- Layout Preview -->
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 mb-2">LAYOUT</p>
<div class="text-xs space-y-1 p-3 bg-gray-50 dark:bg-gray-900 rounded-lg">
<p class="text-gray-700 dark:text-gray-300">
<span class="font-semibold">Product Layout:</span>
<span class="capitalize" x-text="themeData.layout.style"></span>
</p>
<p class="text-gray-700 dark:text-gray-300">
<span class="font-semibold">Header:</span>
<span class="capitalize" x-text="themeData.layout.header"></span>
</p>
<p class="text-gray-700 dark:text-gray-300">
<span class="font-semibold">Product Card:</span>
<span class="capitalize" x-text="themeData.layout.product_card"></span>
</p>
</div>
</div>
<!-- Preview Link -->
<div class="pt-4 border-t border-gray-200 dark:border-gray-700">
<a :href="`http://${vendor?.subdomain}.localhost:8000`"
target="_blank"
class="flex items-center justify-center px-4 py-2 text-sm font-medium text-purple-700 bg-purple-50 border border-purple-300 rounded-lg hover:bg-purple-100 dark:bg-purple-900 dark:bg-opacity-20 dark:text-purple-300 dark:border-purple-700 transition-colors">
<span x-html="$icon('external-link', 'w-4 h-4 mr-2')"></span>
View Live Shop
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/vendor-theme.js') }}"></script>
{% endblock %}