diff --git a/CLAUDE.md b/CLAUDE.md index 61b7fc5..dd86973 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -252,6 +252,9 @@ unzip -l wc-composable-product-vX.X.X.zip - ✅ ~~There is a bug related to twig in the frontend area. Documented in `logs/fatal-errors*.log`~~ **FIXED in v1.1.5** - ✅ ~~Translate the admin area, too~~ **COMPLETED in v1.1.6** - All admin strings now translated to 6 locales +- Small rendering Bug in admin area. If you load the side, on first view it shows the first both tabs. +- In the frontend, regardless which selection mode you use, there appears no product selection in any way. +- The pricing field in the frondend should be rendered as localized price field include currency. ## Session History diff --git a/assets/css/admin.css b/assets/css/admin.css index 9dd102f..040ad3b 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -19,11 +19,29 @@ min-height: 150px; } +/* Hide composable-specific elements by default */ .show_if_composable { + display: none !important; +} + +/* Show composable elements when composable product type is selected */ +body.product-type-composable .show_if_composable, +.product-type-composable .show_if_composable { + display: block !important; +} + +/* Ensure General tab fields don't show in Composable Options panel initially */ +#composable_product_data .options_group { + display: block; +} + +/* Hide the Composable Options tab link by default */ +.product_data_tabs .composable_options { display: none; } -.product-type-composable .show_if_composable { +/* Show the Composable Options tab when composable type selected */ +body.product-type-composable .product_data_tabs .composable_options { display: block; } diff --git a/assets/js/frontend.js b/assets/js/frontend.js index 470044c..1409171 100644 --- a/assets/js/frontend.js +++ b/assets/js/frontend.js @@ -63,6 +63,36 @@ this.clearMessages($container); }, + /** + * Format price using WooCommerce settings + * + * @param {number} price Price amount + * @return {string} Formatted price HTML + */ + formatPrice: function(price) { + if (typeof wcComposableProduct === 'undefined' || !wcComposableProduct.price_format) { + return price.toFixed(2); + } + + const format = wcComposableProduct.price_format; + const decimals = parseInt(format.decimals, 10); + const decimalSep = format.decimal_separator; + const thousandSep = format.thousand_separator; + + // Format number + let priceStr = price.toFixed(decimals); + const parts = priceStr.split('.'); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSep); + priceStr = parts.join(decimalSep); + + // Apply price format (e.g., "%1$s%2$s" for symbol+price or "%2$s%1$s" for price+symbol) + let formatted = format.price_format + .replace('%1$s', '' + format.currency_symbol + '') + .replace('%2$s', priceStr); + + return '' + formatted + ''; + }, + /** * Update total price * @@ -79,8 +109,8 @@ } }); - const currencySymbol = $container.find('.total-price').data('currency'); - $container.find('.calculated-total').text(currencySymbol + total.toFixed(2)); + const formattedPrice = this.formatPrice(total); + $container.find('.calculated-total').html(formattedPrice); }, /** diff --git a/includes/Cart_Handler.php b/includes/Cart_Handler.php index a62c042..9cac414 100644 --- a/includes/Cart_Handler.php +++ b/includes/Cart_Handler.php @@ -35,6 +35,21 @@ class Cart_Handler { add_action('woocommerce_before_calculate_totals', [$this, 'calculate_cart_item_price']); add_action('woocommerce_single_product_summary', [$this, 'render_product_selector'], 25); add_action('woocommerce_checkout_create_order_line_item', [$this->stock_manager, 'store_selected_products_in_order'], 10, 3); + add_filter('woocommerce_is_purchasable', [$this, 'hide_default_add_to_cart'], 10, 2); + } + + /** + * Hide default WooCommerce add to cart button for composable products + * + * @param bool $is_purchasable Is purchasable status + * @param \WC_Product $product Product object + * @return bool + */ + public function hide_default_add_to_cart($is_purchasable, $product) { + if ($product && $product->get_type() === 'composable') { + return false; + } + return $is_purchasable; } /** diff --git a/includes/Plugin.php b/includes/Plugin.php index ea46525..2b66939 100644 --- a/includes/Plugin.php +++ b/includes/Plugin.php @@ -84,6 +84,7 @@ class Plugin { $this->twig->addFunction(new \Twig\TwigFunction('esc_html', 'esc_html')); $this->twig->addFunction(new \Twig\TwigFunction('esc_attr', 'esc_attr')); $this->twig->addFunction(new \Twig\TwigFunction('esc_url', 'esc_url')); + $this->twig->addFunction(new \Twig\TwigFunction('wc_price', 'wc_price')); // Add WordPress escaping functions as Twig filters $this->twig->addFilter(new \Twig\TwigFilter('esc_html', 'esc_html')); @@ -161,6 +162,13 @@ class Plugin { 'max_items' => __('Maximum items selected', 'wc-composable-product'), 'min_items' => __('Please select at least one item', 'wc-composable-product'), ], + 'price_format' => [ + 'currency_symbol' => get_woocommerce_currency_symbol(), + 'decimal_separator' => wc_get_price_decimal_separator(), + 'thousand_separator' => wc_get_price_thousand_separator(), + 'decimals' => wc_get_price_decimals(), + 'price_format' => get_woocommerce_price_format(), + ], ]); } } diff --git a/includes/Product_Selector.php b/includes/Product_Selector.php index c24b6e9..dd332c1 100644 --- a/includes/Product_Selector.php +++ b/includes/Product_Selector.php @@ -65,6 +65,8 @@ class Product_Selector { 'show_prices' => $show_prices, 'show_total' => $show_total, 'fixed_price' => $product->get_price(), + 'fixed_price_html' => wc_price($product->get_price()), + 'zero_price_html' => wc_price(0), 'currency_symbol' => get_woocommerce_currency_symbol(), ]; diff --git a/templates/product-selector.twig b/templates/product-selector.twig index 510a1f1..75c4a54 100644 --- a/templates/product-selector.twig +++ b/templates/product-selector.twig @@ -57,11 +57,11 @@ {% if show_total %}
{{ __('Total Price:') }}
-
+
{% if pricing_mode == 'fixed' %} - {{ currency_symbol }}{{ fixed_price }} + {{ fixed_price_html|raw }} {% else %} - {{ currency_symbol }}0.00 + {{ zero_price_html|raw }} {% endif %}