fix: resolve container width issues, spa redirects, and appearance settings overwrite. feat: enhance order/sub details and newsletter layout

This commit is contained in:
Dwindi Ramadhana
2026-02-05 00:09:40 +07:00
parent a0b5f8496d
commit 5f08c18ec7
77 changed files with 7027 additions and 4546 deletions

View File

@@ -1,50 +1,55 @@
<?php
namespace WooNooW\Frontend;
/**
* Frontend Assets Manager
* Handles loading of customer-spa assets
*/
class Assets {
class Assets
{
/**
* Initialize
*/
public static function init() {
public static function init()
{
add_action('wp_enqueue_scripts', [self::class, 'enqueue_assets'], 20);
add_action('wp_head', [self::class, 'add_inline_config'], 5);
add_action('wp_enqueue_scripts', [self::class, 'dequeue_conflicting_scripts'], 100);
add_filter('script_loader_tag', [self::class, 'add_module_type'], 10, 3);
add_action('woocommerce_before_main_content', [self::class, 'inject_spa_mount_point'], 5);
}
/**
* Add type="module" to customer-spa scripts
*/
public static function add_module_type($tag, $handle, $src) {
public static function add_module_type($tag, $handle, $src)
{
// Add type="module" to our Vite scripts
if (strpos($handle, 'woonoow-customer') !== false) {
$tag = str_replace('<script ', '<script type="module" ', $tag);
}
return $tag;
}
/**
* Enqueue customer-spa assets
*/
public static function enqueue_assets() {
public static function enqueue_assets()
{
// Only load on pages with WooNooW shortcodes or in full SPA mode
if (!self::should_load_assets()) {
return;
}
// Check if dev mode is enabled
$is_dev = defined('WOONOOW_CUSTOMER_DEV') && WOONOOW_CUSTOMER_DEV;
if ($is_dev) {
// Dev mode: Load from Vite dev server
$dev_server = 'https://woonoow.local:5174';
// Vite client for HMR
wp_enqueue_script(
'woonoow-customer-vite',
@@ -53,7 +58,7 @@ class Assets {
null,
false // Load in header
);
// Main entry point
wp_enqueue_script(
'woonoow-customer-spa',
@@ -66,16 +71,16 @@ class Assets {
// Production mode: Load from build
$plugin_url = plugin_dir_url(dirname(dirname(__FILE__)));
$dist_path = plugin_dir_path(dirname(dirname(__FILE__))) . 'customer-spa/dist/';
// Check if build exists
if (!file_exists($dist_path)) {
return;
}
// Production build - load app.js and app.css directly
$js_url = $plugin_url . 'customer-spa/dist/app.js';
$css_url = $plugin_url . 'customer-spa/dist/app.css';
wp_enqueue_script(
'woonoow-customer-spa',
$js_url,
@@ -83,15 +88,15 @@ class Assets {
null,
true
);
// Add type="module" for Vite build
add_filter('script_loader_tag', function($tag, $handle, $src) {
add_filter('script_loader_tag', function ($tag, $handle, $src) {
if ($handle === 'woonoow-customer-spa') {
$tag = str_replace('<script ', '<script type="module" ', $tag);
}
return $tag;
}, 10, 3);
wp_enqueue_style(
'woonoow-customer-spa',
$css_url,
@@ -100,33 +105,35 @@ class Assets {
);
}
}
/**
* Inject SPA mounting point for full mode
*/
public static function inject_spa_mount_point() {
public static function inject_spa_mount_point()
{
if (!self::should_load_assets()) {
return;
}
// Check if we're in full mode and not on a page with shortcode
$spa_settings = get_option('woonoow_customer_spa_settings', []);
$mode = isset($spa_settings['mode']) ? $spa_settings['mode'] : 'disabled';
if ($mode === 'full') {
// Only inject if the mount point doesn't already exist (from shortcode)
echo '<div id="woonoow-customer-app" data-page="shop"><div class="woonoow-loading"><p>Loading...</p></div></div>';
}
}
/**
* Add inline config and scripts to page head
*/
public static function add_inline_config() {
public static function add_inline_config()
{
if (!self::should_load_assets()) {
return;
}
// Get Customer SPA settings
$spa_settings = get_option('woonoow_customer_spa_settings', []);
$default_settings = [
@@ -142,14 +149,14 @@ class Assets {
],
];
$theme_settings = array_replace_recursive($default_settings, $spa_settings);
// Get appearance settings and preload them
$stored_settings = get_option('woonoow_appearance_settings', []);
$default_appearance = \WooNooW\Admin\AppearanceController::get_default_settings();
// Merge stored settings with defaults to ensure new fields (like gradient colors) exist
$appearance_settings = array_replace_recursive($default_appearance, $stored_settings);
// Get WooCommerce currency settings
$currency_settings = [
'code' => get_woocommerce_currency(),
@@ -159,16 +166,16 @@ class Assets {
'decimalSeparator' => wc_get_price_decimal_separator(),
'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();
@@ -193,15 +200,15 @@ class Assets {
'country' => $customer->get_shipping_country(),
];
}
// Determine SPA base path for BrowserRouter
$spa_page_id = $appearance_settings['general']['spa_page'] ?? 0;
$spa_page = $spa_page_id ? get_post($spa_page_id) : null;
// Check if SPA Entry Page is set as WordPress frontpage
$frontpage_id = (int) get_option('page_on_front');
$is_spa_wp_frontpage = $frontpage_id && $spa_page_id && $frontpage_id === (int) $spa_page_id;
// Get SPA Landing page (explicit setting, separate from Entry Page)
// This determines what content to show at the SPA root route "/"
$spa_frontpage_id = $appearance_settings['general']['spa_frontpage'] ?? 0;
@@ -211,14 +218,18 @@ class Assets {
if ($spa_frontpage) {
$front_page_slug = $spa_frontpage->post_name;
}
} elseif ($is_spa_wp_frontpage && $spa_page) {
// If the SPA Entry Page itself is set as the WP Frontpage,
// use its content as the SPA Frontpage content.
$front_page_slug = $spa_page->post_name;
}
// If SPA Entry Page is WP frontpage, base path is /, otherwise use Entry Page slug
$base_path = $is_spa_wp_frontpage ? '' : ($spa_page ? '/' . $spa_page->post_name : '/store');
// Check if BrowserRouter is enabled (default: true for SEO)
$use_browser_router = $appearance_settings['general']['use_browser_router'] ?? true;
$config = [
'apiUrl' => rest_url('woonoow/v1'),
'apiRoot' => rest_url('woonoow/v1'),
@@ -236,19 +247,20 @@ class Assets {
'useBrowserRouter' => $use_browser_router,
'frontPageSlug' => $front_page_slug,
'spaMode' => $appearance_settings['general']['spa_mode'] ?? 'full',
'security' => \WooNooW\Compat\SecuritySettingsProvider::get_public_settings(),
];
?>
?>
<script type="text/javascript">
window.woonoowCustomer = <?php echo wp_json_encode($config); ?>;
</script>
<?php
// If dev mode, output scripts directly
$is_dev = defined('WOONOOW_CUSTOMER_DEV') && WOONOOW_CUSTOMER_DEV;
if ($is_dev) {
$dev_server = 'https://woonoow.local:5174';
?>
?>
<script type="module">
import RefreshRuntime from '<?php echo $dev_server; ?>/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
@@ -258,35 +270,36 @@ class Assets {
</script>
<script type="module" crossorigin src="<?php echo $dev_server; ?>/@vite/client"></script>
<script type="module" crossorigin src="<?php echo $dev_server; ?>/src/main.tsx"></script>
<?php
<?php
}
}
/**
* Check if we should load customer-spa assets
*/
private static function should_load_assets() {
private static function should_load_assets()
{
global $post;
// Check if we're serving SPA directly (set by serve_spa_for_frontpage_routes)
if (defined('WOONOOW_SERVE_SPA') && WOONOOW_SERVE_SPA) {
return true;
}
// Check if we're on a frontpage SPA route (by URL detection)
if (self::is_frontpage_spa_route()) {
return true;
}
// First check: Is this a designated SPA page?
if (self::is_spa_page()) {
return true;
}
// Get SPA mode from appearance settings (the correct source)
$appearance_settings = get_option('woonoow_appearance_settings', []);
$mode = $appearance_settings['general']['spa_mode'] ?? 'full';
// If disabled, only load for pages with shortcodes
if ($mode === 'disabled') {
// Special handling for WooCommerce Shop page (it's an archive, not a regular post)
@@ -299,7 +312,7 @@ class Assets {
}
}
}
// Check for shortcodes on regular pages
if ($post) {
if (has_shortcode($post->post_content, 'woonoow_shop')) {
@@ -317,7 +330,7 @@ class Assets {
}
return false;
}
// Full SPA mode - load on all WooCommerce pages
if ($mode === 'full') {
if (function_exists('is_shop') && is_shop()) {
@@ -337,11 +350,11 @@ class Assets {
}
return false;
}
// Checkout-Only mode - load only on specific pages
if ($mode === 'checkout_only') {
$checkout_pages = isset($spa_settings['checkoutPages']) ? $spa_settings['checkoutPages'] : [];
if (!empty($checkout_pages['checkout']) && function_exists('is_checkout') && is_checkout() && !is_order_received_page()) {
return true;
}
@@ -356,7 +369,7 @@ class Assets {
}
return false;
}
// Check if current page has WooNooW shortcodes
if ($post && has_shortcode($post->post_content, 'woonoow_shop')) {
return true;
@@ -370,65 +383,67 @@ class Assets {
if ($post && has_shortcode($post->post_content, 'woonoow_account')) {
return true;
}
return false;
}
/**
* Check if current page is the designated SPA page
*/
private static function is_spa_page() {
private static function is_spa_page()
{
global $post;
if (!$post) {
return false;
}
// Get SPA page ID from appearance settings
$appearance_settings = get_option('woonoow_appearance_settings', []);
$spa_page_id = isset($appearance_settings['general']['spa_page']) ? $appearance_settings['general']['spa_page'] : 0;
// Check if current page matches the SPA page
if ($spa_page_id && $post->ID == $spa_page_id) {
return true;
}
return false;
}
/**
* Check if current request is a frontpage SPA route
* Used to detect SPA routes by URL when SPA page is set as frontpage
*/
private static function is_frontpage_spa_route() {
private static function is_frontpage_spa_route()
{
// Get SPA settings
$appearance_settings = get_option('woonoow_appearance_settings', []);
$spa_page_id = $appearance_settings['general']['spa_page'] ?? 0;
$spa_mode = $appearance_settings['general']['spa_mode'] ?? 'full';
// Only run in full SPA mode
if ($spa_mode !== 'full' || !$spa_page_id) {
return false;
}
// Check if SPA page is set as WordPress frontpage
$frontpage_id = (int) get_option('page_on_front');
if (!$frontpage_id || $frontpage_id !== (int) $spa_page_id) {
return false;
}
// Get the current request path
$request_uri = $_SERVER['REQUEST_URI'] ?? '/';
$path = parse_url($request_uri, PHP_URL_PATH);
$path = '/' . trim($path, '/');
// Define SPA routes
$spa_routes = ['/', '/shop', '/cart', '/checkout', '/my-account', '/login', '/register', '/reset-password'];
// Check exact matches
if (in_array($path, $spa_routes)) {
return true;
}
// Check path prefixes
$prefix_routes = ['/shop/', '/my-account/', '/product/'];
foreach ($prefix_routes as $prefix) {
@@ -436,30 +451,31 @@ class Assets {
return true;
}
}
return false;
}
/**
* Dequeue conflicting scripts when SPA is active
*/
public static function dequeue_conflicting_scripts() {
public static function dequeue_conflicting_scripts()
{
if (!self::should_load_assets()) {
return;
}
// Dequeue WooCommerce scripts that conflict with SPA
wp_dequeue_script('wc-cart-fragments');
wp_dequeue_script('woocommerce');
wp_dequeue_script('wc-add-to-cart');
wp_dequeue_script('wc-add-to-cart-variation');
// Dequeue WordPress block scripts that cause errors in SPA
wp_dequeue_script('wp-block-library');
wp_dequeue_script('wp-block-navigation');
wp_dequeue_script('wp-interactivity');
wp_dequeue_script('wp-interactivity-router');
// Keep only essential WooCommerce styles, dequeue others if needed
// wp_dequeue_style('woocommerce-general');
// wp_dequeue_style('woocommerce-layout');