Compare commits
2
Commits
b8001a5ab0
...
00872a6568
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00872a6568 | ||
|
|
7034134678 |
No files matched your search
@@ -299,7 +299,11 @@ The child theme inherits from `wp-bootstrap` via WordPress `Template: wp-bootstr
|
|||||||
|
|
||||||
## Architecture Decisions
|
## Architecture Decisions
|
||||||
|
|
||||||
- **Edit forms** use 3+9 column layout: `col-lg-3` sticky sidebar (progress indicator, section nav) + `col-lg-9` card-based sections.
|
- **My Account layout** uses `col-lg-auto` (nav) + `col-lg` (content) so the nav auto-sizes to its content and the main area fills remaining space. Requires CSS resets for WooCommerce's float-based layout (`float: none; width: auto/100%`) and `max-width: none` on `.woocommerce-account main .woocommerce` to override the plugin's `max-width: 1000px`.
|
||||||
|
- **My Account navigation** uses `offcanvas-lg offcanvas-start` responsive pattern: full sidebar with icons on desktop (sticky via `position-sticky`), offcanvas slide-in on mobile with toggle button. Icons are mapped per endpoint via a Twig hash.
|
||||||
|
- **My Account dashboard** uses card-based quick actions (`row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3`) with `bg-primary-subtle` icon containers that adapt to dark mode. Each action card is an `<a class="card">` for full-card clickability.
|
||||||
|
- **My Account forms** (edit-account, edit-address, lost-password, reset-password) are wrapped in `card shadow-sm` sections with icon headers for visual consistency.
|
||||||
|
- **Edit forms** use card-based section grouping (e.g., "Personal information" + "Password change" as separate cards) instead of `<fieldset>/<legend>`.
|
||||||
- **Detail pages** use 8+4 column layout with sticky sidebar.
|
- **Detail pages** use 8+4 column layout with sticky sidebar.
|
||||||
- **Cards** use `<article class="card h-100">` with `stretched-link`.
|
- **Cards** use `<article class="card h-100">` with `stretched-link`.
|
||||||
- **Form layout** uses centered `col-lg-8 col-xl-7` with card + shadow for auth forms.
|
- **Form layout** uses centered `col-lg-8 col-xl-7` with card + shadow for auth forms.
|
||||||
@@ -313,4 +317,28 @@ Current version: **v0.0.1**
|
|||||||
|
|
||||||
## Session History
|
## Session History
|
||||||
|
|
||||||
<!-- AI assistants: document key learnings and session outcomes here -->
|
### 2026-02-28 — My Account Bootstrap 5 Polish
|
||||||
|
|
||||||
|
**Scope:** Redesigned 8 my-account Twig templates + CSS overrides to feel like a polished Bootstrap 5 application.
|
||||||
|
|
||||||
|
**Files changed (10):**
|
||||||
|
|
||||||
|
- `templates/myaccount/navigation.html.twig` — Added endpoint icon map, `offcanvas-lg` responsive pattern, sticky sidebar
|
||||||
|
- `templates/myaccount/dashboard.html.twig` — Replaced plain `<p>` tags with welcome card (avatar + greeting) and 5 quick-action cards in responsive grid
|
||||||
|
- `templates/myaccount/view-order.html.twig` — Replaced `<mark>` tags with summary card using `list-group-flush` and `components/status-badge.html.twig`; notes wrapped in card
|
||||||
|
- `templates/myaccount/form-edit-account.html.twig` — Wrapped in two card sections (Personal info + Password change) with icons
|
||||||
|
- `templates/myaccount/form-edit-address.html.twig` — Wrapped in card with dynamic icon (`bi-receipt` billing / `bi-truck` shipping)
|
||||||
|
- `templates/myaccount/form-lost-password.html.twig` — Wrapped in card with `bi-key` icon
|
||||||
|
- `templates/myaccount/form-reset-password.html.twig` — Wrapped in card with `bi-shield-lock` icon
|
||||||
|
- `templates/myaccount/lost-password-confirmation.html.twig` — Added `text-body-secondary` styling and "Back to login" button
|
||||||
|
- `templates/myaccount/my-account.html.twig` — Changed grid from `col-lg-3`/`col-lg-9` to `col-lg-auto`/`col-lg`
|
||||||
|
- `assets/css/wc-bootstrap.css` — Reset WooCommerce float layout, override `max-width: 1000px`, avatar rounding, card hover lift
|
||||||
|
|
||||||
|
**Design decisions:**
|
||||||
|
|
||||||
|
- **Nav column auto-sizing (`col-lg-auto`)**: Fixed-width columns (e.g., `col-lg-3` = 285px) caused label overflow with icons. Auto-sizing lets the nav take exactly the space it needs across locales while the content fills the rest.
|
||||||
|
- **WooCommerce layout overrides require matching specificity**: The plugin uses `.woocommerce-account .woocommerce-MyAccount-navigation` (specificity `0,2,0`) — single-class selectors don't override. Also `.woocommerce-account main .woocommerce` sets `max-width: 1000px` (specificity `0,2,1`) which must be reset to `none`.
|
||||||
|
- **`offcanvas-lg` over `d-none d-lg-block`**: Bootstrap's responsive offcanvas natively handles the desktop/mobile switch without duplicating nav markup. The toggle button uses `d-lg-none` visibility.
|
||||||
|
- **`bg-primary-subtle` for icon containers**: These Bootstrap 5.3 contextual utilities automatically adapt to dark mode, unlike hardcoded colors.
|
||||||
|
- **Welcome message restructured**: Separated greeting from logout link instead of using WooCommerce's default inline-linked `__()` string. This gives full control over card layout and avoids translated strings containing HTML structure assumptions.
|
||||||
|
- **Templates NOT changed** (already well-done): `orders.html.twig`, `my-address.html.twig`, `form-login.html.twig`, `payment-methods.html.twig`, `form-add-payment-method.html.twig`, `downloads.html.twig`
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Product Archive / Shop Page (Bootstrap 5 Layout)
|
||||||
|
*
|
||||||
|
* Renders the WooCommerce product archive content with a Bootstrap 5 layout
|
||||||
|
* featuring a responsive sidebar (offcanvas on mobile) and product card grid.
|
||||||
|
*
|
||||||
|
* This file is NOT included directly by WordPress. Instead, it is captured via
|
||||||
|
* output buffering by wc_bootstrap_render_product_archive() and injected into
|
||||||
|
* the parent theme's page shell (pages/page.html.twig). Therefore it does NOT
|
||||||
|
* call get_header()/get_footer() or render the wrapper hooks.
|
||||||
|
*
|
||||||
|
* @package WcBootstrap
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
// Fire structured data hook (normally on woocommerce_before_main_content at priority 30).
|
||||||
|
do_action( 'woocommerce_shop_loop_header' );
|
||||||
|
|
||||||
|
$has_sidebar = is_active_sidebar( 'shop-sidebar' );
|
||||||
|
|
||||||
|
if ( woocommerce_product_loop() ) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook: woocommerce_before_shop_loop.
|
||||||
|
*
|
||||||
|
* @hooked woocommerce_output_all_notices - 10
|
||||||
|
* @hooked woocommerce_result_count - 20
|
||||||
|
* @hooked woocommerce_catalog_ordering - 30
|
||||||
|
*/
|
||||||
|
do_action( 'woocommerce_before_shop_loop' );
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div class="row g-4">
|
||||||
|
<?php if ( $has_sidebar ) : ?>
|
||||||
|
<aside class="col-lg-3 mb-4 mb-lg-0">
|
||||||
|
<button class="btn btn-outline-secondary w-100 d-lg-none mb-3"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="offcanvas"
|
||||||
|
data-bs-target="#shopSidebar"
|
||||||
|
aria-controls="shopSidebar">
|
||||||
|
<i class="bi bi-funnel me-1" aria-hidden="true"></i>
|
||||||
|
<?php esc_html_e( 'Filters', 'wc-bootstrap' ); ?>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="offcanvas-lg offcanvas-start"
|
||||||
|
id="shopSidebar"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="shopSidebarLabel">
|
||||||
|
<div class="offcanvas-header d-lg-none">
|
||||||
|
<h5 class="offcanvas-title" id="shopSidebarLabel">
|
||||||
|
<?php esc_html_e( 'Filters', 'wc-bootstrap' ); ?>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="<?php esc_attr_e( 'Close', 'wc-bootstrap' ); ?>"></button>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas-body p-0">
|
||||||
|
<?php dynamic_sidebar( 'shop-sidebar' ); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<?php else : ?>
|
||||||
|
<div class="col-12">
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
woocommerce_product_loop_start();
|
||||||
|
|
||||||
|
if ( wc_get_loop_prop( 'total' ) ) {
|
||||||
|
while ( have_posts() ) {
|
||||||
|
the_post();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook: woocommerce_shop_loop.
|
||||||
|
*/
|
||||||
|
do_action( 'woocommerce_shop_loop' );
|
||||||
|
|
||||||
|
wc_get_template_part( 'content', 'product' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
woocommerce_product_loop_end();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook: woocommerce_after_shop_loop.
|
||||||
|
*
|
||||||
|
* @hooked woocommerce_pagination - 10
|
||||||
|
*/
|
||||||
|
do_action( 'woocommerce_after_shop_loop' );
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Hook: woocommerce_no_products_found.
|
||||||
|
*
|
||||||
|
* @hooked wc_no_products_found - 10
|
||||||
|
*/
|
||||||
|
do_action( 'woocommerce_no_products_found' );
|
||||||
|
}
|
||||||
+165
-8
@@ -109,6 +109,104 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Product link wrapping card content — remove underline, inherit text color */
|
||||||
|
.product.card a.woocommerce-LoopProduct-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Product title in card body */
|
||||||
|
.product.card .woocommerce-loop-product__title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Push price to bottom of card body for even card heights */
|
||||||
|
.product.card .card-body .price {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add-to-cart button — Bootstrap btn-outline-primary style */
|
||||||
|
.product.card .button {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border-radius: var(--bs-border-radius);
|
||||||
|
color: var(--bs-primary);
|
||||||
|
border: var(--bs-border-width) solid var(--bs-primary);
|
||||||
|
background-color: transparent;
|
||||||
|
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product.card .button:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--bs-primary);
|
||||||
|
border-color: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "View options" button for variable products */
|
||||||
|
.product.card .button.product_type_variable {
|
||||||
|
color: var(--bs-secondary);
|
||||||
|
border-color: var(--bs-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product.card .button.product_type_variable:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--bs-secondary);
|
||||||
|
border-color: var(--bs-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "Read more" button for external/non-purchasable products */
|
||||||
|
.product.card .button.product_type_external {
|
||||||
|
color: var(--bs-info);
|
||||||
|
border-color: var(--bs-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product.card .button.product_type_external:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--bs-info);
|
||||||
|
border-color: var(--bs-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Added-to-cart visual feedback */
|
||||||
|
.product.card .added_to_cart {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
color: var(--bs-success);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WooCommerce result count and ordering bar */
|
||||||
|
.woocommerce-result-count {
|
||||||
|
margin-bottom: 0;
|
||||||
|
line-height: 2.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-ordering select {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border: var(--bs-border-width) solid var(--bs-border-color);
|
||||||
|
border-radius: var(--bs-border-radius);
|
||||||
|
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||||
|
appearance: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
Sale Badge
|
Sale Badge
|
||||||
Positioning for the sale overlay badge on product cards.
|
Positioning for the sale overlay badge on product cards.
|
||||||
@@ -179,12 +277,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
WooCommerce Grid Override
|
Shop / Archive Layout
|
||||||
Reset WooCommerce's default grid to let Bootstrap handle layout.
|
Reset WooCommerce's default float grid to let Bootstrap handle layout.
|
||||||
|
Override woocommerce-layout.css float-based widths and clearfixes.
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
.woocommerce ul.products {
|
/* Reset float-based result count / ordering bar — use flexbox instead */
|
||||||
display: contents;
|
.post-type-archive-product .woocommerce-result-count,
|
||||||
|
.tax-product_cat .woocommerce-result-count,
|
||||||
|
.tax-product_tag .woocommerce-result-count,
|
||||||
|
.post-type-archive-product .woocommerce-ordering,
|
||||||
|
.tax-product_cat .woocommerce-ordering,
|
||||||
|
.tax-product_tag .woocommerce-ordering {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset WooCommerce's product grid floats and widths */
|
||||||
|
.woocommerce ul.products,
|
||||||
|
.woocommerce .products {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce ul.products li.product,
|
||||||
|
.woocommerce .products .product {
|
||||||
|
float: none;
|
||||||
|
width: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
@@ -280,16 +403,50 @@ header.sticky-top.is-stuck {
|
|||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
My Account
|
My Account
|
||||||
Navigation and layout for the My Account area.
|
Navigation and layout for the My Account area.
|
||||||
|
Reset WooCommerce's float-based layout to let Bootstrap flex grid handle it.
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
|
.woocommerce-account main .woocommerce,
|
||||||
|
.woocommerce-cart main .woocommerce,
|
||||||
|
.woocommerce-checkout main .woocommerce,
|
||||||
|
.post-type-archive-product main .woocommerce,
|
||||||
|
.tax-product_cat main .woocommerce,
|
||||||
|
.tax-product_tag main .woocommerce {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-account .woocommerce-MyAccount-navigation {
|
||||||
|
float: none;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-account .woocommerce-MyAccount-content {
|
||||||
|
float: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-MyAccount-navigation .list-group-item {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.woocommerce-MyAccount-navigation .list-group-item.active {
|
.woocommerce-MyAccount-navigation .list-group-item.active {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Order status marks */
|
/* Dashboard: avatar rounding */
|
||||||
.woocommerce-order-details mark {
|
.woocommerce-MyAccount-content .avatar {
|
||||||
background: none;
|
border-radius: 50%;
|
||||||
font-weight: 600;
|
}
|
||||||
|
|
||||||
|
/* Dashboard: quick action card hover lift */
|
||||||
|
.woocommerce-MyAccount-content a.card {
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-MyAccount-content a.card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--bs-box-shadow) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* View-order notes */
|
/* View-order notes */
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Product Content in Loop — PHP Bridge to Twig
|
||||||
|
*
|
||||||
|
* Bridge file that renders the Bootstrap 5 card template (content-product.html.twig)
|
||||||
|
* via the parent theme's TwigService instead of WooCommerce's default <li> markup.
|
||||||
|
*
|
||||||
|
* WooCommerce's wc_get_template_part('content', 'product') uses locate_template()
|
||||||
|
* which finds this file in the child theme before falling back to the plugin template.
|
||||||
|
* Unlike wc_get_template(), wc_get_template_part() does NOT fire the
|
||||||
|
* woocommerce_before_template_part / woocommerce_after_template_part hooks,
|
||||||
|
* so the TemplateOverride class cannot intercept it — this bridge file is needed.
|
||||||
|
*
|
||||||
|
* @package WcBootstrap
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
|
global $product;
|
||||||
|
|
||||||
|
// Ensure the product is valid and visible (same guard as WooCommerce's default template).
|
||||||
|
if ( ! is_a( $product, WC_Product::class ) || ! $product->is_visible() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( class_exists( '\WPBootstrap\Twig\TwigService' ) ) {
|
||||||
|
$twig = \WPBootstrap\Twig\TwigService::getInstance();
|
||||||
|
echo $twig->render( 'content-product.html.twig', [] );
|
||||||
|
} else {
|
||||||
|
// Fallback: include WooCommerce's default template directly.
|
||||||
|
include WC()->plugin_path() . '/templates/content-product.php';
|
||||||
|
}
|
||||||
+121
@@ -14,6 +14,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define theme constants.
|
* Define theme constants.
|
||||||
*
|
*
|
||||||
@@ -223,3 +224,123 @@ function wc_bootstrap_sticky_header_script(): void {
|
|||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
add_action( 'wp_footer', 'wc_bootstrap_sticky_header_script' );
|
add_action( 'wp_footer', 'wc_bootstrap_sticky_header_script' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the shop sidebar widget area.
|
||||||
|
*
|
||||||
|
* Provides a widget area for product filters and shop-specific widgets.
|
||||||
|
* Uses Bootstrap-styled markup matching the parent theme's sidebar pattern.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
function wc_bootstrap_register_sidebars(): void {
|
||||||
|
register_sidebar( array(
|
||||||
|
'name' => __( 'Shop Sidebar', 'wc-bootstrap' ),
|
||||||
|
'id' => 'shop-sidebar',
|
||||||
|
'description' => __( 'Add widgets here to appear in the shop sidebar.', 'wc-bootstrap' ),
|
||||||
|
'before_widget' => '<div id="%1$s" class="widget mb-4 %2$s">',
|
||||||
|
'after_widget' => '</div>',
|
||||||
|
'before_title' => '<h3 class="sidebar-heading h6 text-uppercase fw-semibold">',
|
||||||
|
'after_title' => '</h3>',
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
add_action( 'widgets_init', 'wc_bootstrap_register_sidebars' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the number of product columns in the shop loop.
|
||||||
|
*
|
||||||
|
* @return int Number of columns.
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
function wc_bootstrap_loop_columns(): int {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
add_filter( 'loop_shop_columns', 'wc_bootstrap_loop_columns' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove WooCommerce's default sidebar hook.
|
||||||
|
*
|
||||||
|
* The child theme's archive-product.php renders the sidebar inline within the
|
||||||
|
* Bootstrap grid layout, so the default woocommerce_sidebar hook must not render
|
||||||
|
* a second sidebar outside the layout.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
function wc_bootstrap_remove_default_sidebar(): void {
|
||||||
|
remove_action( 'woocommerce_sidebar', 'woocommerce_get_sidebar', 10 );
|
||||||
|
}
|
||||||
|
add_action( 'init', 'wc_bootstrap_remove_default_sidebar' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent the parent theme from rendering product archives.
|
||||||
|
*
|
||||||
|
* The parent theme's TemplateController hooks template_redirect at priority 10
|
||||||
|
* and renders pages/archive.html.twig for all archives (then exits). For product
|
||||||
|
* archives, we need to render our own WooCommerce-specific Bootstrap layout instead.
|
||||||
|
* Returning false tells the parent theme to skip rendering for this request.
|
||||||
|
*
|
||||||
|
* @param bool $should_render Whether the parent theme should render.
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
function wc_bootstrap_skip_parent_archive( bool $should_render ): bool {
|
||||||
|
if ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( function_exists( 'is_product_taxonomy' ) && is_product_taxonomy() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $should_render;
|
||||||
|
}
|
||||||
|
add_filter( 'wp_bootstrap_should_render_template', 'wc_bootstrap_skip_parent_archive' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render product archive pages with Bootstrap 5 layout.
|
||||||
|
*
|
||||||
|
* Since the parent theme's TemplateController is blocked for product archives
|
||||||
|
* (via wp_bootstrap_should_render_template filter), we render the page ourselves
|
||||||
|
* at priority 11 using the parent theme's TwigService and page shell.
|
||||||
|
*
|
||||||
|
* The archive-product.php file provides the Bootstrap layout (sidebar + product
|
||||||
|
* grid) and is captured via output buffering, then injected into the parent
|
||||||
|
* theme's page template — the same pattern as wc_bootstrap_render_page().
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
function wc_bootstrap_render_product_archive(): void {
|
||||||
|
$is_shop = is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) );
|
||||||
|
$is_tax = function_exists( 'is_product_taxonomy' ) && is_product_taxonomy();
|
||||||
|
|
||||||
|
if ( ! $is_shop && ! $is_tax ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! class_exists( '\WPBootstrap\Twig\TwigService' )
|
||||||
|
|| ! class_exists( '\WPBootstrap\Template\ContextBuilder' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture WooCommerce archive content via output buffering.
|
||||||
|
ob_start();
|
||||||
|
include get_stylesheet_directory() . '/archive-product.php';
|
||||||
|
$content = ob_get_clean();
|
||||||
|
|
||||||
|
// Build parent theme context and inject archive content into page shell.
|
||||||
|
$context_builder = new \WPBootstrap\Template\ContextBuilder();
|
||||||
|
$theme_context = $context_builder->build();
|
||||||
|
$twig = \WPBootstrap\Twig\TwigService::getInstance();
|
||||||
|
|
||||||
|
$theme_context['post'] = array_merge(
|
||||||
|
$theme_context['post'] ?? [],
|
||||||
|
[
|
||||||
|
'content' => $content,
|
||||||
|
'title' => '',
|
||||||
|
'thumbnail' => '',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
echo $twig->render( 'pages/page.html.twig', $theme_context );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
add_action( 'template_redirect', 'wc_bootstrap_render_product_archive', 11 );
|
||||||
@@ -78,8 +78,17 @@ class TemplateOverride {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$twig = TwigService::getInstance();
|
$twig = TwigService::getInstance();
|
||||||
echo $twig->render( $twigTemplate, $args );
|
$context = $args;
|
||||||
|
|
||||||
|
// Inject the global $product into the Twig context.
|
||||||
|
// WooCommerce PHP templates access it via `global $product;` but Twig
|
||||||
|
// templates have isolated variable scopes and need it passed explicitly.
|
||||||
|
if ( ! isset( $context['product'] ) && ! empty( $GLOBALS['product'] ) ) {
|
||||||
|
$context['product'] = $GLOBALS['product'];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $twig->render( $twigTemplate, $context );
|
||||||
|
|
||||||
// Buffer the upcoming PHP include so we can discard it.
|
// Buffer the upcoming PHP include so we can discard it.
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<ul class="pagination justify-content-center">
|
<ul class="pagination justify-content-center">
|
||||||
{# Previous button #}
|
{# Previous button #}
|
||||||
<li class="page-item{% if current_page <= 1 %} disabled{% endif %}">
|
<li class="page-item{% if current_page <= 1 %} disabled{% endif %}">
|
||||||
<a class="page-link" href="?page={{ current_page - 1 }}" aria-label="{{ __('Previous') }}">
|
<a class="page-link" href="{{ fn('get_pagenum_link', current_page > 1 ? current_page - 1 : 1)|esc_url }}" aria-label="{{ __('Previous') }}">
|
||||||
<span aria-hidden="true">«</span>
|
<span aria-hidden="true">«</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
</li>
|
</li>
|
||||||
{% elseif i == 1 or i == max_pages or (i >= current_page - 2 and i <= current_page + 2) %}
|
{% elseif i == 1 or i == max_pages or (i >= current_page - 2 and i <= current_page + 2) %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="?page={{ i }}">{{ i }}</a>
|
<a class="page-link" href="{{ fn('get_pagenum_link', i)|esc_url }}">{{ i }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% elseif i == current_page - 3 or i == current_page + 3 %}
|
{% elseif i == current_page - 3 or i == current_page + 3 %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
{# Next button #}
|
{# Next button #}
|
||||||
<li class="page-item{% if current_page >= max_pages %} disabled{% endif %}">
|
<li class="page-item{% if current_page >= max_pages %} disabled{% endif %}">
|
||||||
<a class="page-link" href="?page={{ current_page + 1 }}" aria-label="{{ __('Next') }}">
|
<a class="page-link" href="{{ fn('get_pagenum_link', current_page + 1)|esc_url }}" aria-label="{{ __('Next') }}">
|
||||||
<span aria-hidden="true">»</span>
|
<span aria-hidden="true">»</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
# Product Content in Loop (Bootstrap 5 Override)
|
# Product Content in Loop (Bootstrap 5 Override)
|
||||||
#
|
#
|
||||||
# Renders a single product card within the shop loop grid.
|
# Renders a single product card within the shop loop grid.
|
||||||
# Uses Bootstrap 5 card component with stretched-link.
|
# Uses Bootstrap 5 card component with WooCommerce hook output.
|
||||||
#
|
#
|
||||||
# Expected context:
|
# Rendered via the content-product.php bridge file (not TemplateOverride)
|
||||||
# product - WC_Product object with:
|
# because wc_get_template_part() does not fire the template_part hooks.
|
||||||
# .name - Product name
|
#
|
||||||
# .permalink - Product URL
|
# Hook output structure:
|
||||||
# .image - Product thumbnail HTML
|
# woocommerce_before_shop_loop_item → <a> link open
|
||||||
# .price_html - Formatted price HTML
|
# woocommerce_before_shop_loop_item_title → sale badge, product image
|
||||||
# .rating_html - Star rating HTML
|
# woocommerce_shop_loop_item_title → <h2> product title
|
||||||
# .is_on_sale - Whether product is on sale
|
# woocommerce_after_shop_loop_item_title → star rating, price
|
||||||
# .add_to_cart - Add-to-cart button context
|
# woocommerce_after_shop_loop_item → </a> link close, add-to-cart button
|
||||||
#
|
#
|
||||||
# WooCommerce PHP equivalent: content-product.php
|
# WooCommerce PHP equivalent: content-product.php
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
#}
|
#}
|
||||||
|
|
||||||
{% set cols = columns|default(3) %}
|
{% set cols = columns|default(4) %}
|
||||||
|
|
||||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-{{ cols < 3 ? cols : 3 }} row-cols-lg-{{ cols }} g-4 products">
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-{{ cols }} g-4 products">
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
{% if total <= per_page|default(total) or total == 0 %}
|
{% if total <= per_page|default(total) or total == 0 %}
|
||||||
{{ _n('Showing the single result', 'Showing all %d results', total)|format(total) }}
|
{{ _n('Showing the single result', 'Showing all %d results', total)|format(total) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ __('Showing %1$d–%2$d of %3$d results')|format(first, last, total) }}
|
{{ __('Showing %1$d–%2$d of %3$d results')|format(first, last, total)|raw }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
{#
|
{#
|
||||||
# My Account Dashboard (Bootstrap 5 Override)
|
# My Account Dashboard (Bootstrap 5 Override)
|
||||||
#
|
#
|
||||||
# Shows the welcome screen on the account dashboard.
|
# Shows a card-based dashboard with welcome greeting and
|
||||||
|
# quick action links to each account section.
|
||||||
#
|
#
|
||||||
# Expected context:
|
# Expected context:
|
||||||
# current_user - WP_User object
|
# current_user - WP_User object
|
||||||
@@ -12,28 +13,54 @@
|
|||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
#}
|
#}
|
||||||
|
|
||||||
<p>
|
{% set quick_actions = [
|
||||||
{{ __('Hello %1$s (not %1$s? <a href="%2$s">Log out</a>)')|format(
|
{ endpoint: 'orders', icon: 'bi-bag', label: __('Orders'), desc: __('View and track your orders') },
|
||||||
'<strong>' ~ current_user.display_name|esc_html ~ '</strong>',
|
{ endpoint: 'edit-address', icon: 'bi-geo-alt', label: __('Addresses'), desc: __('Manage billing & shipping') },
|
||||||
wc_logout_url()|esc_url
|
{ endpoint: 'edit-account', icon: 'bi-person-gear', label: __('Account details'), desc: __('Update name, email & password') },
|
||||||
)|wp_kses_post }}
|
{ endpoint: 'downloads', icon: 'bi-download', label: __('Downloads'), desc: __('Access purchased files') },
|
||||||
</p>
|
{ endpoint: 'payment-methods', icon: 'bi-credit-card', label: __('Payment methods'), desc: __('Manage saved cards') },
|
||||||
|
] %}
|
||||||
|
|
||||||
<p>
|
<div class="card shadow-sm mb-4">
|
||||||
{% if wc_shipping_enabled() %}
|
<div class="card-body d-flex align-items-center gap-3">
|
||||||
{{ __('From your account dashboard you can view your <a href="%1$s">recent orders</a>, manage your <a href="%2$s">shipping and billing addresses</a>, and <a href="%3$s">edit your password and account details</a>.')|format(
|
<div class="flex-shrink-0">
|
||||||
wc_get_endpoint_url('orders')|esc_url,
|
{{ get_avatar(current_user.ID, 64)|raw }}
|
||||||
wc_get_endpoint_url('edit-address')|esc_url,
|
</div>
|
||||||
wc_get_endpoint_url('edit-account')|esc_url
|
<div class="flex-grow-1">
|
||||||
)|wp_kses_post }}
|
<h2 class="h5 mb-1">
|
||||||
{% else %}
|
{{ __('Hello, %s!')|format(current_user.display_name|esc_html) }}
|
||||||
{{ __('From your account dashboard you can view your <a href="%1$s">recent orders</a>, manage your <a href="%2$s">billing address</a>, and <a href="%3$s">edit your password and account details</a>.')|format(
|
</h2>
|
||||||
wc_get_endpoint_url('orders')|esc_url,
|
<p class="text-body-secondary mb-0">
|
||||||
wc_get_endpoint_url('edit-address')|esc_url,
|
{{ __('Not %s?')|format(current_user.display_name|esc_html) }}
|
||||||
wc_get_endpoint_url('edit-account')|esc_url
|
<a href="{{ wc_logout_url()|esc_url }}">{{ __('Log out') }}</a>
|
||||||
)|wp_kses_post }}
|
</p>
|
||||||
{% endif %}
|
</div>
|
||||||
</p>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3 mb-4">
|
||||||
|
{% for action in quick_actions %}
|
||||||
|
<div class="col">
|
||||||
|
<a href="{{ wc_get_endpoint_url(action.endpoint)|esc_url }}"
|
||||||
|
class="card h-100 text-decoration-none shadow-sm">
|
||||||
|
<div class="card-body d-flex align-items-start gap-3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<span class="d-inline-flex align-items-center justify-content-center
|
||||||
|
bg-primary-subtle text-primary rounded-3"
|
||||||
|
style="width: 3rem; height: 3rem;"
|
||||||
|
aria-hidden="true">
|
||||||
|
<i class="bi {{ action.icon }} fs-4"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="h6 mb-1 text-body">{{ action.label }}</h3>
|
||||||
|
<p class="text-body-secondary small mb-0">{{ action.desc }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{ do_action('woocommerce_account_dashboard') }}
|
{{ do_action('woocommerce_account_dashboard') }}
|
||||||
{{ do_action('woocommerce_before_my_account') }}
|
{{ do_action('woocommerce_before_my_account') }}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{#
|
{#
|
||||||
# Edit Account Form (Bootstrap 5 Override)
|
# Edit Account Form (Bootstrap 5 Override)
|
||||||
#
|
#
|
||||||
# Account details editing form with Bootstrap form styling.
|
# Account details editing form with card-based sections
|
||||||
|
# for personal information and password change.
|
||||||
#
|
#
|
||||||
# Expected context:
|
# Expected context:
|
||||||
# user - WP_User object
|
# user - WP_User object
|
||||||
@@ -18,109 +19,125 @@
|
|||||||
|
|
||||||
{{ do_action('woocommerce_edit_account_form_start') }}
|
{{ do_action('woocommerce_edit_account_form_start') }}
|
||||||
|
|
||||||
<div class="row g-3 mb-3">
|
<div class="card shadow-sm mb-4">
|
||||||
<div class="col-sm-6">
|
<div class="card-header">
|
||||||
<label for="account_first_name" class="form-label">
|
<h2 class="h5 mb-0">
|
||||||
{{ __('First name') }} <span class="text-danger" aria-hidden="true">*</span>
|
<i class="bi bi-person me-1" aria-hidden="true"></i>
|
||||||
<span class="visually-hidden">{{ __('Required') }}</span>
|
{{ __('Personal information') }}
|
||||||
</label>
|
</h2>
|
||||||
<input type="text"
|
|
||||||
class="form-control"
|
|
||||||
name="account_first_name"
|
|
||||||
id="account_first_name"
|
|
||||||
autocomplete="given-name"
|
|
||||||
value="{{ user.first_name|esc_attr }}"
|
|
||||||
required
|
|
||||||
aria-required="true" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="card-body">
|
||||||
<label for="account_last_name" class="form-label">
|
<div class="row g-3 mb-3">
|
||||||
{{ __('Last name') }} <span class="text-danger" aria-hidden="true">*</span>
|
<div class="col-sm-6">
|
||||||
<span class="visually-hidden">{{ __('Required') }}</span>
|
<label for="account_first_name" class="form-label">
|
||||||
</label>
|
{{ __('First name') }} <span class="text-danger" aria-hidden="true">*</span>
|
||||||
<input type="text"
|
<span class="visually-hidden">{{ __('Required') }}</span>
|
||||||
class="form-control"
|
</label>
|
||||||
name="account_last_name"
|
<input type="text"
|
||||||
id="account_last_name"
|
class="form-control"
|
||||||
autocomplete="family-name"
|
name="account_first_name"
|
||||||
value="{{ user.last_name|esc_attr }}"
|
id="account_first_name"
|
||||||
required
|
autocomplete="given-name"
|
||||||
aria-required="true" />
|
value="{{ user.first_name|esc_attr }}"
|
||||||
|
required
|
||||||
|
aria-required="true" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label for="account_last_name" class="form-label">
|
||||||
|
{{ __('Last name') }} <span class="text-danger" aria-hidden="true">*</span>
|
||||||
|
<span class="visually-hidden">{{ __('Required') }}</span>
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
name="account_last_name"
|
||||||
|
id="account_last_name"
|
||||||
|
autocomplete="family-name"
|
||||||
|
value="{{ user.last_name|esc_attr }}"
|
||||||
|
required
|
||||||
|
aria-required="true" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="account_display_name" class="form-label">
|
||||||
|
{{ __('Display name') }} <span class="text-danger" aria-hidden="true">*</span>
|
||||||
|
<span class="visually-hidden">{{ __('Required') }}</span>
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
name="account_display_name"
|
||||||
|
id="account_display_name"
|
||||||
|
aria-describedby="account_display_name_description"
|
||||||
|
value="{{ user.display_name|esc_attr }}"
|
||||||
|
required
|
||||||
|
aria-required="true" />
|
||||||
|
<div id="account_display_name_description" class="form-text">
|
||||||
|
{{ __('This will be how your name will be displayed in the account section and in reviews') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-0">
|
||||||
|
<label for="account_email" class="form-label">
|
||||||
|
{{ __('Email address') }} <span class="text-danger" aria-hidden="true">*</span>
|
||||||
|
<span class="visually-hidden">{{ __('Required') }}</span>
|
||||||
|
</label>
|
||||||
|
<input type="email"
|
||||||
|
class="form-control"
|
||||||
|
name="account_email"
|
||||||
|
id="account_email"
|
||||||
|
autocomplete="email"
|
||||||
|
value="{{ user.user_email|esc_attr }}"
|
||||||
|
required
|
||||||
|
aria-required="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ do_action('woocommerce_edit_account_form_fields') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="card shadow-sm mb-4">
|
||||||
<label for="account_display_name" class="form-label">
|
<div class="card-header">
|
||||||
{{ __('Display name') }} <span class="text-danger" aria-hidden="true">*</span>
|
<h2 class="h5 mb-0">
|
||||||
<span class="visually-hidden">{{ __('Required') }}</span>
|
<i class="bi bi-shield-lock me-1" aria-hidden="true"></i>
|
||||||
</label>
|
{{ __('Password change') }}
|
||||||
<input type="text"
|
</h2>
|
||||||
class="form-control"
|
</div>
|
||||||
name="account_display_name"
|
<div class="card-body">
|
||||||
id="account_display_name"
|
<div class="mb-3">
|
||||||
aria-describedby="account_display_name_description"
|
<label for="password_current" class="form-label">
|
||||||
value="{{ user.display_name|esc_attr }}"
|
{{ __('Current password (leave blank to leave unchanged)') }}
|
||||||
required
|
</label>
|
||||||
aria-required="true" />
|
<input type="password"
|
||||||
<div id="account_display_name_description" class="form-text">
|
class="form-control"
|
||||||
{{ __('This will be how your name will be displayed in the account section and in reviews') }}
|
name="password_current"
|
||||||
|
id="password_current"
|
||||||
|
autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password_1" class="form-label">
|
||||||
|
{{ __('New password (leave blank to leave unchanged)') }}
|
||||||
|
</label>
|
||||||
|
<input type="password"
|
||||||
|
class="form-control"
|
||||||
|
name="password_1"
|
||||||
|
id="password_1"
|
||||||
|
autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-0">
|
||||||
|
<label for="password_2" class="form-label">
|
||||||
|
{{ __('Confirm new password') }}
|
||||||
|
</label>
|
||||||
|
<input type="password"
|
||||||
|
class="form-control"
|
||||||
|
name="password_2"
|
||||||
|
id="password_2"
|
||||||
|
autocomplete="off" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="account_email" class="form-label">
|
|
||||||
{{ __('Email address') }} <span class="text-danger" aria-hidden="true">*</span>
|
|
||||||
<span class="visually-hidden">{{ __('Required') }}</span>
|
|
||||||
</label>
|
|
||||||
<input type="email"
|
|
||||||
class="form-control"
|
|
||||||
name="account_email"
|
|
||||||
id="account_email"
|
|
||||||
autocomplete="email"
|
|
||||||
value="{{ user.user_email|esc_attr }}"
|
|
||||||
required
|
|
||||||
aria-required="true" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ do_action('woocommerce_edit_account_form_fields') }}
|
|
||||||
|
|
||||||
<fieldset class="mb-3">
|
|
||||||
<legend class="h5 border-bottom pb-2 mb-3">{{ __('Password change') }}</legend>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="password_current" class="form-label">
|
|
||||||
{{ __('Current password (leave blank to leave unchanged)') }}
|
|
||||||
</label>
|
|
||||||
<input type="password"
|
|
||||||
class="form-control"
|
|
||||||
name="password_current"
|
|
||||||
id="password_current"
|
|
||||||
autocomplete="off" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="password_1" class="form-label">
|
|
||||||
{{ __('New password (leave blank to leave unchanged)') }}
|
|
||||||
</label>
|
|
||||||
<input type="password"
|
|
||||||
class="form-control"
|
|
||||||
name="password_1"
|
|
||||||
id="password_1"
|
|
||||||
autocomplete="off" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="password_2" class="form-label">
|
|
||||||
{{ __('Confirm new password') }}
|
|
||||||
</label>
|
|
||||||
<input type="password"
|
|
||||||
class="form-control"
|
|
||||||
name="password_2"
|
|
||||||
id="password_2"
|
|
||||||
autocomplete="off" />
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
{{ do_action('woocommerce_edit_account_form') }}
|
{{ do_action('woocommerce_edit_account_form') }}
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{#
|
{#
|
||||||
# Edit Address Form (Bootstrap 5 Override)
|
# Edit Address Form (Bootstrap 5 Override)
|
||||||
#
|
#
|
||||||
# Address editing form with Bootstrap form styling.
|
# Address editing form wrapped in a Bootstrap card with
|
||||||
|
# contextual icon for billing/shipping.
|
||||||
#
|
#
|
||||||
# Expected context:
|
# Expected context:
|
||||||
# load_address - Address type ('billing' or 'shipping'), or empty
|
# load_address - Address type ('billing' or 'shipping'), or empty
|
||||||
@@ -14,36 +15,45 @@
|
|||||||
#}
|
#}
|
||||||
|
|
||||||
{% set page_title = load_address == 'billing' ? __('Billing address') : __('Shipping address') %}
|
{% set page_title = load_address == 'billing' ? __('Billing address') : __('Shipping address') %}
|
||||||
|
{% set address_icon = load_address == 'billing' ? 'bi-receipt' : 'bi-truck' %}
|
||||||
|
|
||||||
{{ do_action('woocommerce_before_edit_account_address_form') }}
|
{{ do_action('woocommerce_before_edit_account_address_form') }}
|
||||||
|
|
||||||
{% if not load_address %}
|
{% if not load_address %}
|
||||||
{% include 'myaccount/my-address.html.twig' %}
|
{% include 'myaccount/my-address.html.twig' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<form method="post" novalidate>
|
<div class="card shadow-sm">
|
||||||
<h2 class="h4 mb-4">{{ apply_filters('woocommerce_my_account_edit_address_title', page_title, load_address) }}</h2>
|
<div class="card-header">
|
||||||
|
<h2 class="h5 mb-0">
|
||||||
<div class="woocommerce-address-fields">
|
<i class="bi {{ address_icon }} me-1" aria-hidden="true"></i>
|
||||||
{{ do_action('woocommerce_before_edit_address_form_' ~ load_address) }}
|
{{ apply_filters('woocommerce_my_account_edit_address_title', page_title, load_address) }}
|
||||||
|
</h2>
|
||||||
<div class="woocommerce-address-fields__field-wrapper">
|
|
||||||
{% for key, field in address %}
|
|
||||||
{{ woocommerce_form_field(key, field, wc_get_post_data_by_key(key, field.value)) }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ do_action('woocommerce_after_edit_address_form_' ~ load_address) }}
|
|
||||||
|
|
||||||
<div class="mt-3">
|
|
||||||
<button type="submit" class="btn btn-primary" name="save_address" value="{{ __('Save address') }}">
|
|
||||||
<i class="bi bi-check-lg me-1" aria-hidden="true"></i>
|
|
||||||
{{ __('Save address') }}
|
|
||||||
</button>
|
|
||||||
{{ wp_nonce_field('woocommerce-edit_address', 'woocommerce-edit-address-nonce') }}
|
|
||||||
<input type="hidden" name="action" value="edit_address" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div class="card-body">
|
||||||
|
<form method="post" novalidate>
|
||||||
|
<div class="woocommerce-address-fields">
|
||||||
|
{{ do_action('woocommerce_before_edit_address_form_' ~ load_address) }}
|
||||||
|
|
||||||
|
<div class="woocommerce-address-fields__field-wrapper">
|
||||||
|
{% for key, field in address %}
|
||||||
|
{{ woocommerce_form_field(key, field, wc_get_post_data_by_key(key, field.value)) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ do_action('woocommerce_after_edit_address_form_' ~ load_address) }}
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<button type="submit" class="btn btn-primary" name="save_address" value="{{ __('Save address') }}">
|
||||||
|
<i class="bi bi-check-lg me-1" aria-hidden="true"></i>
|
||||||
|
{{ __('Save address') }}
|
||||||
|
</button>
|
||||||
|
{{ wp_nonce_field('woocommerce-edit_address', 'woocommerce-edit-address-nonce') }}
|
||||||
|
<input type="hidden" name="action" value="edit_address" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ do_action('woocommerce_after_edit_account_address_form') }}
|
{{ do_action('woocommerce_after_edit_account_address_form') }}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
{#
|
{#
|
||||||
# Lost Password Form (Bootstrap 5 Override)
|
# Lost Password Form (Bootstrap 5 Override)
|
||||||
#
|
#
|
||||||
# Form to request a password reset email.
|
# Form to request a password reset email, wrapped in a
|
||||||
|
# Bootstrap card for visual consistency.
|
||||||
#
|
#
|
||||||
# WooCommerce PHP equivalent: myaccount/form-lost-password.php
|
# WooCommerce PHP equivalent: myaccount/form-lost-password.php
|
||||||
#
|
#
|
||||||
@@ -11,37 +12,47 @@
|
|||||||
|
|
||||||
{{ do_action('woocommerce_before_lost_password_form') }}
|
{{ do_action('woocommerce_before_lost_password_form') }}
|
||||||
|
|
||||||
<form method="post" class="woocommerce-ResetPassword lost_reset_password">
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header">
|
||||||
<p class="mb-3">
|
<h2 class="h5 mb-0">
|
||||||
{{ apply_filters('woocommerce_lost_password_message', __('Lost your password? Please enter your username or email address. You will receive a link to create a new password via email.')) }}
|
<i class="bi bi-key me-1" aria-hidden="true"></i>
|
||||||
</p>
|
{{ __('Lost your password?') }}
|
||||||
|
</h2>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="user_login" class="form-label">
|
|
||||||
{{ __('Username or email') }} <span class="text-danger" aria-hidden="true">*</span>
|
|
||||||
<span class="visually-hidden">{{ __('Required') }}</span>
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
class="form-control"
|
|
||||||
name="user_login"
|
|
||||||
id="user_login"
|
|
||||||
autocomplete="username"
|
|
||||||
required
|
|
||||||
aria-required="true" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" class="woocommerce-ResetPassword lost_reset_password">
|
||||||
|
|
||||||
{{ do_action('woocommerce_lostpassword_form') }}
|
<p class="text-body-secondary mb-3">
|
||||||
|
{{ apply_filters('woocommerce_lost_password_message', __('Lost your password? Please enter your username or email address. You will receive a link to create a new password via email.')) }}
|
||||||
|
</p>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mb-3">
|
||||||
<input type="hidden" name="wc_reset_password" value="true" />
|
<label for="user_login" class="form-label">
|
||||||
<button type="submit" class="btn btn-primary" value="{{ __('Reset password') }}">
|
{{ __('Username or email') }} <span class="text-danger" aria-hidden="true">*</span>
|
||||||
{{ __('Reset password') }}
|
<span class="visually-hidden">{{ __('Required') }}</span>
|
||||||
</button>
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
name="user_login"
|
||||||
|
id="user_login"
|
||||||
|
autocomplete="username"
|
||||||
|
required
|
||||||
|
aria-required="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ do_action('woocommerce_lostpassword_form') }}
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<input type="hidden" name="wc_reset_password" value="true" />
|
||||||
|
<button type="submit" class="btn btn-primary" value="{{ __('Reset password') }}">
|
||||||
|
{{ __('Reset password') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ wp_nonce_field('lost_password', 'woocommerce-lost-password-nonce') }}
|
||||||
|
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{{ wp_nonce_field('lost_password', 'woocommerce-lost-password-nonce') }}
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{{ do_action('woocommerce_after_lost_password_form') }}
|
{{ do_action('woocommerce_after_lost_password_form') }}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
{#
|
{#
|
||||||
# Reset Password Form (Bootstrap 5 Override)
|
# Reset Password Form (Bootstrap 5 Override)
|
||||||
#
|
#
|
||||||
# Form to set a new password after clicking the reset link.
|
# Form to set a new password after clicking the reset link,
|
||||||
|
# wrapped in a Bootstrap card for visual consistency.
|
||||||
#
|
#
|
||||||
# Expected context:
|
# Expected context:
|
||||||
# args - Array with 'key' and 'login' values
|
# args - Array with 'key' and 'login' values
|
||||||
@@ -14,55 +15,65 @@
|
|||||||
|
|
||||||
{{ do_action('woocommerce_before_reset_password_form') }}
|
{{ do_action('woocommerce_before_reset_password_form') }}
|
||||||
|
|
||||||
<form method="post" class="woocommerce-ResetPassword lost_reset_password">
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header">
|
||||||
<p class="mb-3">
|
<h2 class="h5 mb-0">
|
||||||
{{ apply_filters('woocommerce_reset_password_message', __('Enter a new password below.')) }}
|
<i class="bi bi-shield-lock me-1" aria-hidden="true"></i>
|
||||||
</p>
|
{{ __('Set new password') }}
|
||||||
|
</h2>
|
||||||
<div class="row g-3 mb-3">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<label for="password_1" class="form-label">
|
|
||||||
{{ __('New password') }} <span class="text-danger" aria-hidden="true">*</span>
|
|
||||||
<span class="visually-hidden">{{ __('Required') }}</span>
|
|
||||||
</label>
|
|
||||||
<input type="password"
|
|
||||||
class="form-control"
|
|
||||||
name="password_1"
|
|
||||||
id="password_1"
|
|
||||||
autocomplete="new-password"
|
|
||||||
required
|
|
||||||
aria-required="true" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<label for="password_2" class="form-label">
|
|
||||||
{{ __('Re-enter new password') }} <span class="text-danger" aria-hidden="true">*</span>
|
|
||||||
<span class="visually-hidden">{{ __('Required') }}</span>
|
|
||||||
</label>
|
|
||||||
<input type="password"
|
|
||||||
class="form-control"
|
|
||||||
name="password_2"
|
|
||||||
id="password_2"
|
|
||||||
autocomplete="new-password"
|
|
||||||
required
|
|
||||||
aria-required="true" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" class="woocommerce-ResetPassword lost_reset_password">
|
||||||
|
|
||||||
<input type="hidden" name="reset_key" value="{{ args.key|esc_attr }}" />
|
<p class="text-body-secondary mb-3">
|
||||||
<input type="hidden" name="reset_login" value="{{ args.login|esc_attr }}" />
|
{{ apply_filters('woocommerce_reset_password_message', __('Enter a new password below.')) }}
|
||||||
|
</p>
|
||||||
|
|
||||||
{{ do_action('woocommerce_resetpassword_form') }}
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label for="password_1" class="form-label">
|
||||||
|
{{ __('New password') }} <span class="text-danger" aria-hidden="true">*</span>
|
||||||
|
<span class="visually-hidden">{{ __('Required') }}</span>
|
||||||
|
</label>
|
||||||
|
<input type="password"
|
||||||
|
class="form-control"
|
||||||
|
name="password_1"
|
||||||
|
id="password_1"
|
||||||
|
autocomplete="new-password"
|
||||||
|
required
|
||||||
|
aria-required="true" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label for="password_2" class="form-label">
|
||||||
|
{{ __('Re-enter new password') }} <span class="text-danger" aria-hidden="true">*</span>
|
||||||
|
<span class="visually-hidden">{{ __('Required') }}</span>
|
||||||
|
</label>
|
||||||
|
<input type="password"
|
||||||
|
class="form-control"
|
||||||
|
name="password_2"
|
||||||
|
id="password_2"
|
||||||
|
autocomplete="new-password"
|
||||||
|
required
|
||||||
|
aria-required="true" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<input type="hidden" name="reset_key" value="{{ args.key|esc_attr }}" />
|
||||||
<input type="hidden" name="wc_reset_password" value="true" />
|
<input type="hidden" name="reset_login" value="{{ args.login|esc_attr }}" />
|
||||||
<button type="submit" class="btn btn-primary" value="{{ __('Save') }}">
|
|
||||||
{{ __('Save') }}
|
{{ do_action('woocommerce_resetpassword_form') }}
|
||||||
</button>
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<input type="hidden" name="wc_reset_password" value="true" />
|
||||||
|
<button type="submit" class="btn btn-primary" value="{{ __('Save') }}">
|
||||||
|
{{ __('Save') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ wp_nonce_field('reset_password', 'woocommerce-reset-password-nonce') }}
|
||||||
|
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{{ wp_nonce_field('reset_password', 'woocommerce-reset-password-nonce') }}
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{{ do_action('woocommerce_after_reset_password_form') }}
|
{{ do_action('woocommerce_after_reset_password_form') }}
|
||||||
@@ -16,8 +16,13 @@
|
|||||||
|
|
||||||
{{ do_action('woocommerce_before_lost_password_confirmation_message') }}
|
{{ do_action('woocommerce_before_lost_password_confirmation_message') }}
|
||||||
|
|
||||||
<p>
|
<p class="text-body-secondary">
|
||||||
{{ apply_filters('woocommerce_lost_password_confirmation_message', __('A password reset email has been sent to the email address on file for your account, but may take several minutes to show up in your inbox. Please wait at least 10 minutes before attempting another reset.')) }}
|
{{ apply_filters('woocommerce_lost_password_confirmation_message', __('A password reset email has been sent to the email address on file for your account, but may take several minutes to show up in your inbox. Please wait at least 10 minutes before attempting another reset.')) }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<a href="{{ wc_get_page_permalink('myaccount')|esc_url }}" class="btn btn-outline-primary">
|
||||||
|
<i class="bi bi-arrow-left me-1" aria-hidden="true"></i>
|
||||||
|
{{ __('Back to login') }}
|
||||||
|
</a>
|
||||||
|
|
||||||
{{ do_action('woocommerce_after_lost_password_confirmation_message') }}
|
{{ do_action('woocommerce_after_lost_password_confirmation_message') }}
|
||||||
@@ -13,11 +13,11 @@
|
|||||||
{{ do_action('woocommerce_before_my_account') }}
|
{{ do_action('woocommerce_before_my_account') }}
|
||||||
|
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-auto">
|
||||||
{% include 'myaccount/navigation.html.twig' %}
|
{% include 'myaccount/navigation.html.twig' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-9">
|
<div class="col-lg">
|
||||||
<div class="woocommerce-MyAccount-content">
|
<div class="woocommerce-MyAccount-content">
|
||||||
{{ do_action('woocommerce_account_content') }}
|
{{ do_action('woocommerce_account_content') }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
# My Account Navigation (Bootstrap 5 Override)
|
# My Account Navigation (Bootstrap 5 Override)
|
||||||
#
|
#
|
||||||
# Renders the sidebar navigation for the My Account area
|
# Renders the sidebar navigation for the My Account area
|
||||||
# using Bootstrap list-group component.
|
# using Bootstrap list-group component with icons.
|
||||||
|
# Responsive: offcanvas on mobile, sticky sidebar on desktop.
|
||||||
#
|
#
|
||||||
# WooCommerce PHP equivalent: myaccount/navigation.php
|
# WooCommerce PHP equivalent: myaccount/navigation.php
|
||||||
#
|
#
|
||||||
@@ -10,14 +11,53 @@
|
|||||||
# @since 0.1.0
|
# @since 0.1.0
|
||||||
#}
|
#}
|
||||||
|
|
||||||
<nav class="woocommerce-MyAccount-navigation" aria-label="{{ __('Account navigation') }}">
|
{% set endpoint_icons = {
|
||||||
<div class="list-group">
|
'dashboard': 'bi-speedometer2',
|
||||||
{% for endpoint, label in wc_get_account_menu_items() %}
|
'orders': 'bi-bag',
|
||||||
<a href="{{ wc_get_account_endpoint_url(endpoint)|esc_url }}"
|
'downloads': 'bi-download',
|
||||||
class="list-group-item list-group-item-action{% if wc_get_account_menu_item_classes(endpoint) matches '/is-active/' %} active{% endif %}"
|
'edit-address': 'bi-geo-alt',
|
||||||
{% if wc_get_account_menu_item_classes(endpoint) matches '/is-active/' %}aria-current="page"{% endif %}>
|
'payment-methods': 'bi-credit-card',
|
||||||
{{ label|esc_html }}
|
'edit-account': 'bi-person-gear',
|
||||||
</a>
|
'customer-logout': 'bi-box-arrow-right',
|
||||||
{% endfor %}
|
} %}
|
||||||
|
|
||||||
|
{{ do_action('woocommerce_before_account_navigation') }}
|
||||||
|
|
||||||
|
<button class="btn btn-outline-secondary w-100 mb-3 d-lg-none"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="offcanvas"
|
||||||
|
data-bs-target="#accountNav"
|
||||||
|
aria-controls="accountNav">
|
||||||
|
<i class="bi bi-list me-1" aria-hidden="true"></i>
|
||||||
|
{{ __('Account menu') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="offcanvas-lg offcanvas-start" id="accountNav" tabindex="-1"
|
||||||
|
aria-labelledby="accountNavLabel">
|
||||||
|
<div class="offcanvas-header d-lg-none">
|
||||||
|
<h5 class="offcanvas-title" id="accountNavLabel">{{ __('Account menu') }}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"
|
||||||
|
data-bs-target="#accountNav" aria-label="{{ __('Close') }}"></button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
<div class="offcanvas-body p-0">
|
||||||
|
<nav class="woocommerce-MyAccount-navigation position-sticky"
|
||||||
|
style="top: 5rem;"
|
||||||
|
aria-label="{{ __('Account navigation') }}">
|
||||||
|
<div class="list-group">
|
||||||
|
{% for endpoint, label in wc_get_account_menu_items() %}
|
||||||
|
{% set is_active = wc_get_account_menu_item_classes(endpoint) matches '/is-active/' %}
|
||||||
|
<a href="{{ wc_get_account_endpoint_url(endpoint)|esc_url }}"
|
||||||
|
class="list-group-item list-group-item-action d-flex align-items-center{% if is_active %} active{% endif %}"
|
||||||
|
{% if is_active %}aria-current="page"{% endif %}>
|
||||||
|
{% if endpoint_icons[endpoint] is defined %}
|
||||||
|
<i class="bi {{ endpoint_icons[endpoint] }} me-2" aria-hidden="true"></i>
|
||||||
|
{% endif %}
|
||||||
|
{{ label|esc_html }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ do_action('woocommerce_after_account_navigation') }}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
{#
|
{#
|
||||||
# View Order (Bootstrap 5 Override)
|
# View Order (Bootstrap 5 Override)
|
||||||
#
|
#
|
||||||
# Shows details of a specific order on the account page.
|
# Shows details of a specific order on the account page
|
||||||
|
# with summary card, status badge, and order notes.
|
||||||
# HPOS compatible: uses WC_Order methods only.
|
# HPOS compatible: uses WC_Order methods only.
|
||||||
#
|
#
|
||||||
# Expected context:
|
# Expected context:
|
||||||
@@ -16,29 +17,60 @@
|
|||||||
|
|
||||||
{% set notes = order.get_customer_order_notes() %}
|
{% set notes = order.get_customer_order_notes() %}
|
||||||
|
|
||||||
<p>
|
<div class="card shadow-sm mb-4">
|
||||||
{{ __('Order #%1$s was placed on %2$s and is currently %3$s.')|format(
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
'<mark class="order-number">' ~ order.get_order_number() ~ '</mark>',
|
<h2 class="h5 mb-0">
|
||||||
'<mark class="order-date">' ~ wc_format_datetime(order.get_date_created()) ~ '</mark>',
|
{{ __('Order #%s')|format(order.get_order_number()) }}
|
||||||
'<mark class="order-status">' ~ wc_get_order_status_name(order.get_status()) ~ '</mark>'
|
</h2>
|
||||||
)|wp_kses_post }}
|
{% include 'components/status-badge.html.twig' with { status: order.get_status() } %}
|
||||||
</p>
|
</div>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span class="text-body-secondary">{{ __('Date') }}</span>
|
||||||
|
<time datetime="{{ order.get_date_created().date('c')|esc_attr }}">
|
||||||
|
{{ wc_format_datetime(order.get_date_created())|esc_html }}
|
||||||
|
</time>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span class="text-body-secondary">{{ __('Status') }}</span>
|
||||||
|
{% include 'components/status-badge.html.twig' with { status: order.get_status() } %}
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span class="text-body-secondary">{{ __('Total') }}</span>
|
||||||
|
<strong>{{ order.get_formatted_order_total()|wp_kses_post }}</strong>
|
||||||
|
</li>
|
||||||
|
{% if order.get_payment_method_title() %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span class="text-body-secondary">{{ __('Payment method') }}</span>
|
||||||
|
<span>{{ order.get_payment_method_title()|esc_html }}</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if notes %}
|
{% if notes %}
|
||||||
<h2 class="h5 mt-4 mb-3">{{ __('Order updates') }}</h2>
|
<div class="card shadow-sm mb-4">
|
||||||
<div class="list-group mb-4">
|
<div class="card-header">
|
||||||
{% for note in notes %}
|
<h3 class="h6 mb-0">
|
||||||
<div class="list-group-item">
|
<i class="bi bi-chat-left-text me-1" aria-hidden="true"></i>
|
||||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
{{ __('Order updates') }}
|
||||||
<small class="text-body-secondary">
|
</h3>
|
||||||
{{ date_i18n(__('l jS \\o\\f F Y, h:ia'), strtotime(note.comment_date)) }}
|
</div>
|
||||||
</small>
|
<div class="list-group list-group-flush">
|
||||||
|
{% for note in notes %}
|
||||||
|
<div class="list-group-item">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
|
<small class="text-body-secondary">
|
||||||
|
<i class="bi bi-clock me-1" aria-hidden="true"></i>
|
||||||
|
{{ date_i18n(__('l jS \\o\\f F Y, h:ia'), strtotime(note.comment_date)) }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="mb-0">
|
||||||
|
{{ wpautop(wptexturize(note.comment_content))|raw }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-0">
|
{% endfor %}
|
||||||
{{ wpautop(wptexturize(note.comment_content))|raw }}
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user