'use client'; import { useEffect, useMemo, useState } from 'react'; import { createClient } from '@/lib/supabase/client'; import { Contact, CustomField, MessageTemplate } from '@/types'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { ArrowLeft, ArrowRight, Eye, Loader2 } from 'lucide-react'; type VariableType = 'static' | 'field' | 'custom_field'; interface VariableMapping { type: VariableType; value: string; } interface Step3Props { template: MessageTemplate; variables: Record; onUpdate: (variables: Record) => void; onNext: () => void; onBack: () => void; } const contactFields = [ { value: 'name', label: 'Contact Name' }, { value: 'phone', label: 'Phone Number' }, { value: 'email', label: 'Email Address' }, { value: 'company', label: 'Company' }, ]; const SAMPLE_CONTACT: Contact = { id: 'sample', user_id: '', name: 'John Doe', phone: '+1234567890', email: 'john@example.com', company: 'Acme Corp', created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }; export function Step3Personalize({ template, variables, onUpdate, onNext, onBack, }: Step3Props) { const [customFields, setCustomFields] = useState([]); const [loadingFields, setLoadingFields] = useState(true); const [firstContact, setFirstContact] = useState(null); const [firstContactCustomValues, setFirstContactCustomValues] = useState< Map >(new Map()); const [loadingPreview, setLoadingPreview] = useState(true); // Load user's custom fields + a representative contact for the // live preview. Fall back to sample data if no contacts exist yet. useEffect(() => { let cancelled = false; (async () => { const supabase = createClient(); const [fieldsRes, contactRes] = await Promise.all([ supabase.from('custom_fields').select('*').order('field_name'), supabase .from('contacts') .select('*') .order('created_at', { ascending: false }) .limit(1) .maybeSingle(), ]); if (cancelled) return; setCustomFields(fieldsRes.data ?? []); setLoadingFields(false); const contact = contactRes.data ?? null; setFirstContact(contact); if (contact) { const { data: customVals } = await supabase .from('contact_custom_values') .select('custom_field_id, value') .eq('contact_id', contact.id); if (!cancelled) { const map = new Map(); for (const row of customVals ?? []) { map.set(row.custom_field_id, row.value ?? ''); } setFirstContactCustomValues(map); } } setLoadingPreview(false); })(); return () => { cancelled = true; }; }, []); const placeholders = useMemo(() => { const matches = template.body_text.match(/\{\{(\d+)\}\}/g); if (!matches) return []; return [...new Set(matches)].sort(); }, [template.body_text]); function updateVariable(key: string, patch: Partial) { const current = variables[key] ?? { type: 'static' as VariableType, value: '' }; onUpdate({ ...variables, [key]: { ...current, ...patch }, }); } /** * Substitute placeholders using the first real contact where * possible. Placeholders keyed by "{{N}}" map to variable key "N". */ const previewText = useMemo(() => { const contact = firstContact ?? SAMPLE_CONTACT; const customValues = firstContact ? firstContactCustomValues : new Map(); let text = template.body_text; for (const placeholder of placeholders) { const key = placeholder.replace(/^\{\{|\}\}$/g, ''); const mapping = variables[key]; let replacement = placeholder; if (mapping) { if (mapping.type === 'static' && mapping.value) { replacement = mapping.value; } else if (mapping.type === 'field' && mapping.value) { const fieldMap: Record = { name: contact.name, phone: contact.phone, email: contact.email, company: contact.company, }; replacement = fieldMap[mapping.value] ?? placeholder; } else if (mapping.type === 'custom_field' && mapping.value) { replacement = customValues.get(mapping.value) || placeholder; } } text = text.replaceAll(placeholder, replacement); } return text; }, [ template.body_text, variables, placeholders, firstContact, firstContactCustomValues, ]); const previewLabel = firstContact ? firstContact.name || firstContact.phone : 'sample data'; return (

Personalize Message

Map template variables to contact fields, custom fields, or static values.

{placeholders.length === 0 ? (

This template has no variables to personalize.

) : (
{placeholders.map((placeholder) => { const key = placeholder.replace(/^\{\{|\}\}$/g, ''); const mapping = variables[key] ?? { type: 'static', value: '' }; return (
{placeholder}
{mapping.type === 'static' ? ( updateVariable(key, { value: e.target.value }) } placeholder="Enter value..." className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500" /> ) : mapping.type === 'field' ? ( ) : ( )}
); })}
)} {/* Live Preview — rendered as a WhatsApp-style bubble so the user sees approximately what the recipient will see. */}

Live Preview

({previewLabel}) {loadingPreview && ( )}

{previewText}

); }