Implement Phase 6 & 7: My Account and Order Details templates (Bootstrap 5, HPOS)

Phase 6 - My Account (15 templates):
- Account layout with sidebar navigation (list-group) and content area
- Orders table with status badges, pagination, and responsive design
- View order with order notes as list-group items
- Address cards with edit/add buttons
- Login/Register side-by-side card layout
- Account edit, password change, downloads, payment methods forms
- Lost/reset password forms and confirmation

Phase 7 - Order Details (5 templates):
- Order details table with items, totals, and customer note
- Line item rows with refund quantity display
- Customer billing/shipping address cards
- Order tracking form
- Order again button

All order templates use WC_Order object methods only (HPOS compatible).
Bootstrap 5 components: cards, tables, list-groups, badges, forms, alerts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 10:43:30 +01:00
parent 594d810439
commit 8b1793097c
22 changed files with 1377 additions and 20 deletions

View File

@@ -0,0 +1,52 @@
{#
# Order Tracking Form (Bootstrap 5 Override)
#
# Form to look up an order by ID and billing email.
#
# WooCommerce PHP equivalent: order/form-tracking.php
#
# @package WcBootstrap
# @since 0.1.0
#}
<form action="{{ get_permalink()|esc_url }}" method="post" class="woocommerce-form woocommerce-form-track-order track_order">
{{ do_action('woocommerce_order_tracking_form_start') }}
<p class="mb-3">
{{ __('To track your order please enter your Order ID in the box below and press the "Track" button. This was given to you on your receipt and in the confirmation email you should have received.') }}
</p>
<div class="row g-3 mb-3">
<div class="col-sm-6">
<label for="orderid" class="form-label">{{ __('Order ID') }}</label>
<input type="text"
class="form-control"
name="orderid"
id="orderid"
placeholder="{{ __('Found in your order confirmation email.') }}" />
</div>
<div class="col-sm-6">
<label for="order_email" class="form-label">{{ __('Billing email') }}</label>
<input type="email"
class="form-control"
name="order_email"
id="order_email"
placeholder="{{ __('Email you used during checkout.') }}" />
</div>
</div>
{{ do_action('woocommerce_order_tracking_form') }}
<div class="mt-3">
<button type="submit" class="btn btn-primary" name="track" value="{{ __('Track') }}">
<i class="bi bi-search me-1" aria-hidden="true"></i>
{{ __('Track') }}
</button>
</div>
{{ wp_nonce_field('woocommerce-order_tracking', 'woocommerce-order-tracking-nonce') }}
{{ do_action('woocommerce_order_tracking_form_end') }}
</form>

View File

@@ -0,0 +1,20 @@
{#
# Order Again Button (Bootstrap 5 Override)
#
# Renders a button to reorder a previous order.
#
# Expected context:
# order_again_url - URL to reorder
#
# WooCommerce PHP equivalent: order/order-again.php
#
# @package WcBootstrap
# @since 0.1.0
#}
<p class="order-again mt-3">
<a href="{{ order_again_url|esc_url }}" class="btn btn-outline-primary">
<i class="bi bi-arrow-repeat me-1" aria-hidden="true"></i>
{{ __('Order again') }}
</a>
</p>

View File

@@ -0,0 +1,75 @@
{#
# Order Details Customer (Bootstrap 5 Override)
#
# Displays billing and shipping addresses for a completed order.
# HPOS compatible: uses WC_Order methods only.
#
# Expected context:
# order - WC_Order object
#
# WooCommerce PHP equivalent: order/order-details-customer.php
#
# @package WcBootstrap
# @since 0.1.0
#}
{% set show_shipping = not wc_ship_to_billing_address_only() and order.needs_shipping_address() %}
<section class="woocommerce-customer-details mt-4">
<div class="row g-4">
<div class="{{ show_shipping ? 'col-md-6' : 'col-12' }}">
<div class="card h-100">
<div class="card-header">
<h3 class="h6 mb-0">{{ __('Billing address') }}</h3>
</div>
<div class="card-body">
<address class="mb-0">
{{ order.get_formatted_billing_address(__('N/A'))|wp_kses_post }}
{% if order.get_billing_phone() %}
<p class="woocommerce-customer-details--phone mt-2 mb-0">
<i class="bi bi-telephone me-1" aria-hidden="true"></i>
{{ order.get_billing_phone()|esc_html }}
</p>
{% endif %}
{% if order.get_billing_email() %}
<p class="woocommerce-customer-details--email mt-1 mb-0">
<i class="bi bi-envelope me-1" aria-hidden="true"></i>
{{ order.get_billing_email()|esc_html }}
</p>
{% endif %}
{{ do_action('woocommerce_order_details_after_customer_address', 'billing', order) }}
</address>
</div>
</div>
</div>
{% if show_shipping %}
<div class="col-md-6">
<div class="card h-100">
<div class="card-header">
<h3 class="h6 mb-0">{{ __('Shipping address') }}</h3>
</div>
<div class="card-body">
<address class="mb-0">
{{ order.get_formatted_shipping_address(__('N/A'))|wp_kses_post }}
{% if order.get_shipping_phone() %}
<p class="woocommerce-customer-details--phone mt-2 mb-0">
<i class="bi bi-telephone me-1" aria-hidden="true"></i>
{{ order.get_shipping_phone()|esc_html }}
</p>
{% endif %}
{{ do_action('woocommerce_order_details_after_customer_address', 'shipping', order) }}
</address>
</div>
</div>
</div>
{% endif %}
</div>
{{ do_action('woocommerce_order_details_after_customer_details', order) }}
</section>

View File

@@ -0,0 +1,63 @@
{#
# Order Details Item (Bootstrap 5 Override)
#
# Renders a single line item row within the order details table.
# HPOS compatible: uses WC_Order methods only.
#
# Expected context:
# order - WC_Order object
# item_id - Item ID
# item - WC_Order_Item_Product object
# show_purchase_note - Whether to show purchase note
# purchase_note - Purchase note text
# product - WC_Product object or null
#
# WooCommerce PHP equivalent: order/order-details-item.php
#
# @package WcBootstrap
# @since 0.1.0
#}
{% if not apply_filters('woocommerce_order_item_visible', true, item) %}
{% do return() %}
{% endif %}
{% set is_visible = product and product.is_visible() %}
{% set product_permalink = apply_filters('woocommerce_order_item_permalink', is_visible ? product.get_permalink(item) : '', item, order) %}
<tr class="{{ apply_filters('woocommerce_order_item_class', 'woocommerce-table__line-item order_item', item, order) }}">
<td class="product-name">
{% if product_permalink %}
<a href="{{ product_permalink|esc_url }}">{{ item.get_name()|esc_html }}</a>
{% else %}
{{ item.get_name()|esc_html }}
{% endif %}
{% set qty = item.get_quantity() %}
{% set refunded_qty = order.get_qty_refunded_for_item(item_id) %}
<strong class="product-quantity">
{% if refunded_qty %}
&times;&nbsp;<del>{{ qty }}</del> <ins>{{ qty - (refunded_qty * -1) }}</ins>
{% else %}
&times;&nbsp;{{ qty }}
{% endif %}
</strong>
{{ do_action('woocommerce_order_item_meta_start', item_id, item, order, false) }}
{{ wc_display_item_meta(item) }}
{{ do_action('woocommerce_order_item_meta_end', item_id, item, order, false) }}
</td>
<td class="product-total text-end">
{{ order.get_formatted_line_subtotal(item)|raw }}
</td>
</tr>
{% if show_purchase_note and purchase_note %}
<tr class="product-purchase-note">
<td colspan="2">
{{ purchase_note|wp_kses_post|wpautop }}
</td>
</tr>
{% endif %}

View File

@@ -0,0 +1,91 @@
{#
# Order Details (Bootstrap 5 Override)
#
# Renders the full order details table with items, totals, and customer info.
# HPOS compatible: uses WC_Order methods only.
#
# Expected context:
# order_id - Order ID
# show_downloads - Whether to show downloads table
#
# WooCommerce PHP equivalent: order/order-details.php
#
# @package WcBootstrap
# @since 0.1.0
#}
{% set order = wc_get_order(order_id) %}
{% if not order %}
{% do return() %}
{% endif %}
{% set order_items = order.get_items(apply_filters('woocommerce_purchase_order_item_types', 'line_item')) %}
{% set show_purchase_note = order.has_status(apply_filters('woocommerce_purchase_note_order_statuses', ['completed', 'processing'])) %}
{% set show_customer_details = order.get_user_id() == get_current_user_id() %}
{% if show_downloads is defined and show_downloads %}
{% set downloads = order.get_downloadable_items() %}
{% if downloads is not empty %}
{{ wc_get_template('order/order-downloads.php', { downloads: downloads, show_title: true }) }}
{% endif %}
{% endif %}
<section class="woocommerce-order-details">
{{ do_action('woocommerce_order_details_before_order_table', order) }}
<h2 class="h5 mb-3">{{ __('Order details') }}</h2>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>{{ __('Product') }}</th>
<th class="text-end">{{ __('Total') }}</th>
</tr>
</thead>
<tbody>
{{ do_action('woocommerce_order_details_before_order_table_items', order) }}
{% for item_id, item in order_items %}
{% set product = item.get_product() %}
{% include 'order/order-details-item.html.twig' with {
order: order,
item_id: item_id,
item: item,
show_purchase_note: show_purchase_note,
purchase_note: product ? product.get_purchase_note() : '',
product: product
} %}
{% endfor %}
{{ do_action('woocommerce_order_details_after_order_table_items', order) }}
</tbody>
<tfoot>
{% for key, total in order.get_order_item_totals() %}
<tr>
<th scope="row">{{ total.label|esc_html }}</th>
<td class="text-end">{{ total.value|wp_kses_post }}</td>
</tr>
{% endfor %}
{% if order.get_customer_note() %}
<tr>
<th>{{ __('Note:') }}</th>
<td class="text-end">{{ order.get_customer_note()|nl2br|esc_html }}</td>
</tr>
{% endif %}
</tfoot>
</table>
</div>
{{ do_action('woocommerce_order_details_after_order_table', order) }}
</section>
{{ do_action('woocommerce_after_order_details', order) }}
{% if show_customer_details %}
{% include 'order/order-details-customer.html.twig' with { order: order } %}
{% endif %}