import { useState, useEffect, useCallback } from 'react'; import { useParams } from 'react-router-dom'; import apiClient from '../api/client'; const MAX_SMS_LENGTH = 160; const PLACEHOLDER_TOKEN = '{#var#}'; const PLACEHOLDER_REGEX = /\{#var#\}/g; function getVariantKey(slug, index) { return `${slug}:${index}`; } function createVariantDraft(text = '') { return { originalText: text, currentText: text, validationStatus: 'idle', why: '', lastCheckedText: '', }; } function countPlaceholders(text = '') { return (String(text).match(PLACEHOLDER_REGEX) || []).length; } function buildDraftsForVariants(slug, generatedVariants = []) { const nextDrafts = {}; generatedVariants.forEach((variant, index) => { nextDrafts[getVariantKey(slug, index)] = createVariantDraft(variant); }); return nextDrafts; } function removeDraftsForSlug(drafts, slug) { const nextDrafts = { ...drafts }; Object.keys(nextDrafts).forEach((key) => { if (key.startsWith(`${slug}:`)) delete nextDrafts[key]; }); return nextDrafts; } function syncDraftsWithVariants(existingDrafts, variantsBySlug) { const nextDrafts = {}; Object.entries(variantsBySlug).forEach(([slug, generatedVariants]) => { generatedVariants.forEach((variant, index) => { const key = getVariantKey(slug, index); const existing = existingDrafts[key]; nextDrafts[key] = existing && existing.originalText === variant ? existing : createVariantDraft(variant); }); }); return nextDrafts; } function buildTemplateUiState(templates = []) { const nextVariants = {}; const nextGenState = {}; templates.forEach((template) => { if (!template?.eventSlug) return; if (template.selectedTemplate) { nextGenState[template.eventSlug] = 'selected'; return; } if (Array.isArray(template.generatedVariants) && template.generatedVariants.length > 0) { nextVariants[template.eventSlug] = template.generatedVariants; nextGenState[template.eventSlug] = 'done'; } }); return { nextVariants, nextGenState }; } export default function Events() { const { businessId } = useParams(); const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const [newLabel, setNewLabel] = useState(''); const [addingEvent, setAddingEvent] = useState(false); const [showAddForm, setShowAddForm] = useState(false); const [genState, setGenState] = useState({}); const [variants, setVariants] = useState({}); const [variantDrafts, setVariantDrafts] = useState({}); const [selectingVariantKey, setSelectingVariantKey] = useState(''); const [error, setError] = useState(''); const [readyToGenerate, setReadyToGenerate] = useState(false); const loadEvents = useCallback(async () => { setLoading(true); try { const [eventsRes, activeProfileRes, templatesRes] = await Promise.all([ apiClient.get(`/api/businesses/${businessId}/events`), apiClient.get(`/api/businesses/${businessId}/global-sms/active`).catch(() => ({ data: {} })), apiClient.get(`/api/businesses/${businessId}/templates`).catch(() => ({ data: { templates: [] } })), ]); const templates = templatesRes.data.templates || []; const { nextVariants, nextGenState } = buildTemplateUiState(templates); setEvents(eventsRes.data.events || []); setReadyToGenerate(!!activeProfileRes.data?.activeProfile?.rawCurl); setVariants(nextVariants); setGenState(nextGenState); setVariantDrafts((currentDrafts) => syncDraftsWithVariants(currentDrafts, nextVariants)); } catch { setError('Failed to load events'); } finally { setLoading(false); } }, [businessId]); useEffect(() => { loadEvents(); }, [loadEvents]); async function handleAddEvent(e) { e.preventDefault(); if (!newLabel.trim()) return; setAddingEvent(true); setError(''); try { await apiClient.post(`/api/businesses/${businessId}/events`, { label: newLabel.trim() }); setNewLabel(''); setShowAddForm(false); await loadEvents(); } catch (err) { setError(err.response?.data?.error || 'Failed to add event'); } finally { setAddingEvent(false); } } async function handleDelete(slug) { try { await apiClient.delete(`/api/businesses/${businessId}/events/${slug}`); await loadEvents(); } catch (err) { setError(err.response?.data?.error || 'Failed to delete event'); } } async function handleGenerate(slug) { if (!readyToGenerate) { setError('Configure and activate a cURL profile before generating templates.'); return; } setGenState((state) => ({ ...state, [slug]: 'loading' })); setError(''); try { const res = await apiClient.post(`/api/businesses/${businessId}/events/${slug}/generate`); const generatedVariants = res.data.variants || []; setVariants((currentVariants) => ({ ...currentVariants, [slug]: generatedVariants })); setVariantDrafts((currentDrafts) => ({ ...removeDraftsForSlug(currentDrafts, slug), ...buildDraftsForVariants(slug, generatedVariants), })); setGenState((state) => ({ ...state, [slug]: 'done' })); } catch (err) { setError(err.response?.data?.error || 'Generation failed'); setGenState((state) => ({ ...state, [slug]: 'error' })); } } async function handleValidateEdit(slug, variantIndex) { const variantKey = getVariantKey(slug, variantIndex); const draft = variantDrafts[variantKey]; const editedTemplate = draft?.currentText || ''; if (!editedTemplate) return; setVariantDrafts((currentDrafts) => ({ ...currentDrafts, [variantKey]: { ...(currentDrafts[variantKey] || createVariantDraft(editedTemplate)), validationStatus: 'checking', why: '', lastCheckedText: '', }, })); setError(''); try { const res = await apiClient.post(`/api/businesses/${businessId}/templates/${slug}/validate-edit`, { editedTemplate, }); setVariantDrafts((currentDrafts) => ({ ...currentDrafts, [variantKey]: { ...(currentDrafts[variantKey] || createVariantDraft(editedTemplate)), validationStatus: res.data?.approved ? 'approved' : 'rejected', why: res.data?.why || '', lastCheckedText: editedTemplate, }, })); } catch (err) { setError(err.response?.data?.error || 'Failed to validate edited template'); setVariantDrafts((currentDrafts) => ({ ...currentDrafts, [variantKey]: { ...(currentDrafts[variantKey] || createVariantDraft(editedTemplate)), validationStatus: 'idle', why: '', lastCheckedText: '', }, })); } } async function handleSelect(slug, variant, variantIndex) { const variantKey = getVariantKey(slug, variantIndex); setSelectingVariantKey(variantKey); setError(''); try { await apiClient.post(`/api/businesses/${businessId}/templates/${slug}/select`, { selectedVariant: variant }); setVariants((currentVariants) => ({ ...currentVariants, [slug]: [] })); setVariantDrafts((currentDrafts) => removeDraftsForSlug(currentDrafts, slug)); setGenState((state) => ({ ...state, [slug]: 'selected' })); } catch (err) { setError(err.response?.data?.error || 'Failed to select template'); } finally { setSelectingVariantKey(''); } } function handleVariantChange(slug, variantIndex, nextText) { const variantKey = getVariantKey(slug, variantIndex); const originalText = variantDrafts[variantKey]?.originalText || variants[slug]?.[variantIndex] || ''; setVariantDrafts((currentDrafts) => ({ ...currentDrafts, [variantKey]: { originalText, currentText: nextText, validationStatus: 'idle', why: '', lastCheckedText: '', }, })); } function handleRevertVariant(slug, variantIndex) { const variantKey = getVariantKey(slug, variantIndex); const originalText = variantDrafts[variantKey]?.originalText || variants[slug]?.[variantIndex] || ''; setVariantDrafts((currentDrafts) => ({ ...currentDrafts, [variantKey]: createVariantDraft(originalText), })); } if (loading) { return (
Generate SMS templates for each order event.
{event.slug}
Review, edit, and choose a variant