Rename all "shop" directories and references to "storefront" to match the API and route naming convention already in use. Renamed directories: - app/templates/shop/ → app/templates/storefront/ - static/shop/ → static/storefront/ - app/templates/shared/macros/shop/ → .../macros/storefront/ - docs/frontend/shop/ → docs/frontend/storefront/ Renamed files: - shop.css → storefront.css - shop-layout.js → storefront-layout.js Updated references in: - app/routes/storefront_pages.py (21 template references) - app/modules/cms/routes/pages/vendor.py - app/templates/storefront/base.html (static paths) - All storefront templates (extends/includes) - docs/architecture/frontend-structure.md This aligns the template/static naming with: - Route file: storefront_pages.py - API directory: app/api/v1/storefront/ - Module routes: */routes/api/storefront.py - URL paths: /storefront/* Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
210 lines
7.6 KiB
HTML
210 lines
7.6 KiB
HTML
{#
|
|
Add to Cart Components
|
|
======================
|
|
Standardized add-to-cart functionality with quantity selector.
|
|
|
|
Usage:
|
|
{% from 'shared/macros/shop/add-to-cart.html' import add_to_cart_button, add_to_cart_form %}
|
|
#}
|
|
|
|
|
|
{#
|
|
Add to Cart Button
|
|
==================
|
|
Simple add to cart button with loading state.
|
|
|
|
Parameters:
|
|
- product_id_var: Alpine.js expression for product ID (default: 'product.id')
|
|
- variant_id_var: Alpine.js expression for variant ID (default: 'selectedVariant?.id')
|
|
- quantity_var: Alpine.js variable for quantity (default: 'quantity')
|
|
- loading_var: Alpine.js variable for loading state (default: 'addingToCart')
|
|
- stock_var: Alpine.js expression for stock (default: 'product.stock')
|
|
- action: Alpine.js action to execute (default: 'addToCart()')
|
|
- size: 'sm' | 'md' | 'lg' (default: 'md')
|
|
- full_width: Make button full width (default: true)
|
|
- show_icon: Show cart icon (default: true)
|
|
- label: Button label (default: 'Add to Cart')
|
|
- loading_label: Label while loading (default: 'Adding...')
|
|
|
|
Usage:
|
|
{{ add_to_cart_button() }}
|
|
{{ add_to_cart_button(size='lg', label='Buy Now') }}
|
|
#}
|
|
{% macro add_to_cart_button(
|
|
product_id_var='product.id',
|
|
variant_id_var='selectedVariant?.id',
|
|
quantity_var='quantity',
|
|
loading_var='addingToCart',
|
|
stock_var='product.stock',
|
|
action='addToCart()',
|
|
size='md',
|
|
full_width=true,
|
|
show_icon=true,
|
|
label='Add to Cart',
|
|
loading_label='Adding...'
|
|
) %}
|
|
{% set sizes = {
|
|
'sm': {'btn': 'px-3 py-1.5 text-sm', 'icon': 'w-4 h-4'},
|
|
'md': {'btn': 'px-4 py-2.5 text-base', 'icon': 'w-5 h-5'},
|
|
'lg': {'btn': 'px-6 py-3 text-lg', 'icon': 'w-6 h-6'}
|
|
} %}
|
|
{% set s = sizes[size] %}
|
|
|
|
<button
|
|
type="button"
|
|
@click="{{ action }}"
|
|
:disabled="{{ loading_var }} || {{ stock_var }} === 0"
|
|
class="{{ s.btn }} {{ 'w-full' if full_width else '' }} font-medium text-white bg-purple-600 hover:bg-purple-700 dark:bg-purple-500 dark:hover:bg-purple-600 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
>
|
|
{# Loading Spinner #}
|
|
<span x-show="{{ loading_var }}" x-html="$icon('spinner', '{{ s.icon }}')"></span>
|
|
|
|
{# Cart Icon #}
|
|
{% if show_icon %}
|
|
<span x-show="!{{ loading_var }}" x-html="$icon('shopping-cart', '{{ s.icon }}')"></span>
|
|
{% endif %}
|
|
|
|
{# Label #}
|
|
<span x-text="{{ loading_var }} ? '{{ loading_label }}' : ({{ stock_var }} === 0 ? 'Out of Stock' : '{{ label }}')"></span>
|
|
</button>
|
|
{% endmacro %}
|
|
|
|
|
|
{#
|
|
Add to Cart Form
|
|
================
|
|
Complete add to cart section with quantity selector.
|
|
|
|
Parameters:
|
|
- product_var: Alpine.js variable for product (default: 'product')
|
|
- quantity_var: Alpine.js variable for quantity (default: 'quantity')
|
|
- loading_var: Alpine.js variable for loading state (default: 'addingToCart')
|
|
- action: Alpine.js action to execute (default: 'addToCart()')
|
|
- show_stock: Show stock status (default: true)
|
|
- size: 'sm' | 'md' | 'lg' (default: 'md')
|
|
|
|
Usage:
|
|
{{ add_to_cart_form() }}
|
|
#}
|
|
{% macro add_to_cart_form(
|
|
product_var='product',
|
|
quantity_var='quantity',
|
|
loading_var='addingToCart',
|
|
action='addToCart()',
|
|
show_stock=true,
|
|
size='md'
|
|
) %}
|
|
{% from 'shared/macros/inputs.html' import number_stepper %}
|
|
|
|
<div class="space-y-4">
|
|
{# Stock Status #}
|
|
{% if show_stock %}
|
|
<div class="flex items-center gap-2">
|
|
<span
|
|
class="inline-flex items-center gap-1 text-sm"
|
|
:class="{{ product_var }}.stock > 10 ? 'text-green-600 dark:text-green-400' : {{ product_var }}.stock > 0 ? 'text-orange-600 dark:text-orange-400' : 'text-red-600 dark:text-red-400'"
|
|
>
|
|
<span x-html="$icon({{ product_var }}.stock > 0 ? 'check-circle' : 'x-circle', 'w-4 h-4')"></span>
|
|
<span x-text="{{ product_var }}.stock > 10 ? 'In Stock' : {{ product_var }}.stock > 0 ? 'Only ' + {{ product_var }}.stock + ' left' : 'Out of Stock'"></span>
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Quantity and Add Button #}
|
|
<div class="flex items-center gap-4">
|
|
{# Quantity Selector #}
|
|
<div class="flex-shrink-0">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Quantity</label>
|
|
{{ number_stepper(model=quantity_var, min=1, max=product_var ~ '.stock', size=size, disabled_var=loading_var) }}
|
|
</div>
|
|
|
|
{# Add to Cart Button #}
|
|
<div class="flex-1">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 invisible">Action</label>
|
|
{{ add_to_cart_button(
|
|
stock_var=product_var ~ '.stock',
|
|
loading_var=loading_var,
|
|
action=action,
|
|
size=size,
|
|
full_width=true
|
|
) }}
|
|
</div>
|
|
</div>
|
|
|
|
{# Success Message #}
|
|
<div
|
|
x-show="addedToCart"
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0 transform -translate-y-2"
|
|
x-transition:enter-end="opacity-100 transform translate-y-0"
|
|
class="flex items-center gap-2 text-green-600 dark:text-green-400 text-sm"
|
|
>
|
|
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
|
<span>Added to cart!</span>
|
|
<a href="/cart" class="underline hover:no-underline">View Cart</a>
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
|
|
{#
|
|
Buy Now Button
|
|
==============
|
|
Direct checkout button (skips cart).
|
|
|
|
Parameters:
|
|
- action: Alpine.js action (default: 'buyNow()')
|
|
- loading_var: Loading state variable (default: 'buyingNow')
|
|
- stock_var: Stock expression (default: 'product.stock')
|
|
- size: 'sm' | 'md' | 'lg' (default: 'md')
|
|
- full_width: Full width button (default: true)
|
|
|
|
Usage:
|
|
{{ buy_now_button() }}
|
|
#}
|
|
{% macro buy_now_button(
|
|
action='buyNow()',
|
|
loading_var='buyingNow',
|
|
stock_var='product.stock',
|
|
size='md',
|
|
full_width=true
|
|
) %}
|
|
{% set sizes = {
|
|
'sm': {'btn': 'px-3 py-1.5 text-sm', 'icon': 'w-4 h-4'},
|
|
'md': {'btn': 'px-4 py-2.5 text-base', 'icon': 'w-5 h-5'},
|
|
'lg': {'btn': 'px-6 py-3 text-lg', 'icon': 'w-6 h-6'}
|
|
} %}
|
|
{% set s = sizes[size] %}
|
|
|
|
<button
|
|
type="button"
|
|
@click="{{ action }}"
|
|
:disabled="{{ loading_var }} || {{ stock_var }} === 0"
|
|
class="{{ s.btn }} {{ 'w-full' if full_width else '' }} font-medium text-purple-600 dark:text-purple-400 bg-purple-50 dark:bg-purple-900/30 hover:bg-purple-100 dark:hover:bg-purple-900/50 border border-purple-200 dark:border-purple-800 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
>
|
|
<span x-show="{{ loading_var }}" x-html="$icon('spinner', '{{ s.icon }}')"></span>
|
|
<span x-show="!{{ loading_var }}" x-html="$icon('lightning-bolt', '{{ s.icon }}')"></span>
|
|
<span>Buy Now</span>
|
|
</button>
|
|
{% endmacro %}
|
|
|
|
|
|
{#
|
|
Quantity Selector (Shop variant)
|
|
================================
|
|
Shop-specific quantity selector with stock validation.
|
|
|
|
Parameters:
|
|
- model: Alpine.js model for quantity (required)
|
|
- max_var: Alpine.js expression for max stock (default: 'product.stock')
|
|
- disabled_var: Alpine.js variable for disabled state (default: none)
|
|
- size: 'sm' | 'md' | 'lg' (default: 'md')
|
|
|
|
Usage:
|
|
{{ shop_quantity_selector(model='quantity', max_var='product.stock') }}
|
|
#}
|
|
{% macro shop_quantity_selector(model, max_var='product.stock', disabled_var=none, size='md') %}
|
|
{% from 'shared/macros/inputs.html' import number_stepper %}
|
|
{{ number_stepper(model=model, min=1, max=max_var, size=size, disabled_var=disabled_var, label='Quantity') }}
|
|
{% endmacro %}
|