feat: integrate contextual links and fix coupons navigation
- Added DocLink component and mapped routes - Fixed Coupons nav link to /marketing/coupons - Updated Settings pages to show inline documentation links
This commit is contained in:
49
customer-spa/src/components/CouponURLHandler.tsx
Normal file
49
customer-spa/src/components/CouponURLHandler.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { applyCoupon } from '@/lib/cart/api';
|
||||
import { useCartStore } from '@/lib/cart/store';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
/**
|
||||
* CouponURLHandler
|
||||
*
|
||||
* Global component that listens for 'coupon' or 'apply_coupon' query parameters
|
||||
* and automatically applies them to the cart session.
|
||||
*/
|
||||
export function CouponURLHandler() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { setCart } = useCartStore();
|
||||
const processedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const couponCode = searchParams.get('coupon') || searchParams.get('apply_coupon');
|
||||
|
||||
if (couponCode && !processedRef.current) {
|
||||
processedRef.current = true; // Prevent double firing in StrictMode
|
||||
|
||||
const apply = async () => {
|
||||
const toastId = toast.loading(`Applying coupon: ${couponCode}...`);
|
||||
|
||||
try {
|
||||
const updatedCart = await applyCoupon(couponCode);
|
||||
setCart(updatedCart);
|
||||
toast.success(`Coupon "${couponCode}" applied successfully!`, { id: toastId });
|
||||
} catch (error: any) {
|
||||
console.error('Failed to apply URL coupon:', error);
|
||||
toast.error(error.message || `Failed to apply coupon "${couponCode}"`, { id: toastId });
|
||||
} finally {
|
||||
// Remove the coupon param from URL to prevent re-application on refresh
|
||||
// Use a new URLSearchParams object to avoid direct mutation issues
|
||||
const newParams = new URLSearchParams(searchParams);
|
||||
newParams.delete('coupon');
|
||||
newParams.delete('apply_coupon');
|
||||
setSearchParams(newParams, { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
apply();
|
||||
}
|
||||
}, [searchParams, setSearchParams, setCart]);
|
||||
|
||||
return null; // This component renders nothing
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { NewsletterForm } from '../components/NewsletterForm';
|
||||
import { LayoutWrapper } from './LayoutWrapper';
|
||||
import { useModules } from '../hooks/useModules';
|
||||
import { useModuleSettings } from '../hooks/useModuleSettings';
|
||||
import { CouponURLHandler } from '../components/CouponURLHandler';
|
||||
|
||||
interface BaseLayoutProps {
|
||||
children: ReactNode;
|
||||
@@ -22,22 +23,20 @@ interface BaseLayoutProps {
|
||||
export function BaseLayout({ children }: BaseLayoutProps) {
|
||||
const headerSettings = useHeaderSettings();
|
||||
|
||||
// Map header styles to layouts
|
||||
// classic -> ClassicLayout, centered -> ModernLayout, minimal -> LaunchLayout, split -> BoutiqueLayout
|
||||
switch (headerSettings.style) {
|
||||
case 'classic':
|
||||
return <ClassicLayout>{children}</ClassicLayout>;
|
||||
case 'centered':
|
||||
return <ModernLayout>{children}</ModernLayout>;
|
||||
case 'minimal':
|
||||
return <LaunchLayout>{children}</LaunchLayout>;
|
||||
case 'split':
|
||||
return <BoutiqueLayout>{children}</BoutiqueLayout>;
|
||||
default:
|
||||
return <ClassicLayout>{children}</ClassicLayout>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<CouponURLHandler />
|
||||
{/* Map header styles to layouts */}
|
||||
{headerSettings.style === 'classic' && <ClassicLayout>{children}</ClassicLayout>}
|
||||
{headerSettings.style === 'centered' && <ModernLayout>{children}</ModernLayout>}
|
||||
{headerSettings.style === 'minimal' && <LaunchLayout>{children}</LaunchLayout>}
|
||||
{headerSettings.style === 'split' && <BoutiqueLayout>{children}</BoutiqueLayout>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Temporary internal switch function removed to allow fragment wrapping above.
|
||||
// Re-implementing logic directly in return for cleaner wrapping.
|
||||
/**
|
||||
* Classic Layout - Traditional ecommerce
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user