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:
Dwindi Ramadhana
2026-02-05 22:51:44 +07:00
parent 5f08c18ec7
commit 7da4f0a167
11 changed files with 221 additions and 22 deletions

View 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
}

View File

@@ -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
*/