import { createContext, useContext, useState, useEffect } from 'react'; import { useAuth } from '../auth/AuthProvider'; import { getWizardConfig } from '../services/wizardApi'; import { getAvailableChannels } from '../services/initiativeApi'; // ── Context ── const WizardConfigContext = createContext(null); export const useWizardConfig = () => useContext(WizardConfigContext); // ── Fallbacks (used if API call fails — wizard is never blocked) ── const fallbackObjectives = [ { name: 'awareness', objectiveId: 1 }, { name: 'traffic', objectiveId: 2 }, { name: 'leads', objectiveId: 3 }, { name: 'conversions', objectiveId: 4 }, { name: 'sales', objectiveId: 5 }, ]; const fallbackCategories = [ { name: 'restaurant', categoryId: 1, icon: '🍽️' }, { name: 'retail', categoryId: 2, icon: '🛍️' }, { name: 'b2b services', categoryId: 3, icon: '💼' }, { name: 'local services', categoryId: 4, icon: '🏠' }, { name: 'health & wellness', categoryId: 5, icon: '🏥' }, { name: 'general', categoryId: 6, icon: '📦' }, ]; // ── Display label helper ── // DB stores lowercase names like "b2b services" — format for display export function toDisplayLabel(name) { if (!name) return ''; return name .split(' ') .map(w => { if (w === '&') return '&'; if (w.toLowerCase() === 'b2b') return 'B2B'; return w.charAt(0).toUpperCase() + w.slice(1); }) .join(' '); } // ── Client-side enrichment maps ── // DB stores core data (name, color); client adds icons + descriptions // New objectives added via admin still appear, just without enrichment. export const objectiveDescriptions = { awareness: 'Reach new audiences and build recognition', traffic: 'Drive more visitors to your site', leads: 'Get contact form submissions and signups', conversions: 'Drive specific actions on your site', sales: 'Drive purchases and revenue', }; export const objectiveIcons = { awareness: '📣', traffic: '🌐', leads: '📋', conversions: '🎯', sales: '💰', }; // Labels for display contexts (ReviewStep, etc.) export const objectiveLabels = Object.fromEntries( ['awareness', 'traffic', 'leads', 'conversions', 'sales'] .map(k => [k, toDisplayLabel(k)]) ); export const categoryLabels = Object.fromEntries( fallbackCategories.map(c => [c.name, toDisplayLabel(c.name)]) ); // ── Fallback channels (used if API call fails) ── const fallbackChannels = [ { channelType: 'google_ads', displayName: 'Google Ads', description: 'Search, Display, Shopping & Performance Max', minMonthlyBudget: 300, color: '#4285F4', isStub: false }, { channelType: 'meta', displayName: 'Meta Ads', description: 'Facebook, Instagram, Messenger & Threads', minMonthlyBudget: 250, color: '#0668E1', isStub: true }, { channelType: 'tiktok', displayName: 'TikTok Ads', description: 'In-feed video ads across TikTok', minMonthlyBudget: 200, color: '#000000', isStub: true }, ]; // ── Channel lookup helpers ── // Use these in AllocationStep, ReviewStep, etc. instead of hardcoded maps. export function getChannelLabel(channelType, channels) { if (!channelType) return 'Unknown'; const ch = channels.find(c => c.channelType === channelType); return ch?.displayName || toDisplayLabel(channelType.replace(/_/g, ' ')); } export function getChannelColor(channelType, channels) { if (!channelType) return '#4F46E5'; const ch = channels.find(c => c.channelType === channelType); return ch?.color || '#4F46E5'; } // ── Provider ── export default function WizardConfigProvider({ children }) { const { sessionToken } = useAuth(); const [categories, setCategories] = useState([]); const [objectives, setObjectives] = useState([]); const [channels, setChannels] = useState([]); const [allocationRules, setAllocationRules] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { let cancelled = false; async function loadConfig() { setLoading(true); // Fetch wizard config (categories + objectives) and channels in parallel const [configRes, channelsRes] = await Promise.all([ getWizardConfig(sessionToken), getAvailableChannels(sessionToken), ]); if (cancelled) return; if (configRes.ok && configRes.data) { const d = configRes.data; setCategories(Array.isArray(d.categories) ? d.categories : fallbackCategories); setObjectives(Array.isArray(d.objectives) ? d.objectives : fallbackObjectives); } else { console.warn('Wizard config load failed, using fallbacks:', configRes.error); setCategories(fallbackCategories); setObjectives(fallbackObjectives); } if (channelsRes.ok && channelsRes.data) { setChannels(channelsRes.data.channels || fallbackChannels); setAllocationRules(channelsRes.data.allocation || null); } else { console.warn('Channels load failed, using fallbacks:', channelsRes.error); setChannels(fallbackChannels); } setLoading(false); } loadConfig(); return () => { cancelled = true; }; }, [sessionToken]); const value = { categories, objectives, channels, allocationRules, loading, // Helpers exposed so consumers don't need separate imports toDisplayLabel, objectiveDescriptions, objectiveIcons, getChannelLabel: (type) => getChannelLabel(type, channels), getChannelColor: (type) => getChannelColor(type, channels), }; return ( {children} ); }