From 7acb26602e9ac40d0ff91901307d957d2aeeb14c Mon Sep 17 00:00:00 2001 From: Ritul Date: Mon, 6 Apr 2026 10:26:54 +0530 Subject: [PATCH] Changing endpoint config and adding switch for published templates --- client/src/pages/Events.jsx | 1480 +++++++++++++++++++---------- server/index.js | 3 + server/routes/businesses.js | 410 ++++---- server/routes/platformWebhooks.js | 8 + server/services/openai2.js | 614 ++++++++++-- server/services/pixelbin.js | 3 +- server/services/storagePaths.js | 32 + 7 files changed, 1803 insertions(+), 747 deletions(-) create mode 100644 server/routes/platformWebhooks.js create mode 100644 server/services/storagePaths.js diff --git a/client/src/pages/Events.jsx b/client/src/pages/Events.jsx index 5eee825..b07a979 100644 --- a/client/src/pages/Events.jsx +++ b/client/src/pages/Events.jsx @@ -1,3 +1,4 @@ +import axios from 'axios'; import { useState, useEffect, useCallback, useRef } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import apiClient from '../api/client'; @@ -5,13 +6,17 @@ import { useBusiness } from '../context/BusinessContext'; const MAX_SMS_LENGTH = 160; const DLT_VARIABLE_OPTIONS = [ - { label: '#var', token: '{#var#}' }, - { label: '#numeric', token: '{#numeric#}' }, - { label: '#url', token: '{#url#}' }, - { label: '#cbn', token: '{#cbn#}' }, + { label: 'Generic Text', token: '{#var#}' }, + { label: 'Numeric', token: '{#numeric#}' }, + { label: 'URL', token: '{#url#}' }, + { label: 'URL OTT', token: '{#urlott#}' }, + { label: 'Callback Number', token: '{#cbn#}' }, + { label: 'Email', token: '{#email#}' }, + { label: 'Alphanumeric', token: '{#alphanumeric#}' }, ]; -const DLT_TOKEN_SET = new Set(DLT_VARIABLE_OPTIONS.map((option) => option.token)); -const DLT_TOKEN_REGEX = /\{#(?:var|numeric|url|cbn)#\}/g; +const SUPPORTED_DLT_VARIABLE_OPTIONS = DLT_VARIABLE_OPTIONS; +const DLT_TOKEN_SET = new Set(SUPPORTED_DLT_VARIABLE_OPTIONS.map((option) => option.token)); +const DLT_TOKEN_REGEX = /\{#(?:var|numeric|url|urlott|cbn|email|alphanumeric)#\}/g; const DLT_TOKEN_LIKE_REGEX = /\{#[^{}]*#\}/g; const DELIVERY_EVENT_SLUGS = new Set([ 'out_for_pickup', @@ -200,6 +205,36 @@ function getVariantKey(slug, index) { return `${slug}:${index}`; } +function getSelectedDraftKey(slug) { + return `${slug}:selected`; +} + +function getDraftSuffix(draftKey = '') { + const separatorIndex = String(draftKey).lastIndexOf(':'); + return separatorIndex === -1 ? '' : String(draftKey).slice(separatorIndex + 1); +} + +function getVariantIndexFromDraftKey(draftKey = '') { + const suffix = getDraftSuffix(draftKey); + if (!suffix) return null; + if (suffix === 'selected') return null; + const index = Number(suffix); + return Number.isInteger(index) ? index : null; +} + +function resolveDraftBaseText(slug, draftKey, variantsBySlug, selectedTemplateBySlug) { + if (!draftKey) return ''; + const draftSuffix = getDraftSuffix(draftKey); + + if (draftSuffix === 'selected') { + return String(selectedTemplateBySlug?.[slug]?.selectedTemplate || ''); + } + + const variantIndex = Number(draftSuffix); + if (!Number.isInteger(variantIndex)) return ''; + return String(variantsBySlug?.[slug]?.[variantIndex] || ''); +} + function createVariantDraft(text = '') { return { originalText: text, @@ -273,22 +308,376 @@ function buildTemplateUiState(templates = []) { nextTemplateStatusBySlug[template.eventSlug] = normalizedStatus; nextGenState[template.eventSlug] = 'selected'; nextSelectedTemplateBySlug[template.eventSlug] = buildSelectedTemplatePreview(template); - return; } if (Array.isArray(template.generatedVariants) && template.generatedVariants.length > 0) { nextVariants[template.eventSlug] = template.generatedVariants; - nextGenState[template.eventSlug] = 'done'; + if (!template.selectedTemplate) nextGenState[template.eventSlug] = 'done'; } }); return { nextVariants, nextGenState, nextTemplateStatusBySlug, nextSelectedTemplateBySlug }; } +function TemplateWorkspaceModal({ + eventSlug, + eventLabel, + statusConfig, + loading, + generatedVariants, + selectedTemplatePreview, + activeDraftKey, + activeDraft, + openVariableMenuKey, + onClose, + onChooseVariant, + onDraftChange, + onTrackSelection, + onToggleVariableMenu, + onInsertVariable, + onRevert, + onCheck, + onSelect, + onRegenerate, + setVariableMenuRef, + setTextareaRef, + workspaceError, + selectingDraftKey, + showClosePrompt, + closePromptTitle, + closePromptDescription, + discardingWorkspace, + onKeepWorkspace, + onDiscardWorkspace, +}) { + const currentText = activeDraft?.currentText || ''; + const originalText = activeDraft?.originalText || ''; + const validationStatus = activeDraft?.validationStatus || 'idle'; + const currentMatchesCheckedText = activeDraft?.lastCheckedText === currentText; + const isEdited = currentText !== originalText; + const dltTokenCount = countDltTokens(currentText); + const invalidDltTokens = getInvalidDltTokens(currentText); + const hasMalformedDltToken = hasMalformedDltFragments(currentText); + const hasInvalidPlaceholder = invalidDltTokens.length > 0 || hasMalformedDltToken; + const tooLong = currentText.length > MAX_SMS_LENGTH; + const hasChosenDraft = !!activeDraftKey; + const canRunCheck = hasChosenDraft && !tooLong && !hasInvalidPlaceholder && validationStatus !== 'checking'; + const failedCurrentCheck = validationStatus === 'rejected' && currentMatchesCheckedText; + const canSelectDraft = hasChosenDraft + && !tooLong + && !hasInvalidPlaceholder + && !failedCurrentCheck + && (!isEdited || (validationStatus === 'approved' && currentMatchesCheckedText)); + const canInsertVariable = hasChosenDraft; + const activeVariantIndex = getVariantIndexFromDraftKey(activeDraftKey); + + return ( +
+
+
+
+
+

{eventLabel}

+ {statusConfig && ( + + + {statusConfig.label} + + )} +
+
+ +
+ + {loading ? ( +
+
+
+ ) : ( + <> +
+
+ {workspaceError && ( +
+ {workspaceError} +
+ )} + + {generatedVariants.length > 0 ? ( +
+
+

Variants

+ + {generatedVariants.length} option{generatedVariants.length === 1 ? '' : 's'} + +
+
+ {generatedVariants.map((variant, index) => { + const variantKey = getVariantKey(eventSlug, index); + const isActive = activeVariantIndex === index; + return ( + + ); + })} +
+
+ ) : selectedTemplatePreview ? ( +
+
+ {selectedTemplatePreview.templateId ? ( + + Template ID {selectedTemplatePreview.templateId} + + ) : ( + + Template ID pending + + )} +
+
+ ) : null} + +
+ {hasChosenDraft ? ( +
+
+
+ + {getDraftSuffix(activeDraftKey) === 'selected' ? 'Current Template' : `Variant ${activeVariantIndex + 1}`} + + + {isEdited ? 'Edited Draft' : 'Working Draft'} + + {validationStatus === 'checking' && ( + + Checking + + )} + {validationStatus === 'approved' && currentMatchesCheckedText && ( + + Passed + + )} + {validationStatus === 'rejected' && currentMatchesCheckedText && ( + + Needs changes + + )} +
+ +
+ + + {openVariableMenuKey === activeDraftKey && ( +
+
+

Insert variable

+
+
+ {DLT_VARIABLE_OPTIONS.map((option) => ( + + ))} +
+
+ )} +
+
+ +