refactor: rename shop to storefront for consistency
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>
This commit is contained in:
209
app/templates/shared/macros/storefront/add-to-cart.html
Normal file
209
app/templates/shared/macros/storefront/add-to-cart.html
Normal file
@@ -0,0 +1,209 @@
|
||||
{#
|
||||
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 %}
|
||||
Reference in New Issue
Block a user