From 3d5191aab33893799a7dfaa314f0ba904b2ada14 Mon Sep 17 00:00:00 2001 From: Dwindi Ramadhana Date: Wed, 31 Dec 2025 18:59:49 +0700 Subject: [PATCH] feat: add Newsletter Campaigns frontend UI - Add Campaigns list page with table, status badges, search, actions - Add Campaign editor with title, subject, content fields - Add preview modal, test email dialog, send confirmation - Update Marketing index to show hub with Newsletter, Campaigns, Coupons cards - Add routes in App.tsx --- admin-spa/src/App.tsx | 72 ++-- .../src/routes/Marketing/Campaigns/Edit.tsx | 400 ++++++++++++++++++ .../src/routes/Marketing/Campaigns/index.tsx | 293 +++++++++++++ admin-spa/src/routes/Marketing/index.tsx | 62 ++- 4 files changed, 791 insertions(+), 36 deletions(-) create mode 100644 admin-spa/src/routes/Marketing/Campaigns/Edit.tsx create mode 100644 admin-spa/src/routes/Marketing/Campaigns/index.tsx diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx index 3a164e0..4a2805b 100644 --- a/admin-spa/src/App.tsx +++ b/admin-spa/src/App.tsx @@ -101,12 +101,12 @@ function ActiveNavLink({ to, startsWith, end, className, children, childPaths }: className={(nav) => { // Special case: Dashboard should ONLY match root path "/" or paths starting with "/dashboard" const isDashboard = starts === '/dashboard' && (location.pathname === '/' || location.pathname.startsWith('/dashboard')); - + // Check if current path matches any child paths (e.g., /coupons under Marketing) - const matchesChild = childPaths && Array.isArray(childPaths) + const matchesChild = childPaths && Array.isArray(childPaths) ? childPaths.some((childPath: string) => location.pathname.startsWith(childPath)) : false; - + // For dashboard: only active if isDashboard is true // For others: active if path starts with their path OR matches a child path let activeByPath = false; @@ -115,7 +115,7 @@ function ActiveNavLink({ to, startsWith, end, className, children, childPaths }: } else if (starts) { activeByPath = location.pathname.startsWith(starts) || matchesChild; } - + const mergedActive = nav.isActive || activeByPath; if (typeof className === 'function') { // Preserve caller pattern: className receives { isActive } @@ -133,7 +133,7 @@ function Sidebar() { const link = "flex items-center gap-2 rounded-md px-3 py-2 hover:bg-accent hover:text-accent-foreground shadow-none hover:shadow-none focus:shadow-none focus:outline-none focus:ring-0"; const active = "bg-secondary"; const { main } = useActiveSection(); - + // Icon mapping const iconMap: Record = { 'layout-dashboard': LayoutDashboard, @@ -145,10 +145,10 @@ function Sidebar() { 'palette': Palette, 'settings': SettingsIcon, }; - + // Get navigation tree from backend const navTree = (window as any).WNW_NAV_TREE || []; - + return (