import { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import apiClient from '../api/client'; import { useBusiness } from '../context/BusinessContext'; const BASE_PROFILE_KEYS = new Set(['providerName', 'senderId', 'dltEntityId']); const PENDING_SENDER_ID_PROFILE_NAME = 'Pending Sender ID'; function formatUpdatedAt(value) { if (!value) return 'Not updated yet'; try { return new Date(value).toLocaleString(); } catch { return 'Not updated yet'; } } function buildProfilePatchPayload(inputs = [], values = {}) { const provider = {}; const profileInputValues = {}; inputs.forEach((input) => { const rawValue = String(values[input.key] ?? '').trim(); if (!rawValue) return; if (BASE_PROFILE_KEYS.has(input.key)) { provider[input.key] = input.key === 'senderId' ? rawValue.toUpperCase() : rawValue; return; } profileInputValues[input.key] = rawValue; }); return { ...(Object.keys(provider).length > 0 ? { provider } : {}), ...(Object.keys(profileInputValues).length > 0 ? { profileInputValues } : {}), }; } function getInputInitialValues(inputs = []) { return inputs.reduce((accumulator, input) => { accumulator[input.key] = input.value || ''; return accumulator; }, {}); } function getProfileSummary(profile) { const parts = []; const provider = profile?.provider || {}; const missingCount = profile?.executionReadiness?.missingProfileInputs?.length || 0; if (provider.providerName) parts.push(provider.providerName); if (provider.senderId) parts.push(`Sender ${provider.senderId}`); if (provider.dltEntityId) parts.push('DLT ready'); if (missingCount > 0) parts.push(`${missingCount} required field${missingCount === 1 ? '' : 's'} pending`); return parts.join(' • ') || 'Profile saved. Complete the remaining setup fields to continue.'; } function isPendingSenderIdProfile(profile) { const normalizedName = String(profile?.name || '').trim(); const senderId = String(profile?.provider?.senderId || '').trim(); return (profile?.isAutoNamed === true && !senderId) || normalizedName === PENDING_SENDER_ID_PROFILE_NAME; } function DeleteProfileModal({ preview, deleting, onCancel, onConfirm }) { if (!preview) return null; const impactedTemplates = Array.isArray(preview.impactedTemplates) ? preview.impactedTemplates : []; return (

Delete cURL Profile

{preview.profile?.name || 'This profile'} will be deleted. Bound templates will be removed, but event definitions will stay.

{impactedTemplates.length > 0 ? ( <>

Affected templates

{impactedTemplates.map((template) => (

{template.eventLabel || template.eventSlug}

{template.status || 'generated'}
{template.templateId && (

DLT Template ID: {template.templateId}

)}
))}
) : (
No templates are currently bound to this profile.
)}
); } export default function GlobalSms() { const { businessId } = useParams(); const navigate = useNavigate(); const { isSetupComplete, setHasGlobalSms, setIsSetupComplete } = useBusiness(); const [loading, setLoading] = useState(true); const [profiles, setProfiles] = useState([]); const [activeProfileId, setActiveProfileId] = useState(null); const [saving, setSaving] = useState(false); const [savingInputs, setSavingInputs] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [formCurl, setFormCurl] = useState(''); const [formSetActive, setFormSetActive] = useState(true); const [inputForm, setInputForm] = useState({}); const [revealedProfiles, setRevealedProfiles] = useState({}); const [visibleProfileIds, setVisibleProfileIds] = useState({}); const [deletePreview, setDeletePreview] = useState(null); const [deletingProfileId, setDeletingProfileId] = useState(''); const activeProfile = useMemo( () => profiles.find((profile) => profile.id === activeProfileId) || null, [profiles, activeProfileId], ); const missingInputs = useMemo( () => activeProfile?.executionReadiness?.missingProfileInputs || [], [activeProfile?.executionReadiness?.missingProfileInputs], ); const hasProfiles = profiles.length > 0; const eventsPath = `/${businessId}/events`; const loadProfiles = useCallback(async () => { try { setLoading(true); const res = await apiClient.get(`/api/businesses/${businessId}/global-sms/profiles`); const fetchedProfiles = res.data?.profiles || []; const nextActiveProfileId = res.data?.activeProfileId || null; const nextActiveProfile = fetchedProfiles.find((profile) => profile.id === nextActiveProfileId) || null; const nextIsSetupComplete = nextActiveProfile?.executionReadiness?.isSetupComplete === true; setProfiles(fetchedProfiles); setActiveProfileId(nextActiveProfileId); setHasGlobalSms(fetchedProfiles.length > 0); setIsSetupComplete(nextIsSetupComplete); return { activeProfile: nextActiveProfile, hasProfile: !!nextActiveProfile, complete: nextIsSetupComplete, }; } catch { setError('Failed to load cURL profiles'); setHasGlobalSms(false); setIsSetupComplete(false); return { activeProfile: null, hasProfile: false, complete: false }; } finally { setLoading(false); } }, [businessId, setHasGlobalSms, setIsSetupComplete]); useEffect(() => { loadProfiles(); }, [loadProfiles]); useEffect(() => { setInputForm(getInputInitialValues(missingInputs)); }, [activeProfileId, missingInputs]); const ensureRevealData = useCallback(async (profileId) => { if (revealedProfiles[profileId]) return revealedProfiles[profileId]; const res = await apiClient.get(`/api/businesses/${businessId}/global-sms/profiles/${profileId}/reveal`); setRevealedProfiles((current) => ({ ...current, [profileId]: res.data })); return res.data; }, [businessId, revealedProfiles]); async function handleSubmit(event) { event.preventDefault(); if (!formCurl.trim()) return; setSaving(true); setError(''); setSuccess(''); const shouldAutoAdvance = !isSetupComplete; try { await apiClient.post(`/api/businesses/${businessId}/global-sms/profiles`, { rawCurl: formCurl.trim(), setActive: formSetActive, }); setFormCurl(''); setFormSetActive(true); setSuccess('Profile created successfully.'); const nextState = await loadProfiles(); if (shouldAutoAdvance && nextState.complete) { navigate(eventsPath); } } catch (err) { setError(err.response?.data?.error || 'Failed to save cURL profile'); } finally { setSaving(false); } } async function handleActivate(profileId) { const shouldAutoAdvance = !isSetupComplete; setError(''); setSuccess(''); try { await apiClient.post(`/api/businesses/${businessId}/global-sms/profiles/${profileId}/activate`); const nextState = await loadProfiles(); setSuccess('Active profile updated.'); if (shouldAutoAdvance && nextState.complete) { navigate(eventsPath); } } catch (err) { setError(err.response?.data?.error || 'Failed to activate profile'); } } async function handleCopyCurl(profile) { try { const revealData = await ensureRevealData(profile.id); const textToCopy = revealData?.rawCurl || profile.maskedCurl || ''; if (!textToCopy) return; await navigator.clipboard.writeText(textToCopy); setSuccess(`Copied ${profile.name} cURL.`); } catch { setError('Failed to copy the cURL command.'); } } async function handleToggleCurl(profile) { setError(''); if (!visibleProfileIds[profile.id]) { try { await ensureRevealData(profile.id); } catch (err) { setError(err.response?.data?.error || 'Failed to reveal stored cURL'); return; } } setVisibleProfileIds((current) => ({ ...current, [profile.id]: !current[profile.id], })); } async function handleProviderSubmit(event) { event.preventDefault(); if (!activeProfileId || missingInputs.length === 0) return; setSavingInputs(true); setError(''); setSuccess(''); const shouldAutoAdvance = !isSetupComplete; try { const payload = buildProfilePatchPayload(missingInputs, inputForm); await apiClient.patch(`/api/businesses/${businessId}/global-sms/profiles/${activeProfileId}`, payload); setSuccess('Required profile fields saved.'); const nextState = await loadProfiles(); if (shouldAutoAdvance && nextState.complete) { navigate(eventsPath); } } catch (err) { setError(err.response?.data?.error || 'Failed to save required profile fields'); } finally { setSavingInputs(false); } } async function handleDeleteRequest(profile) { setError(''); setSuccess(''); try { const res = await apiClient.get(`/api/businesses/${businessId}/global-sms/profiles/${profile.id}/delete-impact`); setDeletePreview(res.data); } catch (err) { setError(err.response?.data?.error || 'Failed to load delete impact'); } } async function handleDeleteConfirm() { if (!deletePreview?.profile?.id) return; setDeletingProfileId(deletePreview.profile.id); setError(''); setSuccess(''); try { await apiClient.delete(`/api/businesses/${businessId}/global-sms/profiles/${deletePreview.profile.id}`); setDeletePreview(null); setVisibleProfileIds((current) => { const nextState = { ...current }; delete nextState[deletePreview.profile.id]; return nextState; }); setRevealedProfiles((current) => { const nextState = { ...current }; delete nextState[deletePreview.profile.id]; return nextState; }); await loadProfiles(); setSuccess('Profile deleted successfully.'); } catch (err) { setError(err.response?.data?.error || 'Failed to delete profile'); } finally { setDeletingProfileId(''); } } if (loading) { return (
); } return ( <> setDeletePreview(null)} onConfirm={handleDeleteConfirm} />

Omni-channel SMS

Add and activate a validated provider cURL, complete the required profile fields, and then continue to event template setup.

{error && (
{error}
)} {success && (
{success}
)} {activeProfile ? (

Active Setup:{' '} {activeProfile.name}

{activeProfile.executionReadiness?.isSetupComplete ? 'Setup Complete' : 'Missing Information'}

Current Profile Summary

  • Provider {activeProfile.provider?.providerName || Missing}
  • Sender ID {activeProfile.provider?.senderId || Missing}
  • DLT Entity ID {activeProfile.provider?.dltEntityId || Missing}
  • Setup Status

    {getProfileSummary(activeProfile)}

{!activeProfile.executionReadiness?.isSetupComplete ? (

Complete the required fields

{missingInputs.map((input) => (
setInputForm((current) => ({ ...current, [input.key]: input.key === 'senderId' ? event.target.value.toUpperCase() : event.target.value, }))} className="w-full rounded border border-border-main bg-page-bg px-3 py-2 text-sm text-text-primary focus:ring-1 focus:ring-primary-blue" placeholder={input.label} required={input.required !== false} />
))}
) : (

Your active cURL profile is fully configured.

)}
) : hasProfiles ? (
Select an active cURL profile to continue. Your saved profiles are still available below.
) : null} {hasProfiles && (

Saved Profiles

{profiles.map((profile) => { const isActive = profile.id === activeProfileId; const isVisible = visibleProfileIds[profile.id] === true; const revealedProfile = revealedProfiles[profile.id]; const displayCurl = isVisible ? (revealedProfile?.rawCurl || profile.maskedCurl) : profile.maskedCurl; return (

{profile.name}

{isActive && ( Active Profile )} {profile.executionReadiness?.isSetupComplete ? 'Ready' : 'Needs Fields'}

Updated: {formatUpdatedAt(profile.updatedAt)}

{getProfileSummary(profile)}

Stored cURL

                          {displayCurl || 'No cURL stored.'}
                        
{!isActive && ( )}
); })}
)}

Add New Profile

Paste a provider cURL exactly once. After validation, the stored cURL becomes immutable and can only be replaced by creating a new profile.

{!hasProfiles && (

Start by adding a cURL profile

This becomes the base for validating provider details and unlocking event template generation.

)}