Files
AdPlatform-Client/Client-Client/src/context/WizardConfigContext.jsx
Grae Jones fdb3e117a9
Some checks failed
Client Admin / build-deploy (push) Failing after 8s
Client Client / build-deploy (push) Failing after 3s
Client Registration / build-deploy (push) Failing after 20s
Client Tech / build-deploy (push) Failing after 1s
Client Home / build-deploy (push) Successful in 14s
First build
2026-03-21 17:54:42 -07:00

159 lines
5.7 KiB
JavaScript

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 (
<WizardConfigContext.Provider value={value}>
{children}
</WizardConfigContext.Provider>
);
}