From 49b1d52701c3e359624bbaed2e0b34a5eff9dcd0 Mon Sep 17 00:00:00 2001 From: magdev Date: Sat, 28 Feb 2026 10:53:44 +0100 Subject: [PATCH] Implement Phase 9 & reusable components; skip Phase 8 (emails) Phase 8 (Emails) skipped: WooCommerce email rendering uses wc_get_template_html() which bypasses the Twig pipeline entirely. Email customization deferred to plugins or block email editor. Phase 9 - Supplementary (7 templates): - Brand description with thumbnail, taxonomy archive delegate - Brands A-Z shortcode with alphabetical index navigation - Single brand thumbnail shortcode - REST API OAuth login and grant-access forms - Back-in-stock notification form with email input Reusable Components (6 templates): - price: product price display with sale handling - rating: star rating with Bootstrap Icons (filled/half/empty) - address-card: billing/shipping address card with edit link - status-badge: contextual order status badges - quantity-input: +/- input group widget - form-field: universal form field renderer (text/select/textarea/checkbox) Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 54 +++------ templates/auth/form-grant-access.html.twig | 72 +++++++++++ templates/auth/form-login.html.twig | 69 +++++++++++ templates/brands/brand-description.html.twig | 29 +++++ .../brands/shortcodes/brands-a-z.html.twig | 56 +++++++++ .../brands/shortcodes/single-brand.html.twig | 24 ++++ .../brands/taxonomy-product_brand.html.twig | 12 ++ templates/components/address-card.html.twig | 58 +++++++++ templates/components/form-field.html.twig | 114 ++++++++++++++++++ templates/components/price.html.twig | 20 +++ templates/components/quantity-input.html.twig | 52 ++++++++ templates/components/rating.html.twig | 37 ++++++ templates/components/status-badge.html.twig | 30 +++++ .../back-in-stock-form.html.twig | 65 ++++++++++ 14 files changed, 657 insertions(+), 35 deletions(-) create mode 100644 templates/auth/form-grant-access.html.twig create mode 100644 templates/auth/form-login.html.twig create mode 100644 templates/brands/brand-description.html.twig create mode 100644 templates/brands/shortcodes/brands-a-z.html.twig create mode 100644 templates/brands/shortcodes/single-brand.html.twig create mode 100644 templates/brands/taxonomy-product_brand.html.twig create mode 100644 templates/components/address-card.html.twig create mode 100644 templates/components/form-field.html.twig create mode 100644 templates/components/price.html.twig create mode 100644 templates/components/quantity-input.html.twig create mode 100644 templates/components/rating.html.twig create mode 100644 templates/components/status-badge.html.twig create mode 100644 templates/single-product/back-in-stock-form.html.twig diff --git a/PLAN.md b/PLAN.md index f3e3fe3..9753bd4 100644 --- a/PLAN.md +++ b/PLAN.md @@ -624,45 +624,29 @@ Track completion per file. Mark with `[x]` when done. - [x] `order/form-tracking.html.twig` - [x] `order/order-again.html.twig` -### Phase 8 -- Emails +### Phase 8 -- Emails (SKIPPED) -- [ ] `emails/email-header.html.twig` -- [ ] `emails/email-footer.html.twig` -- [ ] `emails/email-styles.html.twig` -- [ ] `emails/email-order-details.html.twig` -- [ ] `emails/email-order-items.html.twig` -- [ ] `emails/email-customer-details.html.twig` -- [ ] `emails/email-addresses.html.twig` -- [ ] `emails/email-downloads.html.twig` -- [ ] `emails/customer-processing-order.html.twig` -- [ ] `emails/customer-completed-order.html.twig` -- [ ] `emails/customer-on-hold-order.html.twig` -- [ ] `emails/customer-new-account.html.twig` -- [ ] `emails/customer-reset-password.html.twig` -- [ ] `emails/customer-invoice.html.twig` -- [ ] `emails/customer-note.html.twig` -- [ ] `emails/customer-refunded-order.html.twig` -- [ ] `emails/customer-cancelled-order.html.twig` -- [ ] `emails/customer-failed-order.html.twig` -- [ ] `emails/admin-new-order.html.twig` -- [ ] `emails/admin-cancelled-order.html.twig` -- [ ] `emails/admin-failed-order.html.twig` +Skipped: WooCommerce email templates use `wc_get_template_html()` which bypasses +the Twig rendering pipeline. Email overrides would require traditional PHP files +in `woocommerce/emails/`, breaking the Twig-only pattern. Default WooCommerce +email templates are sufficient; email customization can be handled via plugins +(e.g., Kadence WooCommerce Email Designer) or the WooCommerce block email editor. ### Phase 9 -- Supplementary -- [ ] `brands/brand-description.html.twig` -- [ ] `brands/taxonomy-product_brand.html.twig` -- [ ] `brands/shortcodes/brands-a-z.html.twig` -- [ ] `brands/shortcodes/single-brand.html.twig` -- [ ] `auth/form-login.html.twig` -- [ ] `auth/form-grant-access.html.twig` -- [ ] `single-product/back-in-stock-form.html.twig` +- [x] `brands/brand-description.html.twig` +- [x] `brands/taxonomy-product_brand.html.twig` +- [x] `brands/shortcodes/brands-a-z.html.twig` +- [x] `brands/shortcodes/single-brand.html.twig` +- [x] `auth/form-login.html.twig` +- [x] `auth/form-grant-access.html.twig` +- [x] `single-product/back-in-stock-form.html.twig` ### Reusable Components -- [ ] `components/price.html.twig` -- [ ] `components/rating.html.twig` -- [ ] `components/address-card.html.twig` -- [ ] `components/status-badge.html.twig` -- [ ] `components/quantity-input.html.twig` -- [ ] `components/form-field.html.twig` +- [x] `components/price.html.twig` +- [x] `components/rating.html.twig` +- [x] `components/address-card.html.twig` +- [x] `components/status-badge.html.twig` +- [x] `components/quantity-input.html.twig` +- [x] `components/form-field.html.twig` diff --git a/templates/auth/form-grant-access.html.twig b/templates/auth/form-grant-access.html.twig new file mode 100644 index 0000000..4e784c2 --- /dev/null +++ b/templates/auth/form-grant-access.html.twig @@ -0,0 +1,72 @@ +{# + # REST API Auth Grant Access Form (Bootstrap 5 Override) + # + # Confirmation page for granting third-party app access via OAuth. + # Rendered outside the WordPress theme (standalone page). + # + # Expected context: + # app_name - Name of the requesting application + # scope - Access scope (read, write, read_write) + # permissions - Array of permission description strings + # callback_url - Callback URL for the application + # return_url - URL to return to on deny + # granted_url - URL to redirect to on approve + # logout_url - URL to log out + # user - WP_User object of the logged-in user + # + # WooCommerce PHP equivalent: auth/form-grant-access.php + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{{ do_action('woocommerce_auth_page_header') }} + +

