From 2f64dba155630ce78cbf6207240b8c1747dc20cd Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sun, 7 Dec 2025 17:04:12 +0100 Subject: [PATCH] feat: add Priority 1 e-commerce shop macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New macros in shared/macros/shop/: - product-card.html: Product cards with badges, ratings, wishlist, quick-add, size variants (sm/md/lg) - product-grid.html: Responsive grid with loading skeletons, empty state - add-to-cart.html: Add to cart button/form, buy now, quantity selector - mini-cart.html: Cart icon with badge, dropdown, items, summary All components support: - Dark mode via Tailwind dark: classes - Vendor theming via CSS variables - Alpine.js integration - Accessible markup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../shared/macros/shop/add-to-cart.html | 209 +++++++++ .../shared/macros/shop/mini-cart.html | 408 ++++++++++++++++++ .../shared/macros/shop/product-card.html | 253 +++++++++++ .../shared/macros/shop/product-grid.html | 215 +++++++++ 4 files changed, 1085 insertions(+) create mode 100644 app/templates/shared/macros/shop/add-to-cart.html create mode 100644 app/templates/shared/macros/shop/mini-cart.html create mode 100644 app/templates/shared/macros/shop/product-card.html create mode 100644 app/templates/shared/macros/shop/product-grid.html diff --git a/app/templates/shared/macros/shop/add-to-cart.html b/app/templates/shared/macros/shop/add-to-cart.html new file mode 100644 index 00000000..7d35132e --- /dev/null +++ b/app/templates/shared/macros/shop/add-to-cart.html @@ -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] %} + + +{% 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 %} + +
+ {# Stock Status #} + {% if show_stock %} +
+ + + + +
+ {% endif %} + + {# Quantity and Add Button #} +
+ {# Quantity Selector #} +
+ + {{ number_stepper(model=quantity_var, min=1, max=product_var ~ '.stock', size=size, disabled_var=loading_var) }} +
+ + {# Add to Cart Button #} +
+ + {{ add_to_cart_button( + stock_var=product_var ~ '.stock', + loading_var=loading_var, + action=action, + size=size, + full_width=true + ) }} +
+
+ + {# Success Message #} +
+ + Added to cart! + View Cart +
+
+{% 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] %} + + +{% 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 %} diff --git a/app/templates/shared/macros/shop/mini-cart.html b/app/templates/shared/macros/shop/mini-cart.html new file mode 100644 index 00000000..ba64289c --- /dev/null +++ b/app/templates/shared/macros/shop/mini-cart.html @@ -0,0 +1,408 @@ +{# + Mini Cart Components + ==================== + Cart preview dropdown and cart item components. + + Usage: + {% from 'shared/macros/shop/mini-cart.html' import mini_cart, cart_icon_button, cart_item %} +#} + + +{# + Cart Icon Button + ================ + Cart icon with item count badge for header. + + Parameters: + - cart_count_var: Alpine.js expression for cart item count (default: 'cart.items.length') + - toggle_action: Alpine.js action to toggle cart dropdown (default: 'toggleCart()') + - size: 'sm' | 'md' | 'lg' (default: 'md') + + Usage: + {{ cart_icon_button() }} +#} +{% macro cart_icon_button( + cart_count_var='cart.items.length', + toggle_action='toggleCart()', + size='md' +) %} +{% set sizes = { + 'sm': {'btn': 'p-1.5', 'icon': 'w-5 h-5', 'badge': 'w-4 h-4 text-xs -top-1 -right-1'}, + 'md': {'btn': 'p-2', 'icon': 'w-6 h-6', 'badge': 'w-5 h-5 text-xs -top-1.5 -right-1.5'}, + 'lg': {'btn': 'p-2.5', 'icon': 'w-7 h-7', 'badge': 'w-6 h-6 text-sm -top-2 -right-2'} +} %} +{% set s = sizes[size] %} + + +{% endmacro %} + + +{# + Mini Cart Dropdown + ================== + Cart preview dropdown showing recent items. + + Parameters: + - cart_var: Alpine.js variable for cart object (default: 'cart') + - show_var: Alpine.js variable for dropdown visibility (default: 'showCart') + - max_items: Maximum items to show (default: 3) + - cart_url: URL to full cart page (default: '/cart') + - checkout_url: URL to checkout (default: '/checkout') + + Expected cart object structure: + { + items: [ + {id, product_id, name, image_url, price, quantity, variant_name} + ], + subtotal: number, + item_count: number + } + + Usage: + {{ mini_cart() }} +#} +{% macro mini_cart( + cart_var='cart', + show_var='showCart', + max_items=3, + cart_url='/cart', + checkout_url='/checkout' +) %} +
+ {# Header #} +
+

Shopping Cart

+

+
+ + {# Empty State #} +
+
+ +
+

Your cart is empty

+ + Continue Shopping + +
+ + {# Cart Items #} +
+ + + {# More Items Notice #} +
+ +
+
+ + {# Footer #} +
+ {# Subtotal #} +
+ Subtotal + +
+ + {# Actions #} + +
+
+{% endmacro %} + + +{# + Cart Item (Mini version) + ======================== + Compact cart item for mini cart dropdown. + + Parameters: + - cart_var: Cart variable for remove action (default: 'cart') + + Usage: + +#} +{% macro cart_item_mini(cart_var='cart') %} +
+ {# Image #} + + + + + {# Details #} +
+

+ +

+

+
+ + +
+
+ + {# Remove Button #} + +
+{% endmacro %} + + +{# + Cart Item (Full version) + ======================== + Full cart item for cart page with quantity controls. + + Parameters: + - item_var: Variable name for item (default: 'item') + - index_var: Variable name for index (default: 'index') + - show_image: Show product image (default: true) + - editable: Allow quantity editing (default: true) + + Usage: + +#} +{% macro cart_item( + item_var='item', + index_var='index', + show_image=true, + editable=true +) %} +{% from 'shared/macros/inputs.html' import number_stepper %} + +
+ {# Image #} + {% if show_image %} + + + + {% endif %} + + {# Details #} +
+
+
+

+ +

+

+

+
+ + {# Remove Button #} + +
+ + {# Quantity and Total #} +
+ {% if editable %} +
+ {{ number_stepper( + model=item_var ~ '.quantity', + min=1, + max=item_var ~ '.max_quantity', + size='sm', + label='Quantity' + ) }} +
+ {% else %} + + {% endif %} + + +
+ + {# Low Stock Warning #} +
+ + +
+
+
+{% endmacro %} + + +{# + Cart Summary + ============ + Order summary with totals and checkout button. + + Parameters: + - cart_var: Alpine.js variable for cart (default: 'cart') + - show_promo: Show promo code input (default: true) + - show_shipping: Show shipping estimate (default: true) + - checkout_url: Checkout URL (default: '/checkout') + + Expected cart structure: + { + subtotal: number, + discount: number, + shipping: number, + tax: number, + total: number, + promo_code: string | null + } + + Usage: + {{ cart_summary() }} +#} +{% macro cart_summary( + cart_var='cart', + show_promo=true, + show_shipping=true, + checkout_url='/checkout' +) %} +
+

Order Summary

+ + {# Promo Code #} + {% if show_promo %} +
+ +
+ + +
+

+
+ {% endif %} + + {# Totals #} +
+
+ Subtotal + +
+ +
+ Discount + +
+ + {% if show_shipping %} +
+ Shipping + +
+ {% endif %} + +
+ Tax + +
+ +
+
+ Total + +
+
+
+ + {# Checkout Button #} +
+ + Proceed to Checkout + + + {# Trust Badges #} +
+ + + Secure Checkout + + + + SSL Encrypted + +
+
+
+{% endmacro %} diff --git a/app/templates/shared/macros/shop/product-card.html b/app/templates/shared/macros/shop/product-card.html new file mode 100644 index 00000000..b51141df --- /dev/null +++ b/app/templates/shared/macros/shop/product-card.html @@ -0,0 +1,253 @@ +{# + Product Card + ============ + A versatile product card component for e-commerce listings. + Supports multiple sizes, badges, ratings, and quick actions. + + Parameters: + - product_var: Alpine.js variable name for the product object (default: 'product') + - size: 'sm' | 'md' | 'lg' (default: 'md') + - show_rating: Show star rating (default: true) + - show_quick_add: Show quick add to cart button (default: true) + - show_wishlist: Show wishlist heart icon (default: true) + - show_vendor: Show vendor name for marketplace (default: false) + - add_to_cart_action: Alpine.js action for add to cart (default: 'addToCart(product)') + - wishlist_action: Alpine.js action for wishlist toggle (default: 'toggleWishlist(product)') + - product_url_field: Field name for product URL (default: 'url') + - image_field: Field name for image URL (default: 'image_url') + - title_field: Field name for product title (default: 'name') + - price_field: Field name for price (default: 'price') + - sale_price_field: Field name for sale price (default: 'sale_price') + - rating_field: Field name for rating (default: 'rating') + - review_count_field: Field name for review count (default: 'review_count') + - stock_field: Field name for stock quantity (default: 'stock') + - vendor_field: Field name for vendor name (default: 'vendor_name') + + Expected product object structure: + { + id: number, + name: string, + url: string, + image_url: string, + price: number, + sale_price: number | null, + rating: number (0-5), + review_count: number, + stock: number, + is_new: boolean, + vendor_name: string (optional) + } + + Usage: + {% from 'shared/macros/shop/product-card.html' import product_card %} + + + + {# With custom settings #} + {{ product_card(product_var='featuredProduct', size='lg', show_vendor=true) }} +#} + +{% macro product_card( + product_var='product', + size='md', + show_rating=true, + show_quick_add=true, + show_wishlist=true, + show_vendor=false, + add_to_cart_action='addToCart(product)', + wishlist_action='toggleWishlist(product)', + product_url_field='url', + image_field='image_url', + title_field='name', + price_field='price', + sale_price_field='sale_price', + rating_field='rating', + review_count_field='review_count', + stock_field='stock', + vendor_field='vendor_name' +) %} +{% set sizes = { + 'sm': { + 'card': 'max-w-[200px]', + 'image': 'h-32', + 'title': 'text-sm', + 'price': 'text-sm', + 'badge': 'text-xs px-1.5 py-0.5', + 'btn': 'text-xs px-2 py-1', + 'icon': 'w-4 h-4', + 'rating': 'w-3 h-3' + }, + 'md': { + 'card': 'max-w-[280px]', + 'image': 'h-48', + 'title': 'text-base', + 'price': 'text-base', + 'badge': 'text-xs px-2 py-1', + 'btn': 'text-sm px-3 py-2', + 'icon': 'w-5 h-5', + 'rating': 'w-4 h-4' + }, + 'lg': { + 'card': 'max-w-[360px]', + 'image': 'h-64', + 'title': 'text-lg', + 'price': 'text-lg', + 'badge': 'text-sm px-2.5 py-1', + 'btn': 'text-base px-4 py-2.5', + 'icon': 'w-6 h-6', + 'rating': 'w-5 h-5' + } +} %} +{% set s = sizes[size] %} + +
+ {# Image Container #} +
+ + + + + {# Badges #} +
+ {# Sale Badge #} + + + + {# New Badge #} + + New + +
+ + {# Wishlist Button #} + {% if show_wishlist %} + + {% endif %} + + {# Out of Stock Overlay #} +
+ Out of Stock +
+
+ + {# Content #} +
+ {# Vendor Name #} + {% if show_vendor %} +

+ {% endif %} + + {# Title #} +

+ +

+ + {# Rating #} + {% if show_rating %} +
+
+ +
+ +
+ {% endif %} + + {# Price #} +
+ + +
+ + {# Quick Add Button #} + {% if show_quick_add %} + + {% endif %} +
+
+{% endmacro %} + + +{# + Product Badge + ============= + Standalone badge component for product overlays. + + Parameters: + - type: 'sale' | 'new' | 'bestseller' | 'low_stock' | 'out_of_stock' + - value: Optional value (e.g., '-20%' for sale, 'Only 3 left' for low_stock) + - size: 'sm' | 'md' | 'lg' (default: 'md') + + Usage: + {{ product_badge(type='sale', value='-20%') }} + {{ product_badge(type='new') }} + {{ product_badge(type='low_stock', value='Only 3 left') }} +#} +{% macro product_badge(type, value=none, size='md') %} +{% set badge_styles = { + 'sale': 'bg-red-500 text-white', + 'new': 'bg-green-500 text-white', + 'bestseller': 'bg-yellow-500 text-gray-900', + 'low_stock': 'bg-orange-500 text-white', + 'out_of_stock': 'bg-gray-700 text-white' +} %} +{% set badge_labels = { + 'sale': 'Sale', + 'new': 'New', + 'bestseller': 'Bestseller', + 'low_stock': 'Low Stock', + 'out_of_stock': 'Out of Stock' +} %} +{% set sizes = { + 'sm': 'text-xs px-1.5 py-0.5', + 'md': 'text-xs px-2 py-1', + 'lg': 'text-sm px-2.5 py-1' +} %} + + {{ value if value else badge_labels[type] }} + +{% endmacro %} diff --git a/app/templates/shared/macros/shop/product-grid.html b/app/templates/shared/macros/shop/product-grid.html new file mode 100644 index 00000000..d7a29e7a --- /dev/null +++ b/app/templates/shared/macros/shop/product-grid.html @@ -0,0 +1,215 @@ +{# + Product Grid + ============ + Responsive grid layout for product listings. + Includes loading skeletons and empty state. + + Parameters: + - products_var: Alpine.js variable containing products array (default: 'products') + - loading_var: Alpine.js variable for loading state (default: 'loading') + - columns: Dict with breakpoint column counts (default: {sm: 2, md: 3, lg: 4}) + - gap: Gap size 'sm' | 'md' | 'lg' (default: 'md') + - card_size: Product card size 'sm' | 'md' | 'lg' (default: 'md') + - show_rating: Pass to product cards (default: true) + - show_quick_add: Pass to product cards (default: true) + - show_wishlist: Pass to product cards (default: true) + - show_vendor: Pass to product cards (default: false) + - empty_title: Title for empty state (default: 'No products found') + - empty_message: Message for empty state (default: 'Try adjusting your filters') + - empty_icon: Icon for empty state (default: 'shopping-bag') + + Usage: + {% from 'shared/macros/shop/product-grid.html' import product_grid, product_grid_skeleton %} + + {{ product_grid() }} + + {# With custom columns #} + {{ product_grid(columns={'sm': 1, 'md': 2, 'lg': 3}) }} +#} + +{% macro product_grid( + products_var='products', + loading_var='loading', + columns=none, + gap='md', + card_size='md', + show_rating=true, + show_quick_add=true, + show_wishlist=true, + show_vendor=false, + empty_title='No products found', + empty_message='Try adjusting your filters or search terms', + empty_icon='shopping-bag' +) %} +{% from 'shared/macros/shop/product-card.html' import product_card %} + +{% set cols = columns if columns else {'sm': 2, 'md': 3, 'lg': 4} %} +{% set gaps = {'sm': 'gap-3', 'md': 'gap-4', 'lg': 'gap-6'} %} + +{# Loading State #} +
+ +
+ +{# Empty State #} +
+
+ +
+

{{ empty_title }}

+

{{ empty_message }}

+
+ +{# Product Grid #} +
+ +
+{% endmacro %} + + +{# + Product Skeleton + ================ + Loading skeleton for a single product card. + + Parameters: + - size: 'sm' | 'md' | 'lg' (default: 'md') + + Usage: + {{ product_skeleton() }} +#} +{% macro product_skeleton(size='md') %} +{% set sizes = { + 'sm': {'card': 'max-w-[200px]', 'image': 'h-32'}, + 'md': {'card': 'max-w-[280px]', 'image': 'h-48'}, + 'lg': {'card': 'max-w-[360px]', 'image': 'h-64'} +} %} +{% set s = sizes[size] %} + +
+ {# Image Skeleton #} +
+ + {# Content Skeleton #} +
+ {# Title #} +
+
+ + {# Rating #} +
+ +
+ + {# Price #} +
+ + {# Button #} +
+
+
+{% endmacro %} + + +{# + Product List Item + ================= + Horizontal product card for list views. + + Parameters: + - product_var: Alpine.js variable name (default: 'product') + - show_rating: Show star rating (default: true) + - show_quick_add: Show add to cart button (default: true) + - compact: Use compact spacing (default: false) + + Usage: + +#} +{% macro product_list_item( + product_var='product', + show_rating=true, + show_quick_add=true, + compact=false +) %} +
+ {# Image #} + + + + + {# Content #} +
+

+ +

+ + {% if show_rating %} +
+
+ +
+ +
+ {% endif %} + +
+ + +
+
+ + {# Actions #} + {% if show_quick_add %} +
+ +
+ {% endif %} +
+{% endmacro %}