feat: Add-to-cart from URL parameters
Implements direct-to-cart functionality for landing page CTAs. Features: - Parse URL parameters: ?add-to-cart=123 - Support simple products: ?add-to-cart=123 - Support variable products: ?add-to-cart=123&variation_id=456 - Support quantity: ?add-to-cart=123&quantity=2 - Auto-navigate to cart after adding - Clean URL after adding (remove parameters) - Toast notification on success/error Usage examples: 1. Simple product: https://site.com/store?add-to-cart=332 2. Variable product: https://site.com/store?add-to-cart=332&variation_id=456 3. With quantity: https://site.com/store?add-to-cart=332&quantity=3 Flow: - User clicks CTA on landing page - Redirects to SPA with add-to-cart parameter - SPA loads, hook detects parameter - Adds product to cart via API - Navigates to cart page - Shows success toast Works with both SPA modes: - Full SPA: loads shop, adds to cart, navigates to cart - Checkout Only: loads cart, adds to cart, stays on cart
This commit is contained in:
@@ -76,11 +76,16 @@ export default function AppearanceGeneral() {
|
|||||||
|
|
||||||
// Load available pages
|
// Load available pages
|
||||||
const pagesResponse = await api.get('/pages/list');
|
const pagesResponse = await api.get('/pages/list');
|
||||||
if (pagesResponse.data?.data) {
|
console.log('Pages API response:', pagesResponse);
|
||||||
setAvailablePages(pagesResponse.data.data);
|
if (pagesResponse.data) {
|
||||||
|
console.log('Pages loaded:', pagesResponse.data);
|
||||||
|
setAvailablePages(pagesResponse.data);
|
||||||
|
} else {
|
||||||
|
console.warn('No pages data in response:', pagesResponse);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load settings:', error);
|
console.error('Failed to load settings:', error);
|
||||||
|
console.error('Error details:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Toaster } from 'sonner';
|
|||||||
// Theme
|
// Theme
|
||||||
import { ThemeProvider } from './contexts/ThemeContext';
|
import { ThemeProvider } from './contexts/ThemeContext';
|
||||||
import { BaseLayout } from './layouts/BaseLayout';
|
import { BaseLayout } from './layouts/BaseLayout';
|
||||||
|
import { useAddToCartFromUrl } from './hooks/useAddToCartFromUrl';
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
import Shop from './pages/Shop';
|
import Shop from './pages/Shop';
|
||||||
@@ -56,14 +57,21 @@ function App() {
|
|||||||
const appearanceSettings = getAppearanceSettings();
|
const appearanceSettings = getAppearanceSettings();
|
||||||
const toastPosition = (appearanceSettings?.general?.toast_position || 'top-right') as any;
|
const toastPosition = (appearanceSettings?.general?.toast_position || 'top-right') as any;
|
||||||
|
|
||||||
|
// Handle add-to-cart from URL parameters
|
||||||
|
useAddToCartFromUrl();
|
||||||
|
|
||||||
// Get initial route from data attribute (set by PHP based on SPA mode)
|
// Get initial route from data attribute (set by PHP based on SPA mode)
|
||||||
const getInitialRoute = () => {
|
const getInitialRoute = () => {
|
||||||
const appEl = document.getElementById('woonoow-customer-app');
|
const appEl = document.getElementById('woonoow-customer-app');
|
||||||
const initialRoute = appEl?.getAttribute('data-initial-route');
|
const initialRoute = appEl?.getAttribute('data-initial-route');
|
||||||
|
console.log('[WooNooW Customer] Initial route from data attribute:', initialRoute);
|
||||||
|
console.log('[WooNooW Customer] App element:', appEl);
|
||||||
|
console.log('[WooNooW Customer] All data attributes:', appEl?.dataset);
|
||||||
return initialRoute || '/shop'; // Default to shop if not specified
|
return initialRoute || '/shop'; // Default to shop if not specified
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialRoute = getInitialRoute();
|
const initialRoute = getInitialRoute();
|
||||||
|
console.log('[WooNooW Customer] Using initial route:', initialRoute);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
|||||||
95
customer-spa/src/hooks/useAddToCartFromUrl.ts
Normal file
95
customer-spa/src/hooks/useAddToCartFromUrl.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to handle add-to-cart from URL parameters
|
||||||
|
* Supports both simple and variable products
|
||||||
|
*
|
||||||
|
* URL formats:
|
||||||
|
* - Simple product: ?add-to-cart=123
|
||||||
|
* - Variable product: ?add-to-cart=123&variation_id=456
|
||||||
|
* - With quantity: ?add-to-cart=123&quantity=2
|
||||||
|
*/
|
||||||
|
export function useAddToCartFromUrl() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const productId = params.get('add-to-cart');
|
||||||
|
|
||||||
|
if (!productId) return;
|
||||||
|
|
||||||
|
const variationId = params.get('variation_id');
|
||||||
|
const quantity = parseInt(params.get('quantity') || '1', 10);
|
||||||
|
|
||||||
|
console.log('[WooNooW] Add to cart from URL:', {
|
||||||
|
productId,
|
||||||
|
variationId,
|
||||||
|
quantity,
|
||||||
|
fullUrl: window.location.href,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add product to cart
|
||||||
|
addToCart(productId, variationId, quantity)
|
||||||
|
.then(() => {
|
||||||
|
// Remove URL parameters after adding to cart
|
||||||
|
const cleanUrl = window.location.pathname + window.location.hash;
|
||||||
|
window.history.replaceState({}, '', cleanUrl);
|
||||||
|
|
||||||
|
// Navigate to cart if not already there
|
||||||
|
if (!location.pathname.includes('/cart')) {
|
||||||
|
navigate('/cart');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('[WooNooW] Failed to add product to cart:', error);
|
||||||
|
toast.error('Failed to add product to cart');
|
||||||
|
});
|
||||||
|
}, []); // Run once on mount
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addToCart(
|
||||||
|
productId: string,
|
||||||
|
variationId: string | null,
|
||||||
|
quantity: number
|
||||||
|
): Promise<void> {
|
||||||
|
const apiRoot = (window as any).woonoowCustomer?.apiRoot || '/wp-json/woonoow/v1';
|
||||||
|
const nonce = (window as any).woonoowCustomer?.nonce || '';
|
||||||
|
|
||||||
|
const body: any = {
|
||||||
|
product_id: parseInt(productId, 10),
|
||||||
|
quantity,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (variationId) {
|
||||||
|
body.variation_id = parseInt(variationId, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[WooNooW] Adding to cart:', body);
|
||||||
|
|
||||||
|
const response = await fetch(`${apiRoot}/cart/add`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-WP-Nonce': nonce,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || 'Failed to add to cart');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(data.message || 'Failed to add to cart');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[WooNooW] Product added to cart:', data);
|
||||||
|
toast.success('Product added to cart');
|
||||||
|
}
|
||||||
@@ -12,15 +12,21 @@
|
|||||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||||
$spa_mode = isset($appearance_settings['general']['spa_mode']) ? $appearance_settings['general']['spa_mode'] : 'full';
|
$spa_mode = isset($appearance_settings['general']['spa_mode']) ? $appearance_settings['general']['spa_mode'] : 'full';
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
error_log('[WooNooW SPA Template] Settings: ' . print_r($appearance_settings, true));
|
||||||
|
error_log('[WooNooW SPA Template] SPA Mode: ' . $spa_mode);
|
||||||
|
|
||||||
// Set initial page based on mode
|
// Set initial page based on mode
|
||||||
if ($spa_mode === 'checkout_only') {
|
if ($spa_mode === 'checkout_only') {
|
||||||
// Checkout Only mode starts at cart
|
// Checkout Only mode starts at cart
|
||||||
$page_type = 'cart';
|
$page_type = 'cart';
|
||||||
$data_attrs = 'data-page="cart" data-initial-route="/cart"';
|
$data_attrs = 'data-page="cart" data-initial-route="/cart"';
|
||||||
|
error_log('[WooNooW SPA Template] Using CART initial route');
|
||||||
} else {
|
} else {
|
||||||
// Full SPA mode starts at shop
|
// Full SPA mode starts at shop
|
||||||
$page_type = 'shop';
|
$page_type = 'shop';
|
||||||
$data_attrs = 'data-page="shop" data-initial-route="/shop"';
|
$data_attrs = 'data-page="shop" data-initial-route="/shop"';
|
||||||
|
error_log('[WooNooW SPA Template] Using SHOP initial route');
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user