+ {{ __('%s would like to connect to your store')|format(app_name|esc_html) }} +

+ +{{ wc_print_notices() }} + +

+ {{ __('This will give "%1$s" %2$s access which will allow it to:')|format( + '' ~ app_name|esc_html ~ '', + '' ~ scope|esc_html ~ '' + )|wp_kses_post }} +

+ + + + + +
+ {{ get_avatar(user.ID, 48)|raw }} +
+ {{ __('Logged in as %s')|format(user.display_name|esc_html) }} + {{ __('Logout') }} +
+
+ + + +{{ do_action('woocommerce_auth_page_footer') }} diff --git a/templates/auth/form-login.html.twig b/templates/auth/form-login.html.twig new file mode 100644 index 0000000..8584377 --- /dev/null +++ b/templates/auth/form-login.html.twig @@ -0,0 +1,69 @@ +{# + # REST API Auth Login Form (Bootstrap 5 Override) + # + # Login form for third-party app OAuth authentication. + # Rendered outside the WordPress theme (standalone page). + # + # Expected context: + # app_name - Name of the requesting application + # return_url - URL to return to on cancel + # redirect_url - URL to redirect to after login + # + # WooCommerce PHP equivalent: auth/form-login.php + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{{ do_action('woocommerce_auth_page_header') }} + +

+ {{ __('%s would like to connect to your store')|format(app_name|esc_html) }} +

+ +{{ wc_print_notices() }} + +

+ {{ __('To connect to %1$s you need to be logged in. Log in to your store below, or cancel and return to %1$s')|format(app_name|esc_html, return_url|esc_url)|wp_kses_post }} +

+ + + +{{ do_action('woocommerce_auth_page_footer') }} diff --git a/templates/brands/brand-description.html.twig b/templates/brands/brand-description.html.twig new file mode 100644 index 0000000..c5fb739 --- /dev/null +++ b/templates/brands/brand-description.html.twig @@ -0,0 +1,29 @@ +{# + # Brand Description (Bootstrap 5 Override) + # + # Renders a brand's description with optional thumbnail. + # + # Expected context: + # thumbnail - URL of brand thumbnail image (or empty) + # + # WooCommerce PHP equivalent: brands/brand-description.php + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% set image_size = wc_get_image_size('shop_catalog') %} + +
+ {% if thumbnail %} + {{ single_term_title('', false)|esc_attr }} + {% endif %} + +
+ {{ term_description()|wptexturize|wpautop|do_shortcode }} +
+
diff --git a/templates/brands/shortcodes/brands-a-z.html.twig b/templates/brands/shortcodes/brands-a-z.html.twig new file mode 100644 index 0000000..b9a7c19 --- /dev/null +++ b/templates/brands/shortcodes/brands-a-z.html.twig @@ -0,0 +1,56 @@ +{# + # Brands A-Z Listing (Bootstrap 5 Override) + # + # Alphabetical index of product brands used by [product_brand_list] shortcode. + # + # Expected context: + # index - Array of index characters (A-Z, 0-9) + # product_brands - Array keyed by character, each containing brand term objects + # show_empty - Whether to show empty index letters + # show_top_links - Whether to show "Top" links after each section + # + # WooCommerce PHP equivalent: brands/shortcodes/brands-a-z.php + # + # @package WcBootstrap + # @since 0.1.0 + #} + +
+ + + {% for i in index %} + {% if product_brands[i] is defined %} +

{{ i|esc_html }}

+ + + + {% if show_top_links %} + + {{ __('Top') }} + + {% endif %} + {% endif %} + {% endfor %} +
diff --git a/templates/brands/shortcodes/single-brand.html.twig b/templates/brands/shortcodes/single-brand.html.twig new file mode 100644 index 0000000..181aa1b --- /dev/null +++ b/templates/brands/shortcodes/single-brand.html.twig @@ -0,0 +1,24 @@ +{# + # Single Brand Thumbnail (Bootstrap 5 Override) + # + # Renders a single brand image link, used by [product_brand] shortcode. + # + # Expected context: + # term - WP_Term object for the brand + # thumbnail - URL to brand thumbnail image + # class - CSS class for the image + # width - Image width + # height - Image height + # + # WooCommerce PHP equivalent: brands/shortcodes/single-brand.php + # + # @package WcBootstrap + # @since 0.1.0 + #} + + + {{ term.name|esc_attr }} + diff --git a/templates/brands/taxonomy-product_brand.html.twig b/templates/brands/taxonomy-product_brand.html.twig new file mode 100644 index 0000000..c43a762 --- /dev/null +++ b/templates/brands/taxonomy-product_brand.html.twig @@ -0,0 +1,12 @@ +{# + # Brand Taxonomy Archive (Bootstrap 5 Override) + # + # Delegates to the standard archive-product template. + # + # WooCommerce PHP equivalent: brands/taxonomy-product_brand.php + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% include 'archive-product.html.twig' %} diff --git a/templates/components/address-card.html.twig b/templates/components/address-card.html.twig new file mode 100644 index 0000000..47fbc9e --- /dev/null +++ b/templates/components/address-card.html.twig @@ -0,0 +1,58 @@ +{# + # Address Card Component (Bootstrap 5) + # + # Reusable address display card with optional edit link. + # + # Expected context: + # title - Card header title (e.g., "Billing address") + # address - Formatted address HTML string + # phone - Phone number (optional) + # email - Email address (optional) + # edit_url - URL to edit this address (optional) + # + # Usage: + # {% include 'components/address-card.html.twig' with { + # title: 'Billing address', + # address: order.get_formatted_billing_address(), + # phone: order.get_billing_phone(), + # email: order.get_billing_email(), + # edit_url: wc_get_endpoint_url('edit-address', 'billing') + # } %} + # + # @package WcBootstrap + # @since 0.1.0 + #} + +
+
+

{{ title|esc_html }}

+ {% if edit_url is defined and edit_url %} + + {{ __('Edit') }} + + {% endif %} +
+
+
+ {% if address %} + {{ address|wp_kses_post }} + {% else %} + {{ __('N/A') }} + {% endif %} + + {% if phone is defined and phone %} +

+ + {{ phone|esc_html }} +

+ {% endif %} + + {% if email is defined and email %} +

+ + {{ email|esc_html }} +

+ {% endif %} +
+
+
diff --git a/templates/components/form-field.html.twig b/templates/components/form-field.html.twig new file mode 100644 index 0000000..3017c76 --- /dev/null +++ b/templates/components/form-field.html.twig @@ -0,0 +1,114 @@ +{# + # Form Field Component (Bootstrap 5) + # + # Reusable form field wrapper with label, input, and validation. + # Renders different input types based on the field configuration. + # + # Expected context: + # field_key - Field name/key + # field - Field configuration array with: + # .type - Input type (text, email, password, tel, select, textarea, checkbox, radio) + # .label - Field label + # .required - Whether field is required (boolean) + # .placeholder - Placeholder text (optional) + # .options - Array of options for select/radio (optional) + # .value - Current value (optional) + # .description - Help text (optional) + # .class - Additional CSS classes (optional) + # + # Usage: + # {% include 'components/form-field.html.twig' with { + # field_key: 'billing_email', + # field: { type: 'email', label: 'Email', required: true } + # } %} + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% set field_type = field.type|default('text') %} +{% set field_id = field_key|replace({'_': '-', '[': '-', ']': ''}) %} +{% set is_required = field.required|default(false) %} + +{% if field_type == 'checkbox' %} +
+ + +
+{% elseif field_type == 'textarea' %} +
+ + + {% if field.description is defined and field.description %} +
{{ field.description }}
+ {% endif %} +
+{% elseif field_type == 'select' %} +
+ + + {% if field.description is defined and field.description %} +
{{ field.description }}
+ {% endif %} +
+{% else %} +
+ + + {% if field.description is defined and field.description %} +
{{ field.description }}
+ {% endif %} +
+{% endif %} diff --git a/templates/components/price.html.twig b/templates/components/price.html.twig new file mode 100644 index 0000000..3c9aa65 --- /dev/null +++ b/templates/components/price.html.twig @@ -0,0 +1,20 @@ +{# + # Price Component (Bootstrap 5) + # + # Reusable price display with sale/regular price handling. + # + # Expected context: + # product - WC_Product object + # + # Usage: + # {% include 'components/price.html.twig' with { product: product } %} + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% if product.get_price_html() %} + + {{ product.get_price_html()|raw }} + +{% endif %} diff --git a/templates/components/quantity-input.html.twig b/templates/components/quantity-input.html.twig new file mode 100644 index 0000000..417f547 --- /dev/null +++ b/templates/components/quantity-input.html.twig @@ -0,0 +1,52 @@ +{# + # Quantity Input Component (Bootstrap 5) + # + # Reusable quantity input with +/- buttons using Bootstrap input-group. + # Works with the quantity.js script for increment/decrement. + # + # Expected context: + # input_name - Input name attribute (default: 'quantity') + # input_id - Input id attribute (optional) + # input_value - Current quantity value (default: 1) + # min_value - Minimum quantity (default: 1) + # max_value - Maximum quantity (default: '') + # step - Step increment (default: 1) + # classes - Additional CSS classes (optional) + # + # Usage: + # {% include 'components/quantity-input.html.twig' with { + # input_name: 'quantity', + # input_value: 1, + # min_value: 1, + # max_value: product.get_max_purchase_quantity() + # } %} + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% set qty_name = input_name|default('quantity') %} +{% set qty_id = input_id|default('quantity_' ~ random()) %} +{% set qty_value = input_value|default(1) %} +{% set qty_min = min_value|default(1) %} +{% set qty_max = max_value|default('') %} +{% set qty_step = step|default(1) %} + +
+ + + +
diff --git a/templates/components/rating.html.twig b/templates/components/rating.html.twig new file mode 100644 index 0000000..52e62e1 --- /dev/null +++ b/templates/components/rating.html.twig @@ -0,0 +1,37 @@ +{# + # Rating Component (Bootstrap 5) + # + # Reusable star rating display using Bootstrap Icons. + # + # Expected context: + # rating - Numeric rating (0-5) + # review_count - Number of reviews (optional, for display) + # + # Usage: + # {% include 'components/rating.html.twig' with { rating: 4.5, review_count: 12 } %} + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% if rating is defined and rating > 0 %} + +{% endif %} diff --git a/templates/components/status-badge.html.twig b/templates/components/status-badge.html.twig new file mode 100644 index 0000000..8212ca5 --- /dev/null +++ b/templates/components/status-badge.html.twig @@ -0,0 +1,30 @@ +{# + # Status Badge Component (Bootstrap 5) + # + # Reusable order/payment status badge with contextual colors. + # + # Expected context: + # status - Status slug (e.g., 'completed', 'processing', 'on-hold', 'cancelled', 'failed', 'refunded', 'pending') + # label - Display label (optional, defaults to status name via wc_get_order_status_name) + # + # Usage: + # {% include 'components/status-badge.html.twig' with { status: order.get_status() } %} + # {% include 'components/status-badge.html.twig' with { status: 'completed', label: 'Done' } %} + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% set display_label = label|default(wc_get_order_status_name(status)) %} + +{% if status == 'completed' %} + {{ display_label|esc_html }} +{% elseif status == 'processing' %} + {{ display_label|esc_html }} +{% elseif status == 'on-hold' or status == 'pending' %} + {{ display_label|esc_html }} +{% elseif status == 'cancelled' or status == 'failed' or status == 'refunded' %} + {{ display_label|esc_html }} +{% else %} + {{ display_label|esc_html }} +{% endif %} diff --git a/templates/single-product/back-in-stock-form.html.twig b/templates/single-product/back-in-stock-form.html.twig new file mode 100644 index 0000000..b2566c7 --- /dev/null +++ b/templates/single-product/back-in-stock-form.html.twig @@ -0,0 +1,65 @@ +{# + # Back in Stock Form (Bootstrap 5 Override) + # + # Notification signup form shown on out-of-stock product pages. + # + # Expected context: + # product_id - Product ID + # is_visible - Whether the form is visible (not hidden) + # show_email_field - Whether to show the email input + # show_checkbox - Whether to show the privacy opt-in checkbox + # button_class - CSS class for the submit button + # + # WooCommerce PHP equivalent: single-product/back-in-stock-form.php + # + # @package WcBootstrap + # @since 0.1.0 + #} + +
+
+

+ {{ __('Want to be notified when this product is back in stock?') }} +

+ +
+
+ {% if show_email_field %} + + + {% endif %} + + +
+ + {% if show_checkbox %} +
+ + +
+ {% endif %} + + {{ wp_nonce_field('wc_bis_signup', 'wc_bis_nonce') }} + +
+
+