feat: implement header/footer visibility controls for checkout and thankyou pages

- Created LayoutWrapper component to conditionally render header/footer based on route
- Created MinimalHeader component (logo only)
- Created MinimalFooter component (trust badges + policy links)
- Created usePageVisibility hook to get visibility settings per page
- Wrapped ClassicLayout with LayoutWrapper for conditional rendering
- Header/footer visibility now controlled directly in React SPA
- Settings: show/minimal/hide for both header and footer
- Background color support for checkout and thankyou pages
This commit is contained in:
Dwindi Ramadhana
2025-12-25 22:20:48 +07:00
parent c37ecb8e96
commit 9ac09582d2
104 changed files with 14801 additions and 1213 deletions

View File

@@ -137,8 +137,8 @@ class Assets {
'mode' => 'disabled',
'layout' => 'modern',
'colors' => [
'primary' => '#3B82F6',
'secondary' => '#8B5CF6',
'primary' => get_option('woonoow_primary_color', '#111827'), // Gray-900 from Store Details
'secondary' => '#6B7280', // Gray-500
'accent' => '#10B981',
],
'typography' => [
@@ -147,6 +147,13 @@ class Assets {
];
$theme_settings = array_replace_recursive($default_settings, $spa_settings);
// Get appearance settings and preload them
$appearance_settings = get_option('woonoow_appearance_settings', []);
if (empty($appearance_settings)) {
// Use defaults from AppearanceController
$appearance_settings = \WooNooW\Admin\AppearanceController::get_default_settings();
}
// Get WooCommerce currency settings
$currency_settings = [
'code' => get_woocommerce_currency(),
@@ -157,17 +164,52 @@ class Assets {
'decimals' => wc_get_price_decimals(),
];
// Get store logo from WooNooW Store Details (Settings > Store Details)
$logo_url = get_option('woonoow_store_logo', '');
// Get user billing/shipping data if logged in
$user_data = [
'isLoggedIn' => is_user_logged_in(),
'id' => get_current_user_id(),
];
if (is_user_logged_in()) {
$customer = new \WC_Customer(get_current_user_id());
$user_data['email'] = $customer->get_email();
$user_data['billing'] = [
'first_name' => $customer->get_billing_first_name(),
'last_name' => $customer->get_billing_last_name(),
'email' => $customer->get_billing_email(),
'phone' => $customer->get_billing_phone(),
'address_1' => $customer->get_billing_address_1(),
'city' => $customer->get_billing_city(),
'state' => $customer->get_billing_state(),
'postcode' => $customer->get_billing_postcode(),
'country' => $customer->get_billing_country(),
];
$user_data['shipping'] = [
'first_name' => $customer->get_shipping_first_name(),
'last_name' => $customer->get_shipping_last_name(),
'address_1' => $customer->get_shipping_address_1(),
'city' => $customer->get_shipping_city(),
'state' => $customer->get_shipping_state(),
'postcode' => $customer->get_shipping_postcode(),
'country' => $customer->get_shipping_country(),
];
}
$config = [
'apiUrl' => rest_url('woonoow/v1'),
'apiRoot' => rest_url('woonoow/v1'),
'nonce' => wp_create_nonce('wp_rest'),
'siteUrl' => get_site_url(),
'siteTitle' => get_bloginfo('name'),
'user' => [
'isLoggedIn' => is_user_logged_in(),
'id' => get_current_user_id(),
],
'storeName' => get_bloginfo('name'),
'storeLogo' => $logo_url,
'user' => $user_data,
'theme' => $theme_settings,
'currency' => $currency_settings,
'appearanceSettings' => $appearance_settings,
];
?>

View File

@@ -11,24 +11,51 @@ use WP_Error;
*/
class CartController {
/**
* Initialize controller
*/
public static function init() {
// Bypass cookie authentication for cart endpoints to allow guest users
add_filter('rest_authentication_errors', function($result) {
// If already authenticated or error, return as is
if (!empty($result)) {
return $result;
}
// Check if this is a cart endpoint
$request_uri = $_SERVER['REQUEST_URI'] ?? '';
if (strpos($request_uri, '/woonoow/v1/cart') !== false) {
error_log('WooNooW Cart: Bypassing authentication for cart endpoint');
return true; // Allow access
}
return $result;
}, 100);
}
/**
* Register REST API routes
*/
public static function register_routes() {
error_log('WooNooW CartController::register_routes() START');
$namespace = 'woonoow/v1';
// Get cart
register_rest_route($namespace, '/cart', [
$result = register_rest_route($namespace, '/cart', [
'methods' => 'GET',
'callback' => [__CLASS__, 'get_cart'],
'permission_callback' => '__return_true',
]);
error_log('WooNooW CartController: GET /cart registered: ' . ($result ? 'SUCCESS' : 'FAILED'));
// Add to cart
register_rest_route($namespace, '/cart/add', [
$result = register_rest_route($namespace, '/cart/add', [
'methods' => 'POST',
'callback' => [__CLASS__, 'add_to_cart'],
'permission_callback' => '__return_true',
'permission_callback' => function() {
// Allow both logged-in and guest users
return true;
},
'args' => [
'product_id' => [
'required' => true,
@@ -46,12 +73,13 @@ class CartController {
],
],
]);
error_log('WooNooW CartController: POST /cart/add registered: ' . ($result ? 'SUCCESS' : 'FAILED'));
// Update cart item
register_rest_route($namespace, '/cart/update', [
'methods' => 'POST',
'callback' => [__CLASS__, 'update_cart'],
'permission_callback' => '__return_true',
'permission_callback' => function() { return true; },
'args' => [
'cart_item_key' => [
'required' => true,
@@ -68,7 +96,7 @@ class CartController {
register_rest_route($namespace, '/cart/remove', [
'methods' => 'POST',
'callback' => [__CLASS__, 'remove_from_cart'],
'permission_callback' => '__return_true',
'permission_callback' => function() { return true; },
'args' => [
'cart_item_key' => [
'required' => true,
@@ -81,7 +109,7 @@ class CartController {
register_rest_route($namespace, '/cart/apply-coupon', [
'methods' => 'POST',
'callback' => [__CLASS__, 'apply_coupon'],
'permission_callback' => '__return_true',
'permission_callback' => function() { return true; },
'args' => [
'coupon_code' => [
'required' => true,
@@ -94,7 +122,7 @@ class CartController {
register_rest_route($namespace, '/cart/remove-coupon', [
'methods' => 'POST',
'callback' => [__CLASS__, 'remove_coupon'],
'permission_callback' => '__return_true',
'permission_callback' => function() { return true; },
'args' => [
'coupon_code' => [
'required' => true,
@@ -123,28 +151,119 @@ class CartController {
$quantity = $request->get_param('quantity');
$variation_id = $request->get_param('variation_id');
// Initialize WooCommerce session for guest users
if (!WC()->session->has_session()) {
WC()->session->set_customer_session_cookie(true);
error_log("WooNooW Cart: Adding product {$product_id} (variation: {$variation_id}) qty: {$quantity}");
// Check if WooCommerce is available
if (!function_exists('WC')) {
error_log('WooNooW Cart Error: WooCommerce not loaded');
return new WP_Error('wc_not_loaded', 'WooCommerce is not loaded', ['status' => 500]);
}
// Initialize WooCommerce session and cart for REST API requests
// WooCommerce doesn't auto-initialize these for REST API calls
if (!WC()->session) {
error_log('WooNooW Cart: Initializing WC session for REST API');
WC()->initialize_session();
}
if (!WC()->cart) {
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
error_log('WooNooW Cart: Initializing WC cart for REST API');
WC()->initialize_cart();
}
// Set session cookie for guest users
if (!WC()->session->has_session()) {
WC()->session->set_customer_session_cookie(true);
error_log('WooNooW Cart: Session cookie set for guest user');
}
error_log('WooNooW Cart: WC Session and Cart initialized successfully');
// Validate product
$product = wc_get_product($product_id);
if (!$product) {
error_log("WooNooW Cart Error: Product {$product_id} not found");
return new WP_Error('invalid_product', 'Product not found', ['status' => 404]);
}
// Add to cart
$cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, $variation_id);
error_log("WooNooW Cart: Product validated - {$product->get_name()} (Type: {$product->get_type()})");
// For variable products, validate the variation and get attributes
$variation_attributes = [];
if ($variation_id > 0) {
$variation = wc_get_product($variation_id);
if (!$variation) {
error_log("WooNooW Cart Error: Variation {$variation_id} not found");
return new WP_Error('invalid_variation', "Variation {$variation_id} not found", ['status' => 404]);
}
if ($variation->get_parent_id() != $product_id) {
error_log("WooNooW Cart Error: Variation {$variation_id} does not belong to product {$product_id}");
return new WP_Error('invalid_variation', "Variation does not belong to this product", ['status' => 400]);
}
if (!$variation->is_purchasable() || !$variation->is_in_stock()) {
error_log("WooNooW Cart Error: Variation {$variation_id} is not purchasable or out of stock");
return new WP_Error('variation_not_available', "This variation is not available for purchase", ['status' => 400]);
}
// Get variation attributes from post meta
// WooCommerce stores variation attributes as post meta with 'attribute_' prefix
$variation_attributes = [];
// Get parent product to know which attributes to look for
$parent_product = wc_get_product($product_id);
$parent_attributes = $parent_product->get_attributes();
error_log("WooNooW Cart: Parent product attributes: " . print_r(array_keys($parent_attributes), true));
// For each parent attribute, get the value from variation post meta
foreach ($parent_attributes as $attribute) {
if ($attribute->get_variation()) {
$attribute_name = $attribute->get_name();
$meta_key = 'attribute_' . $attribute_name;
// Get the value from post meta
$attribute_value = get_post_meta($variation_id, $meta_key, true);
error_log("WooNooW Cart: Checking attribute {$attribute_name} (meta key: {$meta_key}): {$attribute_value}");
if (!empty($attribute_value)) {
// WooCommerce expects lowercase attribute names
$wc_attribute_key = 'attribute_' . strtolower($attribute_name);
$variation_attributes[$wc_attribute_key] = $attribute_value;
}
}
}
error_log("WooNooW Cart: Variation validated - {$variation->get_name()}");
error_log("WooNooW Cart: Variation attributes extracted: " . print_r($variation_attributes, true));
}
// Clear any existing notices before adding to cart
wc_clear_notices();
// Add to cart with variation attributes
error_log("WooNooW Cart: Calling WC()->cart->add_to_cart({$product_id}, {$quantity}, {$variation_id}, attributes)");
$cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, $variation_id, $variation_attributes);
if (!$cart_item_key) {
return new WP_Error('add_to_cart_failed', 'Failed to add product to cart', ['status' => 400]);
// Get WooCommerce notices to provide better error message
$notices = wc_get_notices('error');
$error_messages = [];
foreach ($notices as $notice) {
$error_messages[] = is_array($notice) ? $notice['notice'] : $notice;
}
$error_message = !empty($error_messages) ? implode(', ', $error_messages) : 'Failed to add product to cart';
wc_clear_notices(); // Clear notices after reading
error_log("WooNooW Cart Error: add_to_cart returned false - {$error_message}");
error_log("WooNooW Cart Error: All WC notices: " . print_r($notices, true));
return new WP_Error('add_to_cart_failed', $error_message, ['status' => 400]);
}
error_log("WooNooW Cart: Product added successfully - Key: {$cart_item_key}");
return new WP_REST_Response([
'message' => 'Product added to cart',
'cart_item_key' => $cart_item_key,

View File

@@ -0,0 +1,91 @@
<?php
namespace WooNooW\Frontend;
class PageAppearance {
private $settings;
public function __construct() {
add_action('wp_head', [$this, 'add_appearance_styles'], 999);
}
private function get_appearance_settings() {
if ($this->settings === null) {
$this->settings = get_option('woonoow_appearance_settings', []);
}
return $this->settings;
}
public function add_appearance_styles() {
// Only inject styles on SPA pages (shop archive)
if (!is_post_type_archive('product') && !is_shop()) {
return;
}
?>
<style id="woonoow-page-appearance">
/* Header visibility styles */
body.woonoow-header-hide header,
body.woonoow-header-hide .site-header,
body.woonoow-header-hide #masthead {
display: none !important;
}
body.woonoow-header-minimal header,
body.woonoow-header-minimal .site-header,
body.woonoow-header-minimal #masthead {
padding: 1rem 0 !important;
box-shadow: none !important;
border-bottom: 1px solid #e5e7eb;
}
body.woonoow-header-minimal header .site-navigation,
body.woonoow-header-minimal .site-header .site-navigation,
body.woonoow-header-minimal #masthead .site-navigation,
body.woonoow-header-minimal header nav,
body.woonoow-header-minimal .site-header nav,
body.woonoow-header-minimal #masthead nav {
display: none !important;
}
body.woonoow-header-minimal header .header-widgets,
body.woonoow-header-minimal .site-header .header-widgets,
body.woonoow-header-minimal #masthead .header-widgets {
display: none !important;
}
/* Footer visibility styles */
body.woonoow-footer-hide footer,
body.woonoow-footer-hide .site-footer,
body.woonoow-footer-hide #colophon {
display: none !important;
}
body.woonoow-footer-minimal footer,
body.woonoow-footer-minimal .site-footer,
body.woonoow-footer-minimal #colophon {
padding: 2rem 0 !important;
background: #f9fafb !important;
border-top: 1px solid #e5e7eb;
}
body.woonoow-footer-minimal footer .footer-widgets,
body.woonoow-footer-minimal .site-footer .footer-widgets,
body.woonoow-footer-minimal #colophon .footer-widgets,
body.woonoow-footer-minimal footer .widget-area,
body.woonoow-footer-minimal .site-footer .widget-area,
body.woonoow-footer-minimal #colophon .widget-area {
display: none !important;
}
body.woonoow-footer-minimal footer .site-info,
body.woonoow-footer-minimal .site-footer .site-info,
body.woonoow-footer-minimal #colophon .site-info {
text-align: center;
font-size: 0.875rem;
color: #6b7280;
}
</style>
<?php
}
}

View File

@@ -100,6 +100,8 @@ class ShopController {
$orderby = $request->get_param('orderby');
$order = $request->get_param('order');
$slug = $request->get_param('slug');
$include = $request->get_param('include');
$exclude = $request->get_param('exclude');
$args = [
'post_type' => 'product',
@@ -115,12 +117,27 @@ class ShopController {
$args['name'] = $slug;
}
// Add include filter (specific product IDs)
if (!empty($include)) {
$ids = array_map('intval', explode(',', $include));
$args['post__in'] = $ids;
$args['orderby'] = 'post__in'; // Maintain order of IDs
}
// Add exclude filter (exclude specific product IDs)
if (!empty($exclude)) {
$ids = array_map('intval', explode(',', $exclude));
$args['post__not_in'] = $ids;
}
// Add category filter
if (!empty($category)) {
// Check if category is numeric (ID) or string (slug)
$field = is_numeric($category) ? 'term_id' : 'slug';
$args['tax_query'] = [
[
'taxonomy' => 'product_cat',
'field' => 'slug',
'field' => $field,
'terms' => $category,
],
];
@@ -251,6 +268,9 @@ class ShopController {
'type' => $product->get_type(),
'image' => wp_get_attachment_url($product->get_image_id()),
'permalink' => get_permalink($product->get_id()),
'categories' => wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'all']),
'virtual' => $product->is_virtual(),
'downloadable' => $product->is_downloadable(),
];
// Add detailed info if requested
@@ -258,7 +278,6 @@ class ShopController {
$data['description'] = $product->get_description();
$data['short_description'] = $product->get_short_description();
$data['sku'] = $product->get_sku();
$data['categories'] = wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'all']);
$data['tags'] = wp_get_post_terms($product->get_id(), 'product_tag', ['fields' => 'names']);
// Gallery images
@@ -329,9 +348,25 @@ class ShopController {
$variation_obj = wc_get_product($variation['variation_id']);
if ($variation_obj) {
// Get attributes directly from post meta (most reliable)
$attributes = [];
$variation_id = $variation['variation_id'];
// Query all post meta for this variation
global $wpdb;
$meta_rows = $wpdb->get_results($wpdb->prepare(
"SELECT meta_key, meta_value FROM {$wpdb->postmeta}
WHERE post_id = %d AND meta_key LIKE 'attribute_%%'",
$variation_id
));
foreach ($meta_rows as $row) {
$attributes[$row->meta_key] = $row->meta_value;
}
$variations[] = [
'id' => $variation['variation_id'],
'attributes' => $variation['attributes'],
'id' => $variation_id,
'attributes' => $attributes,
'price' => $variation_obj->get_price(),
'regular_price' => $variation_obj->get_regular_price(),
'sale_price' => $variation_obj->get_sale_price(),