@embedreach/components 0.3.13 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2788,6 +2788,57 @@ const duplicateBusinessAutomation = async (automationId, name, description2) =>
2788
2788
  );
2789
2789
  return response.data;
2790
2790
  };
2791
+ const findSegmentByName = async (name) => {
2792
+ const response = await baseRequest(
2793
+ `/automations/internal/segments/find-by-name?name=${encodeURIComponent(name)}`,
2794
+ {
2795
+ method: "GET"
2796
+ }
2797
+ );
2798
+ return response.data;
2799
+ };
2800
+ var AutomationTriggerType = /* @__PURE__ */ ((AutomationTriggerType2) => {
2801
+ AutomationTriggerType2["ONE_TIME"] = "one_time";
2802
+ AutomationTriggerType2["TRIGGER_BASED"] = "trigger_based";
2803
+ AutomationTriggerType2["DATE_BASED"] = "date_based";
2804
+ return AutomationTriggerType2;
2805
+ })(AutomationTriggerType || {});
2806
+ var ReachEventNamesEnum = /* @__PURE__ */ ((ReachEventNamesEnum2) => {
2807
+ ReachEventNamesEnum2["USER_ENTERS_SEGMENT"] = "USER_ENTERS_SEGMENT";
2808
+ return ReachEventNamesEnum2;
2809
+ })(ReachEventNamesEnum || {});
2810
+ var BusinessAutomationTypeEnum = /* @__PURE__ */ ((BusinessAutomationTypeEnum2) => {
2811
+ BusinessAutomationTypeEnum2["MANAGED"] = "managed";
2812
+ BusinessAutomationTypeEnum2["CUSTOM"] = "custom";
2813
+ return BusinessAutomationTypeEnum2;
2814
+ })(BusinessAutomationTypeEnum || {});
2815
+ var ActionType = /* @__PURE__ */ ((ActionType2) => {
2816
+ ActionType2["SEND_COMMUNICATION"] = "send_communication";
2817
+ ActionType2["WAIT_UNTIL_NEXT_FIXED_TIME"] = "wait_until_next_fixed_time";
2818
+ return ActionType2;
2819
+ })(ActionType || {});
2820
+ var AutomationStatus = /* @__PURE__ */ ((AutomationStatus2) => {
2821
+ AutomationStatus2["DRAFT"] = "draft";
2822
+ AutomationStatus2["ACTIVE"] = "active";
2823
+ AutomationStatus2["RUNNING"] = "running";
2824
+ AutomationStatus2["COMPLETED"] = "completed";
2825
+ AutomationStatus2["FAILED"] = "failed";
2826
+ AutomationStatus2["DEACTIVATED"] = "deactivated";
2827
+ AutomationStatus2["SENDING"] = "sending";
2828
+ AutomationStatus2["SCHEDULED"] = "scheduled";
2829
+ AutomationStatus2["PARTIALLY_CANCELLED"] = "partially_cancelled";
2830
+ AutomationStatus2["CANCELLED"] = "cancelled";
2831
+ AutomationStatus2["ARCHIVED"] = "archived";
2832
+ return AutomationStatus2;
2833
+ })(AutomationStatus || {});
2834
+ var ChannelIntegrationTypeEnum = /* @__PURE__ */ ((ChannelIntegrationTypeEnum2) => {
2835
+ ChannelIntegrationTypeEnum2["EMAIL"] = "email";
2836
+ ChannelIntegrationTypeEnum2["SMS"] = "sms";
2837
+ return ChannelIntegrationTypeEnum2;
2838
+ })(ChannelIntegrationTypeEnum || {});
2839
+ const shouldPoll = (automationStatus) => {
2840
+ return automationStatus !== AutomationStatus.DRAFT && automationStatus !== AutomationStatus.COMPLETED && automationStatus !== AutomationStatus.PARTIALLY_CANCELLED && automationStatus !== AutomationStatus.CANCELLED;
2841
+ };
2791
2842
  const TOAST_LIMIT = 1;
2792
2843
  const TOAST_REMOVE_DELAY = 1e6;
2793
2844
  let count$8 = 0;
@@ -2961,6 +3012,16 @@ const resetCommunicationGroupToDefault = async (args) => {
2961
3012
  );
2962
3013
  return response.data;
2963
3014
  };
3015
+ const postBusinessMergeFieldFallbacks = async (mergeFieldFallbacks) => {
3016
+ const response = await baseRequest(
3017
+ `/businesses/merge-field-fallbacks`,
3018
+ {
3019
+ method: "PATCH",
3020
+ body: JSON.stringify({ mergeFieldFallbacks })
3021
+ }
3022
+ );
3023
+ return response.data;
3024
+ };
2964
3025
  const getLinkClickStats = async (automationId) => {
2965
3026
  const response = await baseRequest(
2966
3027
  `${COMMUNICATION_GROUP_PATH}/${automationId}/link-click-stats`
@@ -3079,6 +3140,36 @@ const useResetCommunicationGroupToDefault = () => {
3079
3140
  isPending: resetCommunicationGroupToDefaultMutation.isPending
3080
3141
  };
3081
3142
  };
3143
+ const usePostBusinessMergeFieldFallbacks = () => {
3144
+ const queryClient = useQueryClient();
3145
+ const { toast: toast2 } = useToast();
3146
+ const mutation = useMutation({
3147
+ mutationFn: ({
3148
+ mergeFieldFallbacks
3149
+ }) => postBusinessMergeFieldFallbacks(mergeFieldFallbacks),
3150
+ onSuccess: () => {
3151
+ queryClient.invalidateQueries({
3152
+ queryKey: communicationGroupQueryKeys.all
3153
+ });
3154
+ toast2({
3155
+ title: "Merge field fallbacks updated",
3156
+ description: "Merge field fallbacks have been updated"
3157
+ });
3158
+ },
3159
+ onError: () => {
3160
+ toast2({
3161
+ title: "Failed to update merge field fallbacks",
3162
+ description: "Please reach out to your administrator"
3163
+ });
3164
+ }
3165
+ });
3166
+ return {
3167
+ postBusinessMergeFieldFallbacks: mutation.mutate,
3168
+ isPosting: mutation.isPending,
3169
+ postError: mutation.error,
3170
+ isPostSuccess: mutation.isSuccess
3171
+ };
3172
+ };
3082
3173
  const useGetLinkClickStats = (automationId) => {
3083
3174
  const query = useQuery({
3084
3175
  queryKey: ["link-click-stats", automationId],
@@ -3096,7 +3187,14 @@ const useGetBusinessAutomation = (automationId) => {
3096
3187
  const queryClient = useQueryClient();
3097
3188
  const getAutomationQuery = useQuery({
3098
3189
  queryKey: automationKeys.single(automationId),
3099
- queryFn: () => getAutomation(automationId)
3190
+ queryFn: () => getAutomation(automationId),
3191
+ refetchInterval: (data) => {
3192
+ const shouldPollResult = shouldPoll(data?.state.data?.status);
3193
+ if (shouldPollResult) {
3194
+ return 2e3;
3195
+ }
3196
+ return false;
3197
+ }
3100
3198
  });
3101
3199
  return {
3102
3200
  // Get automation query
@@ -3124,7 +3222,13 @@ const useGetBusinessAutomationSentCommunications = (args) => {
3124
3222
  // It will be false if placeholder data is being shown, even though a new fetch is in progress (`isFetching` will be true).
3125
3223
  // This provides a much smoother UX, as the UI doesn't jump to a loading state on pagination or filter changes
3126
3224
  // if previous data is available to show in the meantime.
3127
- placeholderData: keepPreviousData
3225
+ placeholderData: keepPreviousData,
3226
+ refetchInterval: () => {
3227
+ if (shouldPoll(args.automationStatus)) {
3228
+ return 2e3;
3229
+ }
3230
+ return false;
3231
+ }
3128
3232
  });
3129
3233
  return {
3130
3234
  // Get automation sent communications query
@@ -3255,45 +3359,6 @@ const useDuplicateBusinessAutomation = () => {
3255
3359
  isDuplicateSuccess: duplicateAutomationMutation.isSuccess
3256
3360
  };
3257
3361
  };
3258
- var AutomationTriggerType = /* @__PURE__ */ ((AutomationTriggerType2) => {
3259
- AutomationTriggerType2["ONE_TIME"] = "one_time";
3260
- AutomationTriggerType2["TRIGGER_BASED"] = "trigger_based";
3261
- AutomationTriggerType2["DATE_BASED"] = "date_based";
3262
- return AutomationTriggerType2;
3263
- })(AutomationTriggerType || {});
3264
- var ReachEventNamesEnum = /* @__PURE__ */ ((ReachEventNamesEnum2) => {
3265
- ReachEventNamesEnum2["USER_ENTERS_SEGMENT"] = "USER_ENTERS_SEGMENT";
3266
- return ReachEventNamesEnum2;
3267
- })(ReachEventNamesEnum || {});
3268
- var BusinessAutomationTypeEnum = /* @__PURE__ */ ((BusinessAutomationTypeEnum2) => {
3269
- BusinessAutomationTypeEnum2["MANAGED"] = "managed";
3270
- BusinessAutomationTypeEnum2["CUSTOM"] = "custom";
3271
- return BusinessAutomationTypeEnum2;
3272
- })(BusinessAutomationTypeEnum || {});
3273
- var ActionType = /* @__PURE__ */ ((ActionType2) => {
3274
- ActionType2["SEND_COMMUNICATION"] = "send_communication";
3275
- ActionType2["WAIT_UNTIL_NEXT_FIXED_TIME"] = "wait_until_next_fixed_time";
3276
- return ActionType2;
3277
- })(ActionType || {});
3278
- var AutomationStatus = /* @__PURE__ */ ((AutomationStatus2) => {
3279
- AutomationStatus2["DRAFT"] = "draft";
3280
- AutomationStatus2["ACTIVE"] = "active";
3281
- AutomationStatus2["RUNNING"] = "running";
3282
- AutomationStatus2["COMPLETED"] = "completed";
3283
- AutomationStatus2["FAILED"] = "failed";
3284
- AutomationStatus2["DEACTIVATED"] = "deactivated";
3285
- AutomationStatus2["SENDING"] = "sending";
3286
- AutomationStatus2["SCHEDULED"] = "scheduled";
3287
- AutomationStatus2["PARTIALLY_CANCELLED"] = "partially_cancelled";
3288
- AutomationStatus2["CANCELLED"] = "cancelled";
3289
- AutomationStatus2["ARCHIVED"] = "archived";
3290
- return AutomationStatus2;
3291
- })(AutomationStatus || {});
3292
- var ChannelIntegrationTypeEnum = /* @__PURE__ */ ((ChannelIntegrationTypeEnum2) => {
3293
- ChannelIntegrationTypeEnum2["EMAIL"] = "email";
3294
- ChannelIntegrationTypeEnum2["SMS"] = "sms";
3295
- return ChannelIntegrationTypeEnum2;
3296
- })(ChannelIntegrationTypeEnum || {});
3297
3362
  class InvalidTokenError extends Error {
3298
3363
  }
3299
3364
  InvalidTokenError.prototype.name = "InvalidTokenError";
@@ -25678,6 +25743,16 @@ const Info = createLucideIcon("Info", [
25678
25743
  ["path", { d: "M12 16v-4", key: "1dtifu" }],
25679
25744
  ["path", { d: "M12 8h.01", key: "e9boi3" }]
25680
25745
  ]);
25746
+ /**
25747
+ * @license lucide-react v0.464.0 - ISC
25748
+ *
25749
+ * This source code is licensed under the ISC license.
25750
+ * See the LICENSE file in the root directory of this source tree.
25751
+ */
25752
+ const Link$1 = createLucideIcon("Link", [
25753
+ ["path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71", key: "1cjeqo" }],
25754
+ ["path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71", key: "19qd67" }]
25755
+ ]);
25681
25756
  /**
25682
25757
  * @license lucide-react v0.464.0 - ISC
25683
25758
  *
@@ -29244,6 +29319,47 @@ const applyThemeStyles = (styles2, darkStyles) => {
29244
29319
  if (existingStyles) {
29245
29320
  existingStyles.remove();
29246
29321
  }
29322
+ const isGoogleFont = (font) => {
29323
+ if (!font) return null;
29324
+ const match2 = font.match(/^["']?([\w\s]+)["']?/);
29325
+ if (!match2) return null;
29326
+ const fontName = match2[1].trim();
29327
+ const systemFonts = [
29328
+ "Inter",
29329
+ "system-ui",
29330
+ "-apple-system",
29331
+ "BlinkMacSystemFont",
29332
+ "Segoe UI",
29333
+ "Roboto",
29334
+ "sans-serif",
29335
+ "serif",
29336
+ "monospace",
29337
+ "Arial",
29338
+ "Georgia",
29339
+ "Times New Roman",
29340
+ "Courier New",
29341
+ "Plus Jakarta Sans"
29342
+ ];
29343
+ if (systemFonts.some((sys) => fontName === sys)) return null;
29344
+ return fontName;
29345
+ };
29346
+ const googleFonts = /* @__PURE__ */ new Set();
29347
+ const fontBody = styles2["font-body"];
29348
+ const fontHeading = styles2["font-heading"];
29349
+ const bodyFont = isGoogleFont(fontBody);
29350
+ const headingFont = isGoogleFont(fontHeading);
29351
+ if (bodyFont) googleFonts.add(bodyFont);
29352
+ if (headingFont) googleFonts.add(headingFont);
29353
+ googleFonts.forEach((fontName) => {
29354
+ const fontId = `reach-google-font-${fontName.replace(/\s+/g, "-")}`;
29355
+ if (!document.getElementById(fontId)) {
29356
+ const link2 = document.createElement("link");
29357
+ link2.id = fontId;
29358
+ link2.rel = "stylesheet";
29359
+ link2.href = `https://fonts.googleapis.com/css?family=${encodeURIComponent(fontName)}:400,500,700&display=swap`;
29360
+ document.head.appendChild(link2);
29361
+ }
29362
+ });
29247
29363
  const styleElement2 = document.createElement("style");
29248
29364
  styleElement2.id = "reach-theme-styles";
29249
29365
  let cssText = `
@@ -32360,6 +32476,8 @@ const measure$1 = {
32360
32476
  total_revenue: "Total Revenue",
32361
32477
  total_revenue_description: "The complete revenue generated by your business during the selected period from all sources, not just advertising campaigns. \n\nThis includes revenue from direct traffic, organic search, referrals, and any other channels in addition to paid advertising.",
32362
32478
  website_visitors: "Website Visitors",
32479
+ unknown_revenue: "Unknown Revenue",
32480
+ unknown_revenue_description: "Revenue from purchases where we were unable to find customer interactions (like site visits or phone calls) in the 90 days before buying.",
32363
32481
  website_visitors_description: "The number of unique visitors who came to your website from your advertising campaigns during the selected period. \n\nThis metric is tracked using analytics tools and represents actual people who successfully navigated from your ads to your website."
32364
32482
  },
32365
32483
  settings: {
@@ -32367,7 +32485,6 @@ const measure$1 = {
32367
32485
  description: "Manage your account settings",
32368
32486
  connected_accounts: "Connected Accounts",
32369
32487
  not_running_paid_ads: "Not running paid ads? Use AI to create a Google Ads campaign",
32370
- not_running_paid_ads_tooltip: "Contact your administrator to learn more about this feature and how to activate it for your account",
32371
32488
  privacy_policy: "Privacy Policy",
32372
32489
  quick_links: "Quick Links",
32373
32490
  setup_tracking_pixel: "Setup tracking pixel",
@@ -32388,7 +32505,11 @@ const measure$1 = {
32388
32505
  purchases_tooltip: "Purchases are the number of times a user made a purchase after clicking on your ad. \n\nThe percentage is calculated by dividing the number of purchases by the number of clicks (from the previous bar line)"
32389
32506
  },
32390
32507
  roas_chart: {
32391
- title: "Return on Ad Spend Trends"
32508
+ title: "Return on Ad Spend Trends",
32509
+ attribution_tooltip_title: "How revenue is counted in this chart",
32510
+ attribution_tooltip_description: "For each day, we show the total ad spend, revenue, and ROAS (return on ad spend) from people who clicked an ad that day.\n\nIf someone clicks an ad and buys within 90 days, their purchase is counted for the day they clicked the ad.\n\nThe most recent 90 days may look low, since more purchases can still come in.",
32511
+ incomplete_banner: "Revenue and ROAS for recent days may be lower than their final values, since we continue to count purchases made up to 90 days after someone clicks an ad.",
32512
+ incomplete_tooltip: "Still updating (<90 days)"
32392
32513
  }
32393
32514
  },
32394
32515
  campaigns: {
@@ -32466,7 +32587,8 @@ const dates$1 = {
32466
32587
  last_week: "Last week",
32467
32588
  last_month: "Last month",
32468
32589
  last_quarter: "Last 90 days",
32469
- last_year: "Last year"
32590
+ last_year: "Last year",
32591
+ all_time: "All time"
32470
32592
  };
32471
32593
  const campaigns$1 = {
32472
32594
  setup: {
@@ -32545,16 +32667,15 @@ const analytics$1 = {
32545
32667
  google_tag_manager: {
32546
32668
  step_1: "Go to https://tagmanager.google.com",
32547
32669
  step_2: "Sign in to your Google Account",
32548
- step_3: "Click the Accounts tab, then select Create Account",
32549
- step_4: "Enter an account name and select your country location",
32550
- step_5: "In Container Setup, enter a container name (e.g., your website URL)",
32551
- step_6: 'Select "Web" as the container type',
32552
- step_7: "Click Create and agree to the Terms of Service",
32553
- step_8: "Once created, you'll receive two code snippets to install:",
32554
- step_9: " Place the first snippet in the <head> of your page",
32555
- step_10: " Place the second snippet immediately after the opening <body> tag",
32556
- step_11: "In the Tags tab, click the + button to create a new tag",
32557
- step_12: 'Select "Google Analytics: Universal Analytics" as the tag type',
32670
+ step_3: 'In the Tags tab, click the "New" button to create a new tag',
32671
+ step_4: 'Choose "Custom HTML" as the tag type',
32672
+ step_5: "Paste the tracking code above into the HTML field",
32673
+ step_6: "Click anywhere in the triggering section to edit this setting",
32674
+ step_7: 'Select "Initialization - All Pages" as the trigger',
32675
+ step_8: "Click Save to save the tag",
32676
+ step_9: "Click Submit in the top right",
32677
+ step_10: "Add a version name and description",
32678
+ step_11: "Click Publish to deploy your changes",
32558
32679
  special_note: "Make sure you have the Google Tag Manager container code already installed on your site before adding this tag."
32559
32680
  },
32560
32681
  hubspot: {
@@ -32774,6 +32895,8 @@ const broadcast_other$1 = "Broadcasts";
32774
32895
  const trigger_based$1 = "Trigger Based";
32775
32896
  const insight$1 = "Insight";
32776
32897
  const insight_other$1 = "Insights";
32898
+ const merge_field$1 = "Merge Field";
32899
+ const merge_field_other$1 = "Merge Fields";
32777
32900
  const enEngage = {
32778
32901
  user: user$1,
32779
32902
  user_other: user_other$1,
@@ -32785,7 +32908,9 @@ const enEngage = {
32785
32908
  broadcast_other: broadcast_other$1,
32786
32909
  trigger_based: trigger_based$1,
32787
32910
  insight: insight$1,
32788
- insight_other: insight_other$1
32911
+ insight_other: insight_other$1,
32912
+ merge_field: merge_field$1,
32913
+ merge_field_other: merge_field_other$1
32789
32914
  };
32790
32915
  const account_id = "ID de cuenta";
32791
32916
  const account_name = "Nombre de cuenta";
@@ -32908,6 +33033,8 @@ const measure = {
32908
33033
  total_revenue: "Ingresos totales",
32909
33034
  total_revenue_description: "Los ingresos totales generados por tu negocio durante el período seleccionado, no solo a través de campañas de anuncios. \n\nEsto incluye ingresos de tráfico directo, búsqueda orgánica, referencias y cualquier otro canal además de campañas de anuncios.",
32910
33035
  website_visitors: "Visitantes de tu sitio web",
33036
+ unknown_revenue: "Ingresos desconocidos",
33037
+ unknown_revenue_description: "Ingresos de compras donde no pudimos encontrar interacciones del cliente (como visitas al sitio o llamadas telefónicas) en los 90 días antes de la compra.",
32911
33038
  website_visitors_description: "El número de visitantes únicos que llegaron a tu sitio web a través de tus campañas de anuncios durante el período seleccionado. \n\nEsta métrica se rastrea usando herramientas de analytics y representa a las personas que realmente navegaron desde tus anuncios a tu sitio web."
32912
33039
  },
32913
33040
  settings: {
@@ -32915,7 +33042,6 @@ const measure = {
32915
33042
  description: "Gestiona tus ajustes de cuenta",
32916
33043
  connected_accounts: "Cuentas conectadas",
32917
33044
  not_running_paid_ads: "¿No estás ejecutando anuncios pagados? Usa IA para crear una campaña de Google Ads",
32918
- not_running_paid_ads_tooltip: "Contacta a tu administrador para aprender más sobre esta función y cómo activarla para tu cuenta",
32919
33045
  privacy_policy: "Política de privacidad",
32920
33046
  quick_links: "Enlaces rápidos",
32921
33047
  setup_tracking_pixel: "Configurar pixel de seguimiento",
@@ -32936,7 +33062,11 @@ const measure = {
32936
33062
  purchases_tooltip: "Las compras son el número de veces que un usuario hizo una compra después de hacer clic en tu anuncio. \n\nEl porcentaje se calcula dividiendo el número de compras por el número de clics (de la línea anterior)"
32937
33063
  },
32938
33064
  roas_chart: {
32939
- title: "Tendencias de Retorno de Inversión en Anuncios"
33065
+ title: "Tendencias de Retorno de Inversión en Anuncios",
33066
+ attribution_tooltip_title: "Cómo se cuentan los ingresos en este gráfico",
33067
+ attribution_tooltip_description: "Para cada día, mostramos el gasto total en anuncios, ingresos y ROAS (retorno de inversión en anuncios) de personas que hicieron clic en un anuncio ese día.\n\nSi alguien hace clic en un anuncio y compra dentro de 90 días, su compra se cuenta para el día en que hizo clic en el anuncio.\n\nLos últimos 90 días pueden parecer bajos, ya que pueden seguir llegando más compras.",
33068
+ incomplete_banner: "Los ingresos y ROAS de los días más recientes pueden ser inferiores a sus valores finales, ya que seguimos contando compras realizadas hasta 90 días después de que alguien haga clic en un anuncio.",
33069
+ incomplete_tooltip: "Aún actualizando (<90 días)"
32940
33070
  }
32941
33071
  },
32942
33072
  campaigns: {
@@ -33014,7 +33144,8 @@ const dates = {
33014
33144
  last_week: "Semana pasada",
33015
33145
  last_month: "Mes pasado",
33016
33146
  last_quarter: "Últimos 90 días",
33017
- last_year: "Año pasado"
33147
+ last_year: "Año pasado",
33148
+ all_time: "Todo el tiempo"
33018
33149
  };
33019
33150
  const campaigns = {
33020
33151
  setup: {
@@ -33093,16 +33224,15 @@ const analytics = {
33093
33224
  google_tag_manager: {
33094
33225
  step_1: "Ve a https://tagmanager.google.com",
33095
33226
  step_2: "Inicia sesión en tu cuenta de Google",
33096
- step_3: "Haz click en la pestaña de Cuentas y selecciona Crear cuenta",
33097
- step_4: "Ingresa un nombre para tu cuenta y selecciona tu ubicación",
33098
- step_5: "En Container Setup, ingresa un nombre para tu contenedor (e.g., tu URL de sitio web)",
33099
- step_6: 'Selecciona "Web" como tipo de contenedor',
33100
- step_7: "Haz click en Crear y acepta los Términos de Servicio",
33101
- step_8: "Una vez creado, recibirás dos fragmentos de código para instalar:",
33102
- step_9: " Coloca el primer fragmento en la etiqueta <head> de tu página",
33103
- step_10: " Coloca el segundo fragmento justo después de la etiqueta <body>",
33104
- step_11: "En la pestaña de Etiquetas, haz click en el botón + para crear una nueva etiqueta",
33105
- step_12: 'Selecciona "Google Analytics: Universal Analytics" como tipo de etiqueta',
33227
+ step_3: 'En la pestaña de Etiquetas, haz click en el botón "Nuevo" para crear una nueva etiqueta',
33228
+ step_4: 'Elige "Código personalizado" como tipo de etiqueta',
33229
+ step_5: "Pega el código de seguimiento en el campo HTML",
33230
+ step_6: "Haz click en cualquier sección del trigger para editar esta configuración",
33231
+ step_7: 'Elige "Inicialización - Todas las páginas" como trigger',
33232
+ step_8: "Haz click en Guardar para guardar la etiqueta",
33233
+ step_9: "Haz click en Enviar en la parte superior derecha",
33234
+ step_10: "Añade un nombre y descripción de la versión",
33235
+ step_11: "Haz click en Publicar para desplegar tus cambios",
33106
33236
  special_note: "Asegúrate de tener el código del contenedor de Google Tag Manager instalado en tu sitio web antes de agregar esta etiqueta."
33107
33237
  },
33108
33238
  hubspot: {
@@ -33322,6 +33452,8 @@ const broadcast_other = "Transmisiones";
33322
33452
  const trigger_based = "Basado en un disparador";
33323
33453
  const insight = "Estadísticas";
33324
33454
  const insight_other = "Estadísticas";
33455
+ const merge_field = "Campo de fusión";
33456
+ const merge_field_other = "Campos de fusión";
33325
33457
  const esEngage = {
33326
33458
  user,
33327
33459
  user_other,
@@ -33333,7 +33465,9 @@ const esEngage = {
33333
33465
  broadcast_other,
33334
33466
  trigger_based,
33335
33467
  insight,
33336
- insight_other
33468
+ insight_other,
33469
+ merge_field,
33470
+ merge_field_other
33337
33471
  };
33338
33472
  const transaction_table_id_header$1 = "Transaction ID";
33339
33473
  const transaction_table_created_header$1 = "Transaction Created";
@@ -33521,6 +33655,8 @@ const defaultTranslations = {
33521
33655
  total_revenue: "Total Revenue",
33522
33656
  total_revenue_description: "The complete revenue generated by your business during the selected period from all sources, not just advertising campaigns. \n\nThis includes revenue from direct traffic, organic search, referrals, and any other channels in addition to paid advertising.",
33523
33657
  website_visitors: "Website Visitors",
33658
+ unknown_revenue: "Unknown Revenue",
33659
+ unknown_revenue_description: "Revenue from purchases where we were unable to find customer interactions (like site visits or phone calls) in the 90 days before buying.",
33524
33660
  website_visitors_description: "The number of unique visitors who came to your website from your advertising campaigns during the selected period. \n\nThis metric is tracked using analytics tools and represents actual people who successfully navigated from your ads to your website."
33525
33661
  },
33526
33662
  settings: {
@@ -33528,7 +33664,6 @@ const defaultTranslations = {
33528
33664
  description: "Manage your account settings",
33529
33665
  connected_accounts: "Connected Accounts",
33530
33666
  not_running_paid_ads: "Not running paid ads? Use AI to create a Google Ads campaign",
33531
- not_running_paid_ads_tooltip: "Contact your administrator to learn more about this feature and how to activate it for your account",
33532
33667
  privacy_policy: "Privacy Policy",
33533
33668
  quick_links: "Quick Links",
33534
33669
  setup_tracking_pixel: "Setup tracking pixel",
@@ -33549,7 +33684,11 @@ const defaultTranslations = {
33549
33684
  purchases_tooltip: "Purchases are the number of times a user made a purchase after clicking on your ad. \n\nThe percentage is calculated by dividing the number of purchases by the number of clicks (from the previous bar line)"
33550
33685
  },
33551
33686
  roas_chart: {
33552
- title: "Return on Ad Spend Trends"
33687
+ title: "Return on Ad Spend Trends",
33688
+ attribution_tooltip_title: "How revenue is counted in this chart",
33689
+ attribution_tooltip_description: "For each day, we show the total ad spend, revenue, and ROAS (return on ad spend) from people who clicked an ad that day.\n\nIf someone clicks an ad and buys within 90 days, their purchase is counted for the day they clicked the ad.\n\nThe most recent 90 days may look low, since more purchases can still come in.",
33690
+ incomplete_banner: "Revenue and ROAS for recent days may be lower than their final values, since we continue to count purchases made up to 90 days after someone clicks an ad.",
33691
+ incomplete_tooltip: "Still updating (<90 days)"
33553
33692
  }
33554
33693
  },
33555
33694
  campaigns: {
@@ -33628,7 +33767,8 @@ const defaultTranslations = {
33628
33767
  last_week: "Last week",
33629
33768
  last_month: "Last month",
33630
33769
  last_quarter: "Last 90 days",
33631
- last_year: "Last year"
33770
+ last_year: "Last year",
33771
+ all_time: "All time"
33632
33772
  },
33633
33773
  campaigns: {
33634
33774
  setup: {
@@ -33707,16 +33847,15 @@ const defaultTranslations = {
33707
33847
  google_tag_manager: {
33708
33848
  step_1: "Go to https://tagmanager.google.com",
33709
33849
  step_2: "Sign in to your Google Account",
33710
- step_3: "Click the Accounts tab, then select Create Account",
33711
- step_4: "Enter an account name and select your country location",
33712
- step_5: "In Container Setup, enter a container name (e.g., your website URL)",
33713
- step_6: 'Select "Web" as the container type',
33714
- step_7: "Click Create and agree to the Terms of Service",
33715
- step_8: "Once created, you'll receive two code snippets to install:",
33716
- step_9: " Place the first snippet in the <head> of your page",
33717
- step_10: " Place the second snippet immediately after the opening <body> tag",
33718
- step_11: "In the Tags tab, click the + button to create a new tag",
33719
- step_12: 'Select "Google Analytics: Universal Analytics" as the tag type',
33850
+ step_3: 'In the Tags tab, click the "New" button to create a new tag',
33851
+ step_4: 'Choose "Custom HTML" as the tag type',
33852
+ step_5: "Paste the tracking code above into the HTML field",
33853
+ step_6: "Click anywhere in the triggering section to edit this setting",
33854
+ step_7: 'Select "Initialization - All Pages" as the trigger',
33855
+ step_8: "Click Save to save the tag",
33856
+ step_9: "Click Submit in the top right",
33857
+ step_10: "Add a version name and description",
33858
+ step_11: "Click Publish to deploy your changes",
33720
33859
  special_note: "Make sure you have the Google Tag Manager container code already installed on your site before adding this tag."
33721
33860
  },
33722
33861
  hubspot: {
@@ -48907,16 +49046,16 @@ const getSegment = async (segmentId) => {
48907
49046
  return null;
48908
49047
  }
48909
49048
  const response = await baseRequest(
48910
- `${BUSINESS_SEGMENT_PATH}/${segmentId}`
49049
+ `${BUSINESS_SEGMENT_PATH}/${segmentId}`,
49050
+ {
49051
+ method: "GET"
49052
+ }
48911
49053
  );
48912
49054
  return response.data;
48913
49055
  };
48914
- const updateSegment = async (params, segmentId) => {
48915
- if (!segmentId) {
48916
- return null;
48917
- }
49056
+ const updateSegment = async (id2, params) => {
48918
49057
  const response = await baseRequest(
48919
- `${BUSINESS_SEGMENT_PATH}/${segmentId}`,
49058
+ `${BUSINESS_SEGMENT_PATH}/${id2}`,
48920
49059
  {
48921
49060
  method: "PATCH",
48922
49061
  body: JSON.stringify(params)
@@ -48924,16 +49063,12 @@ const updateSegment = async (params, segmentId) => {
48924
49063
  );
48925
49064
  return response.data;
48926
49065
  };
48927
- const textToSegment = async (args) => {
48928
- const { userInput, userCustomVerb } = args;
49066
+ const textToSegment = async (params) => {
48929
49067
  const response = await baseRequest(
48930
49068
  `${BUSINESS_SEGMENT_PATH}/text-to-segment`,
48931
49069
  {
48932
49070
  method: "POST",
48933
- body: JSON.stringify({
48934
- text: userInput,
48935
- userCustomVerb: userCustomVerb || "user"
48936
- })
49071
+ body: JSON.stringify(params)
48937
49072
  }
48938
49073
  );
48939
49074
  return response.data;
@@ -49047,7 +49182,12 @@ const useGetSegment = (segmentId) => {
49047
49182
  const useUpdateSegment = (segmentId) => {
49048
49183
  const queryClient = useQueryClient();
49049
49184
  const updateSegmentMutation = useMutation({
49050
- mutationFn: (params) => updateSegment(params, segmentId),
49185
+ mutationFn: (params) => {
49186
+ if (!segmentId) {
49187
+ throw new Error("Segment ID is required");
49188
+ }
49189
+ return updateSegment(segmentId, params);
49190
+ },
49051
49191
  onSuccess: () => {
49052
49192
  queryClient.invalidateQueries({ queryKey: segmentKeys.all });
49053
49193
  }
@@ -49074,6 +49214,7 @@ var BusinessSegmentTypeEnum = /* @__PURE__ */ ((BusinessSegmentTypeEnum2) => {
49074
49214
  BusinessSegmentTypeEnum2["ALL_USERS"] = "all_users";
49075
49215
  BusinessSegmentTypeEnum2["MANAGED"] = "managed";
49076
49216
  BusinessSegmentTypeEnum2["CUSTOM"] = "custom";
49217
+ BusinessSegmentTypeEnum2["ONE_OFF"] = "one_off";
49077
49218
  return BusinessSegmentTypeEnum2;
49078
49219
  })(BusinessSegmentTypeEnum || {});
49079
49220
  var ConditionOperatorEnumType = /* @__PURE__ */ ((ConditionOperatorEnumType2) => {
@@ -49297,22 +49438,29 @@ const ActionButtons = ({
49297
49438
  )
49298
49439
  ] }) });
49299
49440
  };
49300
- const BigSelector = ({ onClick, title: title2, subtitle, icon, selected, disabled = false }) => {
49441
+ const BigSelector = ({
49442
+ onClick,
49443
+ title: title2,
49444
+ subtitle,
49445
+ icon,
49446
+ selected,
49447
+ disabled = false,
49448
+ hideSelectedText = false
49449
+ }) => {
49301
49450
  return /* @__PURE__ */ jsxs(
49302
49451
  "button",
49303
49452
  {
49304
49453
  onClick,
49305
- className: `relative flex flex-col items-center justify-center rounded-lg border-2 p-6 transition-all duration-300 ease-in-out hover:shadow-lg ${selected ? "border-blue-500 shadow-lg" : "border-gray-200 "} ${disabled ? "opacity-50 cursor-not-allowed hover:shadow-none" : "hover:scale-[1.04] scale-[1.02]"} ${!selected && !disabled && "hover:border-blue-500 hover:bg-gray-50"}`,
49454
+ className: `relative flex flex-col items-center justify-center rounded-lg border-2 p-6 transition-all duration-300 ease-in-out hover:shadow-lg ${selected ? "[border-color:hsl(var(--reach-primary))] shadow-lg" : "border-gray-200 "} ${disabled ? "opacity-50 cursor-not-allowed hover:shadow-none" : "hover:scale-[1.04] scale-[1.02]"} ${!selected && !disabled && "hover:[border-color:hsl(var(--reach-primary))] hover:bg-gray-50"}`,
49306
49455
  children: [
49307
49456
  icon,
49308
49457
  /* @__PURE__ */ jsxs("div", { className: "text-center mt-3", children: [
49309
49458
  /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-primary", children: title2 }),
49310
49459
  /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground mt-1", children: subtitle })
49311
49460
  ] }),
49312
- selected && // inverse the colors from regular bg-card text-card-foreground pattern
49313
- /* @__PURE__ */ jsxs("div", { className: "absolute top-1 right-1 bg-card-foreground rounded-full px-3 py-1 flex items-center gap-1", children: [
49314
- /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-card" }),
49315
- /* @__PURE__ */ jsx("span", { className: "text-card text-xs hidden md:inline", children: "Selected" })
49461
+ selected && /* @__PURE__ */ jsxs("div", { className: "absolute top-1 right-1 bg-card rounded-full px-3 py-1 flex items-center gap-1", children: [
49462
+ /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-card-foreground" }),
49463
+ !hideSelectedText && /* @__PURE__ */ jsx("span", { className: "text-card-foreground text-xs hidden md:inline", children: "Selected" })
49316
49464
  ] })
49317
49465
  ]
49318
49466
  }
@@ -49477,6 +49625,7 @@ const statusArchived = "reach-styles-module__statusArchived___XlN-L";
49477
49625
  const segmentAllUsers = "reach-styles-module__segmentAllUsers___1aXpc";
49478
49626
  const segmentManaged = "reach-styles-module__segmentManaged___o0gOu";
49479
49627
  const segmentCustom = "reach-styles-module__segmentCustom___xc3lG";
49628
+ const beta = "reach-styles-module__beta___n9qPf";
49480
49629
  const styles$5 = {
49481
49630
  base: base$4,
49482
49631
  "default": "reach-styles-module__default___zeGU1",
@@ -49503,7 +49652,8 @@ const styles$5 = {
49503
49652
  statusArchived,
49504
49653
  segmentAllUsers,
49505
49654
  segmentManaged,
49506
- segmentCustom
49655
+ segmentCustom,
49656
+ beta
49507
49657
  };
49508
49658
  const badgeVariants = cva([styles$5.base], {
49509
49659
  variants: {
@@ -49523,11 +49673,14 @@ const badgeVariants = cva([styles$5.base], {
49523
49673
  segmentAllUsers: styles$5.segmentAllUsers,
49524
49674
  segmentManaged: styles$5.segmentManaged,
49525
49675
  segmentCustom: styles$5.segmentCustom,
49676
+ // Not real since we'll never show it
49677
+ segmentOneOff: styles$5.segmentManaged,
49526
49678
  statusScheduled: styles$5.statusScheduled,
49527
49679
  statusQueued: styles$5.statusQueued,
49528
49680
  statusPartiallyCancelled: styles$5.statusPartiallyCancelled,
49529
49681
  statusCancelled: styles$5.statusCancelled,
49530
- statusArchived: styles$5.statusArchived
49682
+ statusArchived: styles$5.statusArchived,
49683
+ beta: styles$5.beta
49531
49684
  },
49532
49685
  size: {
49533
49686
  default: styles$5.sizeDefault,
@@ -67062,6 +67215,8 @@ const getTypeDescription = (type) => {
67062
67215
  return `System-defined ${t$2("engage:segment").toLowerCase()} that are not editable or deletable.`;
67063
67216
  case BusinessSegmentTypeEnum.CUSTOM:
67064
67217
  return `Custom ${t$2("engage:segment").toLowerCase()} that you can edit and delete as needed.`;
67218
+ case BusinessSegmentTypeEnum.ONE_OFF:
67219
+ return `One-off ${t$2("engage:segment").toLowerCase()} that is not editable or deletable.`;
67065
67220
  default:
67066
67221
  throw UnreachableCaseStatement(type, BusinessSegmentTypeEnum);
67067
67222
  }
@@ -67074,6 +67229,8 @@ const getSegmentTypeVariant = (type) => {
67074
67229
  return "segmentManaged";
67075
67230
  case BusinessSegmentTypeEnum.CUSTOM:
67076
67231
  return "segmentCustom";
67232
+ case BusinessSegmentTypeEnum.ONE_OFF:
67233
+ return "segmentOneOff";
67077
67234
  default:
67078
67235
  throw UnreachableCaseStatement(type, BusinessSegmentTypeEnum);
67079
67236
  }
@@ -67109,7 +67266,9 @@ function createNameColumn(nameAccessor = "name", headerText, showDescription = f
67109
67266
  const typeDotStyles = {
67110
67267
  [BusinessSegmentTypeEnum.ALL_USERS]: "bg-purple-500",
67111
67268
  [BusinessSegmentTypeEnum.MANAGED]: "bg-teal-500",
67112
- [BusinessSegmentTypeEnum.CUSTOM]: "bg-amber-500"
67269
+ [BusinessSegmentTypeEnum.CUSTOM]: "bg-amber-500",
67270
+ // Not real since we'll never show it
67271
+ [BusinessSegmentTypeEnum.ONE_OFF]: "bg-gray-500"
67113
67272
  };
67114
67273
  const dotColor = type ? typeDotStyles[type] : status ? badgeStyles[status] : "bg-blue-500";
67115
67274
  return /* @__PURE__ */ jsx("div", { className: "w-full flex gap-6 justify-between items-center", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-1 items-center w-full", children: [
@@ -67487,16 +67646,16 @@ const AttributionDialogContent = () => {
67487
67646
  return /* @__PURE__ */ jsxs(
67488
67647
  motion.div,
67489
67648
  {
67490
- className: "space-y-8 p-6",
67649
+ className: "space-y-8 p-2",
67491
67650
  variants: containerVariants,
67492
67651
  initial: "hidden",
67493
67652
  animate: "visible",
67494
67653
  children: [
67495
- /* @__PURE__ */ jsxs(motion.div, { className: "text-center space-y-3", variants: itemVariants, children: [
67654
+ /* @__PURE__ */ jsxs(motion.div, { className: "space-y-3", variants: itemVariants, children: [
67496
67655
  /* @__PURE__ */ jsxs(
67497
67656
  motion.h2,
67498
67657
  {
67499
- className: "text-2xl font-semibold flex items-center justify-center gap-2",
67658
+ className: "text-2xl font-semibold flex items-center justify-start gap-2",
67500
67659
  initial: { scale: 0.9 },
67501
67660
  animate: { scale: 1 },
67502
67661
  transition: { duration: 0.5, ease: "easeOut" },
@@ -67630,6 +67789,41 @@ const AttributionDialogContent = () => {
67630
67789
  ] })
67631
67790
  ] }, scenario.id)) }) })
67632
67791
  ] }),
67792
+ /* @__PURE__ */ jsxs(Card, { children: [
67793
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
67794
+ /* @__PURE__ */ jsxs(CardTitle, { className: "flex items-center gap-2", children: [
67795
+ /* @__PURE__ */ jsx(Eye, { className: "h-5 w-5" }),
67796
+ 'Understanding "Unknown" Revenue'
67797
+ ] }),
67798
+ /* @__PURE__ */ jsx(CardDescription, { children: "Sometimes purchases can't be attributed to specific marketing touchpoints" })
67799
+ ] }),
67800
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
67801
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: `Revenue appears as "Unknown" when we can't connect a purchase to any marketing touchpoint within our 90-day attribution window. Here are common scenarios:` }),
67802
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
67803
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
67804
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-amber-500 mt-2 flex-shrink-0" }),
67805
+ /* @__PURE__ */ jsxs("div", { children: [
67806
+ /* @__PURE__ */ jsx("h5", { className: "font-medium text-sm", children: "Attribution Window Expired" }),
67807
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Customer clicked an ad and purchased 91+ days later, outside our tracking window" })
67808
+ ] })
67809
+ ] }),
67810
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
67811
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-amber-500 mt-2 flex-shrink-0" }),
67812
+ /* @__PURE__ */ jsxs("div", { children: [
67813
+ /* @__PURE__ */ jsx("h5", { className: "font-medium text-sm", children: "Identity Mismatch" }),
67814
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Customer used different email/phone or didn't provide contact information when they visited your site vs. when they made the purchase." })
67815
+ ] })
67816
+ ] }),
67817
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
67818
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-amber-500 mt-2 flex-shrink-0" }),
67819
+ /* @__PURE__ */ jsxs("div", { children: [
67820
+ /* @__PURE__ */ jsx("h5", { className: "font-medium text-sm", children: "Technical Limitations" }),
67821
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Ad blockers, privacy settings, or technical issues prevented tracking" })
67822
+ ] })
67823
+ ] })
67824
+ ] })
67825
+ ] })
67826
+ ] }),
67633
67827
  /* @__PURE__ */ jsx(Card, { className: "bg-blue-50 border-blue-200", children: /* @__PURE__ */ jsx(CardContent, { className: "pt-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
67634
67828
  /* @__PURE__ */ jsx(Clock, { className: "h-5 w-5 text-blue-600 mt-0.5 flex-shrink-0" }),
67635
67829
  /* @__PURE__ */ jsxs("div", { children: [
@@ -67738,7 +67932,7 @@ const InfoTooltip = ({
67738
67932
  onClick: handleAttributionClick,
67739
67933
  variant: "ghost",
67740
67934
  size: "sm",
67741
- className: "flex justify-start w-full items-start gap-2 text-xs text-blue-400 hover:text-blue-200 hover:bg-transparent underline my-1 py-2 px-0 h-auto text-pretty",
67935
+ className: "flex justify-start w-full items-start gap-2 text-xs text-blue-400 hover:text-blue-200 hover:bg-transparent underline my-1 py-2 px-0 h-auto text-pretty text-left",
67742
67936
  children: [
67743
67937
  /* @__PURE__ */ jsx(ExternalLink, { className: "text-blue-400 h-3 w-3" }),
67744
67938
  t3("measure.dashboard.methodology.tooltip")
@@ -90213,9 +90407,9 @@ var ChannelType = /* @__PURE__ */ ((ChannelType2) => {
90213
90407
  return ChannelType2;
90214
90408
  })(ChannelType || {});
90215
90409
  const HEIGHT_BUFFER = 10;
90216
- const CHECK_DELAYS = [0, 100, 500];
90410
+ const CHECK_DELAYS = [0, 100, 500, 1e3, 2e3];
90217
90411
  const DEFAULT_EMAIL_PREVIEW_HEIGHT = 400;
90218
- const EMAIL_PREVIEW_HEIGHT_CALCULATION_TIMEOUT = 3e3;
90412
+ const EMAIL_PREVIEW_HEIGHT_CALCULATION_TIMEOUT = 5e3;
90219
90413
  const SMS_LIMITS = {
90220
90414
  RECOMMENDED_LIMIT: 320
90221
90415
  };
@@ -90276,53 +90470,146 @@ const createHeightMonitor = () => {
90276
90470
  return `
90277
90471
  (function() {
90278
90472
  let lastHeight = 0;
90473
+ let checkCount = 0;
90474
+ const MAX_CHECKS = 20; // Prevent infinite loops
90279
90475
 
90280
- // Calculate max height from various measurements
90476
+ // Calculate max height from various measurements with better error handling
90281
90477
  function getContentHeight() {
90282
- const { body, documentElement: html } = document;
90283
- return Math.max(
90284
- body.scrollHeight,
90285
- body.offsetHeight,
90286
- html.clientHeight,
90287
- html.scrollHeight,
90288
- html.offsetHeight
90289
- );
90478
+ try {
90479
+ const { body, documentElement: html } = document;
90480
+ if (!body || !html) {
90481
+ return 0;
90482
+ }
90483
+
90484
+ const measurements = [
90485
+ body.scrollHeight,
90486
+ body.offsetHeight,
90487
+ html.clientHeight,
90488
+ html.scrollHeight,
90489
+ html.offsetHeight
90490
+ ].filter(h => h > 0 && isFinite(h));
90491
+
90492
+ if (measurements.length === 0) {
90493
+ console.warn('HeightMonitor: No valid height measurements found');
90494
+ return 0;
90495
+ }
90496
+
90497
+ return Math.max(...measurements);
90498
+ } catch (error) {
90499
+ console.warn('HeightMonitor: Error calculating height', error);
90500
+ return 0;
90501
+ }
90290
90502
  }
90291
90503
 
90292
90504
  function reportHeight() {
90293
- const height = getContentHeight();
90294
- if (height !== lastHeight && height > 0) {
90295
- lastHeight = height;
90296
- window.parent.postMessage({
90297
- type: 'IFRAME_HEIGHT_UPDATE',
90298
- height: height + ${HEIGHT_BUFFER}
90299
- }, '*');
90505
+ try {
90506
+ const height = getContentHeight();
90507
+ if (height > 0 && height !== lastHeight) {
90508
+ lastHeight = height;
90509
+ window.parent.postMessage({
90510
+ type: 'IFRAME_HEIGHT_UPDATE',
90511
+ height: height + ${HEIGHT_BUFFER}
90512
+ }, '*');
90513
+ }
90514
+ } catch (error) {
90515
+ console.warn('HeightMonitor: Error reporting height', error);
90516
+ }
90517
+ }
90518
+
90519
+ // Enhanced height monitoring with multiple strategies
90520
+ function setupHeightMonitoring() {
90521
+ try {
90522
+ // Strategy 1: ResizeObserver for real-time monitoring
90523
+ if (window.ResizeObserver) {
90524
+ const observer = new ResizeObserver(() => {
90525
+ requestAnimationFrame(() => {
90526
+ reportHeight();
90527
+ checkCount++;
90528
+ });
90529
+ });
90530
+ observer.observe(document.body);
90531
+ observer.observe(document.documentElement);
90532
+ } else {
90533
+ console.warn('HeightMonitor: ResizeObserver not available, using fallback');
90534
+ }
90535
+
90536
+ // Strategy 2: Handle image loading with better error handling
90537
+ const images = document.querySelectorAll('img');
90538
+ images.forEach(img => {
90539
+ if (!img.complete) {
90540
+ img.addEventListener('load', () => {
90541
+ setTimeout(reportHeight, 100); // Small delay to ensure layout is complete
90542
+ });
90543
+ img.addEventListener('error', () => {
90544
+ setTimeout(reportHeight, 100);
90545
+ });
90546
+ }
90547
+ });
90548
+
90549
+ // Strategy 3: Watch for DOM changes with throttling
90550
+ let mutationTimeout;
90551
+ new MutationObserver(() => {
90552
+ if (mutationTimeout) clearTimeout(mutationTimeout);
90553
+ mutationTimeout = setTimeout(() => {
90554
+ requestAnimationFrame(reportHeight);
90555
+ checkCount++;
90556
+ }, 100); // Throttle mutations
90557
+ }).observe(document.body, {
90558
+ childList: true,
90559
+ subtree: true,
90560
+ attributes: true,
90561
+ attributeFilter: ['style', 'class']
90562
+ });
90563
+
90564
+ // Strategy 4: Periodic checks with exponential backoff
90565
+ let checkDelay = 100;
90566
+ const periodicCheck = () => {
90567
+ if (checkCount >= MAX_CHECKS) {
90568
+ console.warn('HeightMonitor: Max checks reached, stopping periodic checks');
90569
+ return;
90570
+ }
90571
+
90572
+ reportHeight();
90573
+ checkCount++;
90574
+
90575
+ // Exponential backoff for periodic checks
90576
+ checkDelay = Math.min(checkDelay * 1.5, 2000);
90577
+ setTimeout(periodicCheck, checkDelay);
90578
+ };
90579
+
90580
+ // Start periodic checks after initial setup
90581
+ setTimeout(periodicCheck, 500);
90582
+
90583
+ } catch (error) {
90584
+ console.warn('HeightMonitor: Error setting up monitoring', error);
90300
90585
  }
90301
90586
  }
90302
90587
 
90303
- // Setup observers for real-time monitoring
90304
- if (window.ResizeObserver) {
90305
- const observer = new ResizeObserver(() => requestAnimationFrame(reportHeight));
90306
- observer.observe(document.body);
90307
- observer.observe(document.documentElement);
90588
+ // Initial setup with multiple triggers
90589
+ if (document.readyState === 'loading') {
90590
+ document.addEventListener('DOMContentLoaded', setupHeightMonitoring);
90591
+ } else {
90592
+ setupHeightMonitoring();
90308
90593
  }
90309
90594
 
90310
- // Handle image loading
90311
- document.querySelectorAll('img').forEach(img => {
90312
- if (!img.complete) {
90313
- img.addEventListener('load', reportHeight);
90314
- img.addEventListener('error', reportHeight);
90315
- }
90595
+ window.addEventListener('load', () => {
90596
+ setTimeout(() => {
90597
+ reportHeight();
90598
+ checkCount++;
90599
+ }, 100);
90316
90600
  });
90317
90601
 
90318
- // Watch for DOM changes
90319
- new MutationObserver(() => requestAnimationFrame(reportHeight))
90320
- .observe(document.body, { childList: true, subtree: true });
90321
-
90322
90602
  // Initial checks with progressive delays
90323
- window.addEventListener('load', () => setTimeout(reportHeight, 100));
90324
- document.addEventListener('DOMContentLoaded', reportHeight);
90325
- ${CHECK_DELAYS.map((delay2) => `setTimeout(reportHeight, ${delay2});`).join("\n ")}
90603
+ ${CHECK_DELAYS.map((delay2) => `setTimeout(() => { reportHeight(); checkCount++; }, ${delay2});`).join("\n ")}
90604
+
90605
+ // Final fallback check
90606
+ setTimeout(() => {
90607
+ if (lastHeight === 0) {
90608
+ console.warn('HeightMonitor: No height reported, forcing final check');
90609
+ reportHeight();
90610
+ }
90611
+ }, 4000);
90612
+
90326
90613
  })();
90327
90614
  `;
90328
90615
  };
@@ -90330,9 +90617,16 @@ const EmailPreviewHtmlRenderer = ({
90330
90617
  html,
90331
90618
  onHeightChange
90332
90619
  }) => {
90333
- const iframeRef = useRef(null);
90334
90620
  const timeoutRef = useRef();
90335
90621
  const [state, setState] = useState({ type: "loading" });
90622
+ const [iframeElement, setIframeElement] = useState(
90623
+ null
90624
+ );
90625
+ const setIframeRef = useCallback((element) => {
90626
+ if (element) {
90627
+ setIframeElement(element);
90628
+ }
90629
+ }, []);
90336
90630
  const communicationGroup = useCommunicationGroup();
90337
90631
  const { getMergeFields: getMergeFields2, isGetting: isGettingMergeFields } = useGetMergeFields(
90338
90632
  communicationGroup?.id ?? void 0
@@ -90357,6 +90651,24 @@ const EmailPreviewHtmlRenderer = ({
90357
90651
  },
90358
90652
  [onHeightChange]
90359
90653
  );
90654
+ const handleFallback = useCallback(() => {
90655
+ setState({ type: "error", fallbackHeight: DEFAULT_EMAIL_PREVIEW_HEIGHT });
90656
+ handleHeightUpdate(DEFAULT_EMAIL_PREVIEW_HEIGHT);
90657
+ }, [handleHeightUpdate]);
90658
+ const estimateHeightFromContent = useCallback((htmlContent) => {
90659
+ try {
90660
+ const lines = htmlContent.split("\n").length;
90661
+ const estimatedHeight = Math.max(200, Math.min(800, lines * 20));
90662
+ return estimatedHeight;
90663
+ } catch (error2) {
90664
+ return DEFAULT_EMAIL_PREVIEW_HEIGHT;
90665
+ }
90666
+ }, []);
90667
+ const forceStateChange = useCallback(() => {
90668
+ const estimatedHeight = estimateHeightFromContent(prettyHtml);
90669
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90670
+ handleHeightUpdate(estimatedHeight);
90671
+ }, [prettyHtml, estimateHeightFromContent, handleHeightUpdate]);
90360
90672
  useEffect(() => {
90361
90673
  if (prettyHtml) {
90362
90674
  setState({ type: "calculating" });
@@ -90366,68 +90678,83 @@ const EmailPreviewHtmlRenderer = ({
90366
90678
  }
90367
90679
  }, [prettyHtml]);
90368
90680
  useEffect(() => {
90369
- if (!prettyHtml || !iframeRef.current || state.type === "ready") return;
90370
- const iframe = iframeRef.current;
90681
+ if (!prettyHtml || !iframeElement) {
90682
+ return;
90683
+ }
90684
+ if (state.type === "ready") {
90685
+ return;
90686
+ }
90687
+ const iframe = iframeElement;
90688
+ const timeoutDuration = Math.max(
90689
+ EMAIL_PREVIEW_HEIGHT_CALCULATION_TIMEOUT,
90690
+ 5e3
90691
+ );
90371
90692
  timeoutRef.current = setTimeout(() => {
90372
- setState({ type: "error", fallbackHeight: DEFAULT_EMAIL_PREVIEW_HEIGHT });
90373
- handleHeightUpdate(DEFAULT_EMAIL_PREVIEW_HEIGHT);
90374
- }, EMAIL_PREVIEW_HEIGHT_CALCULATION_TIMEOUT);
90693
+ handleFallback();
90694
+ }, timeoutDuration);
90695
+ const aggressiveTimeout = setTimeout(() => {
90696
+ if (state.type === "calculating") {
90697
+ forceStateChange();
90698
+ }
90699
+ }, 1e4);
90700
+ const estimatedHeight = estimateHeightFromContent(prettyHtml);
90375
90701
  const setupHeightMonitoring = () => {
90376
90702
  try {
90377
90703
  const doc2 = iframe.contentDocument || iframe.contentWindow?.document;
90378
90704
  if (!doc2) {
90379
- setState({
90380
- type: "error",
90381
- fallbackHeight: DEFAULT_EMAIL_PREVIEW_HEIGHT
90382
- });
90705
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90706
+ handleHeightUpdate(estimatedHeight);
90383
90707
  return;
90384
90708
  }
90385
90709
  const injectStyles2 = () => {
90386
- const existingStyles = doc2.querySelectorAll(
90387
- "style[data-merge-field-pills]"
90388
- );
90389
- existingStyles.forEach((style22) => style22.remove());
90390
- const style2 = doc2.createElement("style");
90391
- style2.textContent = "html, body { height: auto !important; min-height: 0 !important; }";
90392
- doc2.head.appendChild(style2);
90393
- const pillStyle = doc2.createElement("style");
90394
- pillStyle.setAttribute("data-merge-field-pills", "true");
90395
- pillStyle.textContent = `
90396
- .merge-field-pill {
90397
- display: inline-block !important;
90398
- padding: 4px 8px !important;
90399
- border-radius: 6px !important;
90400
- background: #f3f4f6 !important;
90401
- color: #6b7280 !important;
90402
- font-size: 0.875rem !important;
90403
- border: 1px solid #e5e7eb !important;
90404
- font-weight: 500 !important;
90405
- margin: 0 2px !important;
90406
- vertical-align: middle !important;
90407
- line-height: 1.2 !important;
90408
- white-space: nowrap !important;
90409
- box-sizing: border-box !important;
90410
- }
90411
-
90412
- /* Ensure pills are visible even with conflicting styles */
90413
- span.merge-field-pill {
90414
- display: inline-block !important;
90415
- background: #f3f4f6 !important;
90416
- color: #6b7280 !important;
90417
- border: 1px solid #e5e7eb !important;
90418
- }
90419
-
90420
- /* Force override any inherited styles */
90421
- * .merge-field-pill {
90422
- display: inline-block !important;
90423
- background: #f3f4f6 !important;
90424
- color: #6b7280 !important;
90425
- border: 1px solid #e5e7eb !important;
90426
- padding: 4px 8px !important;
90427
- border-radius: 6px !important;
90428
- }
90429
- `;
90430
- doc2.head.appendChild(pillStyle);
90710
+ try {
90711
+ const existingStyles = doc2.querySelectorAll(
90712
+ "style[data-merge-field-pills]"
90713
+ );
90714
+ existingStyles.forEach((style22) => style22.remove());
90715
+ const style2 = doc2.createElement("style");
90716
+ style2.textContent = "html, body { height: auto !important; min-height: 0 !important; }";
90717
+ doc2.head.appendChild(style2);
90718
+ const pillStyle = doc2.createElement("style");
90719
+ pillStyle.setAttribute("data-merge-field-pills", "true");
90720
+ pillStyle.textContent = `
90721
+ .merge-field-pill {
90722
+ display: inline-block !important;
90723
+ padding: 4px 8px !important;
90724
+ border-radius: 6px !important;
90725
+ background: #f3f4f6 !important;
90726
+ color: #6b7280 !important;
90727
+ font-size: 0.875rem !important;
90728
+ border: 1px solid #e5e7eb !important;
90729
+ font-weight: 500 !important;
90730
+ margin: 0 2px !important;
90731
+ vertical-align: middle !important;
90732
+ line-height: 1.2 !important;
90733
+ white-space: nowrap !important;
90734
+ box-sizing: border-box !important;
90735
+ }
90736
+
90737
+ /* Ensure pills are visible even with conflicting styles */
90738
+ span.merge-field-pill {
90739
+ display: inline-block !important;
90740
+ background: #f3f4f6 !important;
90741
+ color: #6b7280 !important;
90742
+ border: 1px solid #e5e7eb !important;
90743
+ }
90744
+
90745
+ /* Force override any inherited styles */
90746
+ * .merge-field-pill {
90747
+ display: inline-block !important;
90748
+ background: #f3f4f6 !important;
90749
+ color: #6b7280 !important;
90750
+ border: 1px solid #e5e7eb !important;
90751
+ padding: 4px 8px !important;
90752
+ border-radius: 6px !important;
90753
+ }
90754
+ `;
90755
+ doc2.head.appendChild(pillStyle);
90756
+ } catch (styleError) {
90757
+ }
90431
90758
  };
90432
90759
  if (doc2.readyState === "complete" || doc2.readyState === "interactive") {
90433
90760
  injectStyles2();
@@ -90435,14 +90762,57 @@ const EmailPreviewHtmlRenderer = ({
90435
90762
  doc2.addEventListener("DOMContentLoaded", injectStyles2);
90436
90763
  doc2.addEventListener("load", injectStyles2);
90437
90764
  }
90438
- const script = doc2.createElement("script");
90439
- script.textContent = createHeightMonitor();
90440
- doc2.body.appendChild(script);
90765
+ try {
90766
+ const script = doc2.createElement("script");
90767
+ script.textContent = createHeightMonitor();
90768
+ doc2.body.appendChild(script);
90769
+ setTimeout(() => {
90770
+ if (state.type === "calculating") {
90771
+ try {
90772
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
90773
+ if (iframeDoc) {
90774
+ const bodyHeight = iframeDoc.body?.scrollHeight || iframeDoc.body?.offsetHeight;
90775
+ if (bodyHeight && bodyHeight > 0) {
90776
+ handleHeightUpdate(bodyHeight + 10);
90777
+ } else {
90778
+ setState({
90779
+ type: "error",
90780
+ fallbackHeight: estimatedHeight
90781
+ });
90782
+ handleHeightUpdate(estimatedHeight);
90783
+ }
90784
+ }
90785
+ } catch (directError) {
90786
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90787
+ handleHeightUpdate(estimatedHeight);
90788
+ }
90789
+ }
90790
+ }, 2e3);
90791
+ } catch (scriptError) {
90792
+ setTimeout(() => {
90793
+ try {
90794
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
90795
+ if (iframeDoc) {
90796
+ const bodyHeight = iframeDoc.body?.scrollHeight || iframeDoc.body?.offsetHeight;
90797
+ if (bodyHeight && bodyHeight > 0) {
90798
+ handleHeightUpdate(bodyHeight + 10);
90799
+ } else {
90800
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90801
+ handleHeightUpdate(estimatedHeight);
90802
+ }
90803
+ } else {
90804
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90805
+ handleHeightUpdate(estimatedHeight);
90806
+ }
90807
+ } catch (error2) {
90808
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90809
+ handleHeightUpdate(estimatedHeight);
90810
+ }
90811
+ }, 1e3);
90812
+ }
90441
90813
  } catch (error2) {
90442
- setState({
90443
- type: "error",
90444
- fallbackHeight: DEFAULT_EMAIL_PREVIEW_HEIGHT
90445
- });
90814
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90815
+ handleHeightUpdate(estimatedHeight);
90446
90816
  }
90447
90817
  };
90448
90818
  const handleHeightMessage = (event) => {
@@ -90457,20 +90827,49 @@ const EmailPreviewHtmlRenderer = ({
90457
90827
  }
90458
90828
  };
90459
90829
  const handleError = () => {
90460
- setState({ type: "error", fallbackHeight: DEFAULT_EMAIL_PREVIEW_HEIGHT });
90830
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90831
+ handleHeightUpdate(estimatedHeight);
90832
+ };
90833
+ const handleLoad = () => {
90834
+ setupHeightMonitoring();
90461
90835
  };
90462
- iframe.addEventListener("load", setupHeightMonitoring);
90836
+ iframe.addEventListener("load", handleLoad);
90463
90837
  iframe.addEventListener("error", handleError);
90464
90838
  window.addEventListener("message", handleHeightMessage);
90839
+ setTimeout(() => {
90840
+ if (state.type === "calculating") {
90841
+ try {
90842
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
90843
+ if (iframeDoc && iframeDoc.readyState === "complete") {
90844
+ setupHeightMonitoring();
90845
+ } else {
90846
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90847
+ handleHeightUpdate(estimatedHeight);
90848
+ }
90849
+ } catch (error2) {
90850
+ setState({ type: "error", fallbackHeight: estimatedHeight });
90851
+ handleHeightUpdate(estimatedHeight);
90852
+ }
90853
+ }
90854
+ }, 1e3);
90465
90855
  return () => {
90466
- iframe.removeEventListener("load", setupHeightMonitoring);
90856
+ iframe.removeEventListener("load", handleLoad);
90467
90857
  iframe.removeEventListener("error", handleError);
90468
90858
  window.removeEventListener("message", handleHeightMessage);
90469
90859
  if (timeoutRef.current) {
90470
90860
  clearTimeout(timeoutRef.current);
90471
90861
  }
90862
+ clearTimeout(aggressiveTimeout);
90472
90863
  };
90473
- }, [prettyHtml, handleHeightUpdate, state.type]);
90864
+ }, [
90865
+ prettyHtml,
90866
+ handleHeightUpdate,
90867
+ handleFallback,
90868
+ forceStateChange,
90869
+ estimateHeightFromContent,
90870
+ state.type,
90871
+ iframeElement
90872
+ ]);
90474
90873
  if (isGettingMergeFields && getMergeFields2 === void 0) {
90475
90874
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-5", children: /* @__PURE__ */ jsx(BasicLoader, { text: "Fetching merge fields..." }) });
90476
90875
  }
@@ -90483,7 +90882,7 @@ const EmailPreviewHtmlRenderer = ({
90483
90882
  /* @__PURE__ */ jsx(
90484
90883
  "iframe",
90485
90884
  {
90486
- ref: iframeRef,
90885
+ ref: setIframeRef,
90487
90886
  title: "Email Preview",
90488
90887
  srcDoc: prettyHtml,
90489
90888
  className: "w-full block",
@@ -90551,32 +90950,32 @@ const EmailPreviewHtmlRenderer = ({
90551
90950
  ) })
90552
90951
  ] });
90553
90952
  };
90554
- const HELP_TOPICS = [
90555
- {
90556
- id: "dynamic-content",
90557
- title: "Dynamic Content",
90558
- image: "https://cdn.embedreach.com/assets/engage/merge-tags-demo.gif",
90559
- imageAlt: "Merge Tags Demo",
90560
- imageDesc: "Demonstration: Adding merge tags to personalize your emails",
90561
- selectorTitle: "Dynamic Content",
90562
- selectorSubtitle: "Dynamic content is a way to insert dynamic content into your email like name, email, or other details.",
90563
- selectorIcon: /* @__PURE__ */ jsx(Merge, {})
90564
- },
90565
- {
90566
- id: "responsive-design",
90567
- title: "Responsive Design",
90568
- image: "https://cdn.embedreach.com/assets/engage/layout-mobile-desktop.gif",
90569
- imageAlt: "Mobile Desktop Switch Demo",
90570
- imageDesc: "Demonstration: Switching between mobile and desktop preview modes",
90571
- selectorTitle: "Responsive Design",
90572
- selectorSubtitle: "Switch between mobile and desktop preview modes to ensure your emails look great on all devices.",
90573
- selectorIcon: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
90574
- /* @__PURE__ */ jsx(Smartphone, { className: "w-4 h-4" }),
90575
- /* @__PURE__ */ jsx(Monitor, { className: "w-4 h-4" })
90576
- ] })
90577
- }
90578
- ];
90579
90953
  const EmailPreviewHelpDialog = () => {
90954
+ const HELP_TOPICS = [
90955
+ {
90956
+ id: "dynamic-content",
90957
+ title: "Dynamic Content",
90958
+ image: "https://cdn.embedreach.com/assets/engage/merge-tags-demo.gif",
90959
+ imageAlt: `${t$2("engage:merge_field")} Demo`,
90960
+ imageDesc: `Demonstration: Adding ${t$2("engage:merge_field").toLowerCase()} to personalize your emails`,
90961
+ selectorTitle: "Dynamic Content",
90962
+ selectorSubtitle: "Dynamic content is a way to insert dynamic content into your email like name, email, or other details.",
90963
+ selectorIcon: /* @__PURE__ */ jsx(Merge, {})
90964
+ },
90965
+ {
90966
+ id: "responsive-design",
90967
+ title: "Responsive Design",
90968
+ image: "https://cdn.embedreach.com/assets/engage/layout-mobile-desktop.gif",
90969
+ imageAlt: "Mobile Desktop Switch Demo",
90970
+ imageDesc: "Demonstration: Switching between mobile and desktop preview modes",
90971
+ selectorTitle: "Responsive Design",
90972
+ selectorSubtitle: "Switch between mobile and desktop preview modes to ensure your emails look great on all devices.",
90973
+ selectorIcon: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
90974
+ /* @__PURE__ */ jsx(Smartphone, { className: "w-4 h-4" }),
90975
+ /* @__PURE__ */ jsx(Monitor, { className: "w-4 h-4" })
90976
+ ] })
90977
+ }
90978
+ ];
90580
90979
  const { toggleEmailHelpDialog: toggleEmailHelpDialog2, data: business } = useBusiness$1();
90581
90980
  const { toast: toast2 } = useToast();
90582
90981
  const [selectedTopic, setSelectedTopic] = useState("dynamic-content");
@@ -90587,7 +90986,7 @@ const EmailPreviewHelpDialog = () => {
90587
90986
  });
90588
90987
  };
90589
90988
  const shouldShowDontShowAgainButton = business?.uiDefaults?.emailHelpDialogShow !== false;
90590
- const topic = HELP_TOPICS.find((t3) => t3.id === selectedTopic) ?? HELP_TOPICS[0];
90989
+ const topic = HELP_TOPICS.find((t22) => t22.id === selectedTopic) ?? HELP_TOPICS[0];
90591
90990
  return /* @__PURE__ */ jsxs("div", { className: "p-6 overflow-y-auto", children: [
90592
90991
  /* @__PURE__ */ jsxs("div", { className: "mb-8 text-center", children: [
90593
90992
  /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold mb-2", children: "Email Editor Help Center" }),
@@ -90618,16 +91017,16 @@ const EmailPreviewHelpDialog = () => {
90618
91017
  },
90619
91018
  topic.id
90620
91019
  ) }) }),
90621
- /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 sm:flex-row sm:gap-6 max-w-xl mx-auto", children: HELP_TOPICS.map((t3) => /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
91020
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 sm:flex-row sm:gap-6 max-w-xl mx-auto", children: HELP_TOPICS.map((t22) => /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
90622
91021
  BigSelector,
90623
91022
  {
90624
- onClick: () => setSelectedTopic(t3.id),
90625
- title: t3.selectorTitle,
90626
- subtitle: t3.selectorSubtitle,
90627
- icon: t3.selectorIcon,
90628
- selected: selectedTopic === t3.id
91023
+ onClick: () => setSelectedTopic(t22.id),
91024
+ title: t22.selectorTitle,
91025
+ subtitle: t22.selectorSubtitle,
91026
+ icon: t22.selectorIcon,
91027
+ selected: selectedTopic === t22.id
90629
91028
  }
90630
- ) }, t3.id)) }),
91029
+ ) }, t22.id)) }),
90631
91030
  shouldShowDontShowAgainButton && /* @__PURE__ */ jsx("div", { className: "flex justify-center pt-4", children: /* @__PURE__ */ jsx(Button$1, { size: "sm", onClick: handleDontShowAgain, children: "Don't show me again" }) })
90632
91031
  ] });
90633
91032
  };
@@ -90790,6 +91189,17 @@ const StripoEditor = ({ containerRef, stripoDialogDimensions, showSaving }) => {
90790
91189
  }
90791
91190
  });
90792
91191
  });
91192
+ const mergeTagsElements = shadowRoot.querySelectorAll(
91193
+ "span.service-element.caption.ng-star-inserted"
91194
+ );
91195
+ mergeTagsElements.forEach((element) => {
91196
+ const htmlElement = element;
91197
+ if (htmlElement.textContent?.trim() === "Merge Tags") {
91198
+ htmlElement.textContent = t$2("engage:merge_field", {
91199
+ count: 2
91200
+ });
91201
+ }
91202
+ });
90793
91203
  };
90794
91204
  let shadowObserver = null;
90795
91205
  const setupShadowObserver = () => {
@@ -90937,115 +91347,174 @@ const StripoWrapper = ({
90937
91347
  mergeFieldsResponse,
90938
91348
  cancelClicked,
90939
91349
  setCancelClicked,
90940
- actionId
91350
+ actionId,
91351
+ onEditorReadyChange
90941
91352
  }) => {
91353
+ const [stripoEmailSaved, setStripoEmailSaved] = useState(false);
91354
+ const [stripoEmailCompiled, setStripoEmailCompiled] = useState(false);
91355
+ const [isEditorReady, setIsEditorReady] = useState(false);
90942
91356
  const stripoOpenedOnce = useRef(false);
90943
91357
  const containerRef = useRef(null);
90944
- const { data: template, isLoading: isTemplateLoading } = useStripoTemplateForActionId({
91358
+ const editorReadyCheckInterval = useRef(null);
91359
+ const {
91360
+ data: template,
91361
+ isLoading: isTemplateLoading,
91362
+ error: templateError
91363
+ } = useStripoTemplateForActionId({
90945
91364
  automationId: automation2.id,
90946
91365
  actionId
90947
91366
  });
90948
- const { data: editorData, isLoading: isEditorDataLoading } = useStripoEditorData();
90949
- const [stripoEmailSaved, setStripoEmailSaved] = useState(false);
90950
- const [stripoEmailCompiled, setStripoEmailCompiled] = useState(false);
91367
+ const {
91368
+ data: editorData,
91369
+ isLoading: isEditorDataLoading,
91370
+ error: editorDataError
91371
+ } = useStripoEditorData();
90951
91372
  const { updateCommunicationGroup: updateCommunicationGroup2 } = useUpdateCommunicationGroup();
91373
+ const checkEditorReady = useCallback(() => {
91374
+ const isReady = !!(window.StripoEditorApi?.actionsApi && typeof window.StripoEditorApi.actionsApi.save === "function" && typeof window.StripoEditorApi.actionsApi.compileEmail === "function");
91375
+ if (isReady && !isEditorReady) {
91376
+ setIsEditorReady(true);
91377
+ onEditorReadyChange?.(true);
91378
+ if (editorReadyCheckInterval.current) {
91379
+ clearInterval(editorReadyCheckInterval.current);
91380
+ editorReadyCheckInterval.current = null;
91381
+ }
91382
+ }
91383
+ return isReady;
91384
+ }, [isEditorReady, onEditorReadyChange]);
90952
91385
  const resetAndCloseEditor = useCallback(() => {
90953
91386
  setSaveClicked(false);
90954
91387
  setCancelClicked(false);
90955
91388
  setStripoEmailSaved(false);
90956
91389
  setStripoEmailCompiled(false);
91390
+ setIsEditorReady(false);
91391
+ onEditorReadyChange?.(false);
90957
91392
  setShowStripoEditor(false);
91393
+ if (editorReadyCheckInterval.current) {
91394
+ clearInterval(editorReadyCheckInterval.current);
91395
+ editorReadyCheckInterval.current = null;
91396
+ }
90958
91397
  }, [
90959
91398
  setSaveClicked,
90960
91399
  setCancelClicked,
90961
91400
  setStripoEmailSaved,
90962
91401
  setStripoEmailCompiled,
91402
+ setIsEditorReady,
91403
+ onEditorReadyChange,
90963
91404
  setShowStripoEditor
90964
91405
  ]);
90965
- useEffect(() => {
90966
- if (saveClicked && window.UIEditor && template?.communicationGroupId && window.StripoEditorApi?.actionsApi) {
90967
- const compileEmailCallback = function(error2, html) {
90968
- if (error2) {
90969
- console.error("Failed to compile email:", error2);
90970
- return;
90971
- }
90972
- updateCommunicationGroup2({
90973
- groupId: template.communicationGroupId,
90974
- params: {
90975
- emailHtmlBody: html
91406
+ const executeSave = useCallback(async () => {
91407
+ if (!isEditorReady) {
91408
+ console.warn("Save operation blocked - editor not ready yet");
91409
+ return;
91410
+ }
91411
+ if (!window.StripoEditorApi?.actionsApi || !template?.communicationGroupId) {
91412
+ console.warn("Save operation blocked - preconditions not met");
91413
+ return;
91414
+ }
91415
+ try {
91416
+ const saveOperation = () => new Promise((resolve, reject) => {
91417
+ const compileEmailCallback = async function(error2, html) {
91418
+ if (error2) {
91419
+ reject(new Error(`Compile failed: ${error2}`));
91420
+ return;
91421
+ }
91422
+ try {
91423
+ await updateCommunicationGroup2({
91424
+ groupId: template.communicationGroupId,
91425
+ params: { emailHtmlBody: html }
91426
+ });
91427
+ } catch (err) {
91428
+ reject(err);
90976
91429
  }
91430
+ setStripoEmailCompiled(true);
91431
+ resolve();
91432
+ };
91433
+ const saveCallback = function(error2) {
91434
+ if (error2) {
91435
+ reject(new Error(`Save failed: ${error2}`));
91436
+ return;
91437
+ }
91438
+ setStripoEmailSaved(true);
91439
+ };
91440
+ window.StripoEditorApi.actionsApi.compileEmail({
91441
+ callback: compileEmailCallback,
91442
+ minimize: true,
91443
+ mergeTags: [],
91444
+ forceAmp: false,
91445
+ resetDataSavedFlag: false,
91446
+ disableLineHeightsReplace: true
90977
91447
  });
90978
- setStripoEmailCompiled(true);
90979
- };
90980
- window.StripoEditorApi.actionsApi.compileEmail({
90981
- callback: compileEmailCallback,
90982
- minimize: true,
90983
- mergeTags: [],
90984
- forceAmp: false,
90985
- resetDataSavedFlag: false,
90986
- disableLineHeightsReplace: true
91448
+ window.StripoEditorApi.actionsApi.save(saveCallback);
90987
91449
  });
90988
- const saveCallback = function(error2) {
90989
- if (error2) {
90990
- console.error("Failed to save email:", error2);
90991
- return;
90992
- }
90993
- setStripoEmailSaved(true);
90994
- };
90995
- window.StripoEditorApi.actionsApi.save(saveCallback);
91450
+ await saveOperation();
91451
+ } catch (error2) {
91452
+ console.error("Save operation failed:", error2);
90996
91453
  }
90997
- }, [
90998
- saveClicked,
90999
- containerRef,
91000
- setSaveClicked,
91001
- setShowStripoEditor,
91002
- template,
91003
- updateCommunicationGroup2
91004
- ]);
91454
+ }, [template, updateCommunicationGroup2, isEditorReady]);
91005
91455
  useEffect(() => {
91006
- const closeEditor = async () => {
91007
- if (stripoEmailSaved && stripoEmailCompiled || showStripoEditor === false) {
91008
- if (window.UIEditor && window.UIEditor.removeEditor && typeof window.UIEditor.removeEditor === "function") {
91009
- window.UIEditor.removeEditor();
91010
- }
91011
- resetAndCloseEditor();
91012
- }
91013
- };
91014
- closeEditor();
91456
+ if (saveClicked && isEditorReady) {
91457
+ executeSave();
91458
+ } else if (saveClicked && !isEditorReady) {
91459
+ console.warn("Save clicked but editor not ready - ignoring save request");
91460
+ setSaveClicked(false);
91461
+ }
91462
+ }, [saveClicked, executeSave, isEditorReady, setSaveClicked]);
91463
+ useEffect(() => {
91464
+ if (stripoEmailSaved && stripoEmailCompiled || showStripoEditor === false) {
91465
+ resetAndCloseEditor();
91466
+ }
91015
91467
  }, [
91016
91468
  stripoEmailSaved,
91017
91469
  stripoEmailCompiled,
91018
91470
  showStripoEditor,
91019
- setShowStripoEditor,
91020
- setSaveClicked,
91021
91471
  resetAndCloseEditor
91022
91472
  ]);
91023
91473
  useEffect(() => {
91024
91474
  if (cancelClicked) {
91025
- if (window.UIEditor && window.UIEditor.removeEditor && typeof window.UIEditor.removeEditor === "function") {
91026
- window.UIEditor.removeEditor();
91027
- }
91028
91475
  resetAndCloseEditor();
91029
91476
  }
91030
91477
  }, [cancelClicked, resetAndCloseEditor]);
91031
91478
  useEffect(() => {
91032
- if (editorData?.businessId && containerRef.current && template) {
91033
- if (!stripoOpenedOnce.current) {
91034
- initStripo({
91035
- emailId: template.communicationGroupId,
91036
- html: template.html || "<div></div>",
91037
- css: template.css || "",
91038
- container: containerRef.current,
91039
- businessId: editorData.businessId,
91040
- mergeFields: mergeFieldsResponse.mergeFields || [],
91041
- forceRecreate: template.forceRecreate
91042
- }).catch((error2) => {
91043
- console.error("Failed to initialize Stripo:", error2);
91044
- });
91045
- stripoOpenedOnce.current = true;
91046
- }
91479
+ if (editorData?.businessId && containerRef.current && template && !stripoOpenedOnce.current) {
91480
+ const initializeEditor = async () => {
91481
+ try {
91482
+ await initStripo({
91483
+ emailId: template.communicationGroupId,
91484
+ html: template.html || "<div></div>",
91485
+ css: template.css || "",
91486
+ container: containerRef.current,
91487
+ businessId: editorData.businessId,
91488
+ mergeFields: mergeFieldsResponse.mergeFields || [],
91489
+ forceRecreate: template.forceRecreate
91490
+ });
91491
+ stripoOpenedOnce.current = true;
91492
+ console.log(
91493
+ "Stripo initStripo completed, starting readiness check..."
91494
+ );
91495
+ editorReadyCheckInterval.current = setInterval(() => {
91496
+ checkEditorReady();
91497
+ }, 500);
91498
+ } catch (error2) {
91499
+ console.error("Failed to initialize Stripo editor:", error2);
91500
+ }
91501
+ };
91502
+ initializeEditor();
91047
91503
  }
91048
- }, [editorData, mergeFieldsResponse, template]);
91504
+ }, [editorData, mergeFieldsResponse, template, checkEditorReady]);
91505
+ useEffect(() => {
91506
+ return () => {
91507
+ if (editorReadyCheckInterval.current) {
91508
+ clearInterval(editorReadyCheckInterval.current);
91509
+ }
91510
+ };
91511
+ }, []);
91512
+ if (templateError || editorDataError) {
91513
+ return /* @__PURE__ */ jsxs("div", { className: "w-full h-full flex flex-col items-center justify-center", children: [
91514
+ /* @__PURE__ */ jsx("div", { className: "text-red-500 mb-4", children: "Failed to load editor data" }),
91515
+ /* @__PURE__ */ jsx(Button$1, { onClick: () => window.location.reload(), variant: "outline", children: "Retry" })
91516
+ ] });
91517
+ }
91049
91518
  if (isTemplateLoading || isEditorDataLoading || !editorData || !template) {
91050
91519
  return /* @__PURE__ */ jsx("div", { className: "w-full h-full flex flex-col items-center justify-center", children: /* @__PURE__ */ jsx(BasicLoader, { text: ["Loading editor..."] }) });
91051
91520
  }
@@ -91054,7 +91523,7 @@ const StripoWrapper = ({
91054
91523
  {
91055
91524
  containerRef,
91056
91525
  stripoDialogDimensions,
91057
- showSaving: saveClicked
91526
+ showSaving: saveClicked && isEditorReady
91058
91527
  }
91059
91528
  ) });
91060
91529
  };
@@ -91100,6 +91569,7 @@ const EmailPreview = ({
91100
91569
  const [showStripoEditor, setShowStripoEditor] = useState(false);
91101
91570
  const [saveClicked, setSaveClicked] = useState(false);
91102
91571
  const [cancelClicked, setCancelClicked] = useState(false);
91572
+ const [isEditorReady, setIsEditorReady] = useState(false);
91103
91573
  const [fromName, setFromName] = useState("");
91104
91574
  const [replyToEmail, setReplyToEmail] = useState("");
91105
91575
  const [showHelpDialog, setShowHelpDialog] = useState(null);
@@ -91319,6 +91789,7 @@ const EmailPreview = ({
91319
91789
  {
91320
91790
  onClick: () => setSaveClicked(true),
91321
91791
  className: "text-sm px-3 py-1 rounded",
91792
+ disabled: !isEditorReady,
91322
91793
  children: "Save"
91323
91794
  }
91324
91795
  )
@@ -91336,7 +91807,8 @@ const EmailPreview = ({
91336
91807
  setSaveClicked,
91337
91808
  stripoDialogDimensions,
91338
91809
  mergeFieldsResponse,
91339
- actionId: selectedActionId
91810
+ actionId: selectedActionId,
91811
+ onEditorReadyChange: setIsEditorReady
91340
91812
  }
91341
91813
  )
91342
91814
  ] })
@@ -94246,6 +94718,7 @@ var ENGAGE_STEPS = /* @__PURE__ */ ((ENGAGE_STEPS2) => {
94246
94718
  ENGAGE_STEPS2["BUSINESS"] = "business";
94247
94719
  ENGAGE_STEPS2["EMAIL"] = "email";
94248
94720
  ENGAGE_STEPS2["SMS"] = "sms";
94721
+ ENGAGE_STEPS2["MERGE_FIELDS"] = "merge-fields";
94249
94722
  ENGAGE_STEPS2["SPLASH"] = "splash";
94250
94723
  ENGAGE_STEPS2["BUSINESS_SKIP_INTERSTITIAL"] = "business-skip-interstitial";
94251
94724
  ENGAGE_STEPS2["SMS_INTERSTITIAL"] = "sms-interstitial";
@@ -94263,6 +94736,7 @@ const ENGAGE_STRINGS = {
94263
94736
  BUSINESS_INFORMATION_TITLE: "Business Information",
94264
94737
  EMAIL_CHANNEL_TITLE: "Email Channel",
94265
94738
  SMS_CHANNEL_TITLE: "SMS Channel",
94739
+ MERGE_FIELDS_TITLE: "Merge Fields",
94266
94740
  // Step titles - Onboarding mode
94267
94741
  BRAND_ONBOARDING_TITLE: "Customize Your Brand",
94268
94742
  BUSINESS_ONBOARDING_TITLE: "Business Settings",
@@ -94301,7 +94775,11 @@ const SHARED_STEP_CONFIGS = {
94301
94775
  [
94302
94776
  "sms"
94303
94777
  /* SMS */
94304
- ]: { label: ENGAGE_STRINGS.SMS_CHANNEL_TITLE }
94778
+ ]: { label: ENGAGE_STRINGS.SMS_CHANNEL_TITLE },
94779
+ [
94780
+ "merge-fields"
94781
+ /* MERGE_FIELDS */
94782
+ ]: { label: ENGAGE_STRINGS.MERGE_FIELDS_TITLE }
94305
94783
  };
94306
94784
  const SETTINGS_STEP_CONFIGS = [
94307
94785
  {
@@ -94331,6 +94809,13 @@ const SETTINGS_STEP_CONFIGS = [
94331
94809
  "sms"
94332
94810
  /* SMS */
94333
94811
  ].label
94812
+ },
94813
+ {
94814
+ key: "merge-fields",
94815
+ label: SHARED_STEP_CONFIGS[
94816
+ "merge-fields"
94817
+ /* MERGE_FIELDS */
94818
+ ].label
94334
94819
  }
94335
94820
  ];
94336
94821
  const ONBOARDING_STEP_CONFIGS = [
@@ -94362,6 +94847,13 @@ const ONBOARDING_STEP_CONFIGS = [
94362
94847
  /* SMS */
94363
94848
  ].label
94364
94849
  },
94850
+ {
94851
+ key: "merge-fields",
94852
+ label: SHARED_STEP_CONFIGS[
94853
+ "merge-fields"
94854
+ /* MERGE_FIELDS */
94855
+ ].label
94856
+ },
94365
94857
  {
94366
94858
  key: "completion",
94367
94859
  label: ENGAGE_STRINGS.COMPLETION_TITLE
@@ -94901,6 +95393,20 @@ function extractBase64Data(dataUrl) {
94901
95393
  }
94902
95394
  return parts[1];
94903
95395
  }
95396
+ const capitalize = (str) => {
95397
+ if (!str) return str;
95398
+ return str.charAt(0).toUpperCase() + str.slice(1);
95399
+ };
95400
+ const getMergeFieldsFromUrl = (url, getMergeFields2) => {
95401
+ if (!url || !getMergeFields2) return [];
95402
+ const regex = /\{\{([^}]+)\}\}/g;
95403
+ const matches2 = [...url.matchAll(regex)];
95404
+ return matches2.map((match2) => {
95405
+ const mergeFieldValue = match2[1];
95406
+ const mergeField = getMergeFields2.mergeFields?.flatMap((f2) => f2.entries).find((entry) => entry.value === `{{${mergeFieldValue}}}`);
95407
+ return mergeField ? mergeField.label : mergeFieldValue;
95408
+ }).filter(Boolean);
95409
+ };
94904
95410
  const convertToHtml = (text2, mergeFields, variant) => {
94905
95411
  if (!text2) return "";
94906
95412
  const regex = /\{\{.*?\}\}/g;
@@ -94964,13 +95470,28 @@ const SMSPreview = ({ body, imageUrls }) => {
94964
95470
  const { getMergeFields: getMergeFields2, isGetting } = useGetMergeFields(
94965
95471
  communicationGroup?.id ?? void 0
94966
95472
  );
95473
+ const hasMergeFieldsInUrl = React__default.useCallback((url) => {
95474
+ return /\{\{[^}]+\}\}/.test(url);
95475
+ }, []);
94967
95476
  if (isGetting || getMergeFields2 === void 0) {
94968
95477
  return null;
94969
95478
  }
94970
95479
  return /* @__PURE__ */ jsx("div", { className: "sms-preview-outer @container", children: /* @__PURE__ */ jsxs("div", { className: "sms-phone-mockup", children: [
94971
95480
  /* @__PURE__ */ jsx("div", { className: "sms-phone-notch" }),
94972
95481
  /* @__PURE__ */ jsxs("div", { className: "sms-phone-inner", children: [
94973
- imageUrls && imageUrls.length > 0 && imageUrls.map((url, idx) => /* @__PURE__ */ jsx("div", { className: "sms-message-row", children: /* @__PURE__ */ jsx("div", { className: "sms-message-bubble", children: /* @__PURE__ */ jsx(
95482
+ imageUrls && imageUrls.length > 0 && imageUrls.map((url, idx) => /* @__PURE__ */ jsx("div", { className: "sms-message-row", children: /* @__PURE__ */ jsx("div", { className: "sms-message-bubble", children: hasMergeFieldsInUrl(url) ? /* @__PURE__ */ jsx("div", { className: "sms-message-image-placeholder", children: /* @__PURE__ */ jsxs("div", { className: "text-center text-white text-[10px] leading-tight", children: [
95483
+ /* @__PURE__ */ jsx("div", { className: "font-medium mb-1", children: "Image will be populated at send time based on the following merge fields:" }),
95484
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 justify-center", children: getMergeFieldsFromUrl(url, getMergeFields2).map(
95485
+ (fieldName, fieldIndex) => /* @__PURE__ */ jsx(
95486
+ "span",
95487
+ {
95488
+ className: "inline-flex items-center px-1 py-0.5 rounded text-[10px] bg-green-500/30 text-white font-medium border border-green-400/40",
95489
+ children: fieldName
95490
+ },
95491
+ fieldIndex
95492
+ )
95493
+ ) })
95494
+ ] }) }) : /* @__PURE__ */ jsx(
94974
95495
  "img",
94975
95496
  {
94976
95497
  src: url,
@@ -97330,9 +97851,16 @@ const SMSEditor = ({
97330
97851
  initialImageUrls,
97331
97852
  imageBase64,
97332
97853
  isUpdating,
97333
- hasUnsavedChanges
97854
+ hasUnsavedChanges,
97855
+ communicationGroupId
97334
97856
  }) => {
97335
97857
  const [characterCount, setCharacterCount] = useState(0);
97858
+ const [isAddImagePopoverOpen, setIsAddImagePopoverOpen] = useState(false);
97859
+ const [isUrlDialogOpen, setIsUrlDialogOpen] = useState(false);
97860
+ const [imageUrl, setImageUrl] = useState("");
97861
+ const [isSavingUrl, setIsSavingUrl] = useState(false);
97862
+ const { updateCommunicationGroup: updateCommunicationGroup2 } = useUpdateCommunicationGroup();
97863
+ const { toast: toast2 } = useToast();
97336
97864
  const allMergeFields = useMemo(
97337
97865
  () => mergeFieldsResponse?.mergeFields?.flatMap((f2) => f2.entries) ?? [],
97338
97866
  [mergeFieldsResponse]
@@ -97407,6 +97935,43 @@ const SMSEditor = ({
97407
97935
  characterCount,
97408
97936
  maxLength
97409
97937
  );
97938
+ const handleAddImageFromFile = useCallback(() => {
97939
+ setIsAddImagePopoverOpen(false);
97940
+ onAddImage?.();
97941
+ }, [onAddImage]);
97942
+ const handleAddImageFromUrl = useCallback(() => {
97943
+ setIsAddImagePopoverOpen(false);
97944
+ setIsUrlDialogOpen(true);
97945
+ }, []);
97946
+ const handleUrlSubmit = useCallback(async () => {
97947
+ if (!imageUrl.trim() || !communicationGroupId) {
97948
+ return;
97949
+ }
97950
+ setIsSavingUrl(true);
97951
+ try {
97952
+ await updateCommunicationGroup2({
97953
+ groupId: communicationGroupId,
97954
+ params: {
97955
+ textMessageMediaUrls: [imageUrl.trim()]
97956
+ }
97957
+ });
97958
+ setImageUrl("");
97959
+ setIsUrlDialogOpen(false);
97960
+ toast2({
97961
+ title: "Image URL added",
97962
+ description: "The image URL has been added to your SMS message."
97963
+ });
97964
+ } catch (error2) {
97965
+ console.error("Failed to save image URL:", error2);
97966
+ toast2({
97967
+ title: "Failed to add image URL",
97968
+ description: "Please try again.",
97969
+ variant: "destructive"
97970
+ });
97971
+ } finally {
97972
+ setIsSavingUrl(false);
97973
+ }
97974
+ }, [imageUrl, communicationGroupId, updateCommunicationGroup2, toast2]);
97410
97975
  if (!editor) {
97411
97976
  return /* @__PURE__ */ jsx("div", { children: "Loading editor..." });
97412
97977
  }
@@ -97422,15 +97987,98 @@ const SMSEditor = ({
97422
97987
  disabled: isRemovingImage || isUpdatingCommunicationGroup,
97423
97988
  children: "Remove Image"
97424
97989
  }
97425
- ) : /* @__PURE__ */ jsx(
97426
- Button$1,
97990
+ ) : /* @__PURE__ */ jsxs(
97991
+ Popover,
97427
97992
  {
97428
- variant: "outline",
97429
- onClick: onAddImage,
97430
- disabled: (initialImageUrls?.length || 0) >= 1 || isRemovingImage || imageBase64 !== null || isUpdatingCommunicationGroup,
97431
- children: "Add Image"
97993
+ modal: true,
97994
+ open: isAddImagePopoverOpen,
97995
+ onOpenChange: setIsAddImagePopoverOpen,
97996
+ children: [
97997
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
97998
+ Button$1,
97999
+ {
98000
+ variant: "outline",
98001
+ disabled: (initialImageUrls?.length || 0) >= 1 || isRemovingImage || imageBase64 !== null || isUpdatingCommunicationGroup,
98002
+ className: "flex items-center gap-2",
98003
+ children: [
98004
+ "Add Image",
98005
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3" })
98006
+ ]
98007
+ }
98008
+ ) }),
98009
+ /* @__PURE__ */ jsx(PopoverContent, { className: "w-56 p-3 z-[100]", align: "start", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
98010
+ /* @__PURE__ */ jsxs(
98011
+ Button$1,
98012
+ {
98013
+ variant: "ghost",
98014
+ size: "sm",
98015
+ onClick: handleAddImageFromFile,
98016
+ className: "w-full justify-start gap-2 h-9",
98017
+ children: [
98018
+ /* @__PURE__ */ jsx(Upload, { className: "h-4 w-4" }),
98019
+ "Upload from file"
98020
+ ]
98021
+ }
98022
+ ),
98023
+ /* @__PURE__ */ jsxs(
98024
+ Button$1,
98025
+ {
98026
+ variant: "ghost",
98027
+ size: "sm",
98028
+ onClick: handleAddImageFromUrl,
98029
+ className: "w-full justify-start gap-2 h-9",
98030
+ children: [
98031
+ /* @__PURE__ */ jsx(Link$1, { className: "h-4 w-4" }),
98032
+ "Add from URL"
98033
+ ]
98034
+ }
98035
+ ),
98036
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground pt-1 border-t", children: "Maximum file size: 5MB. We will try to compress the image if possible." })
98037
+ ] }) })
98038
+ ]
97432
98039
  }
97433
98040
  ),
98041
+ /* @__PURE__ */ jsx(Dialog, { open: isUrlDialogOpen, onOpenChange: setIsUrlDialogOpen, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
98042
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
98043
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Add Image from URL" }),
98044
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
98045
+ "Enter the URL of the image you want to add to your SMS message. You can use merge fields to make the URL dynamic (e.g., https://example.com/images/{{customer_name}}.jpg).",
98046
+ /* @__PURE__ */ jsx("br", {}),
98047
+ /* @__PURE__ */ jsx("br", {}),
98048
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Maximum file size: 5MB. We will try to compress the image if possible." })
98049
+ ] })
98050
+ ] }),
98051
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
98052
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
98053
+ /* @__PURE__ */ jsx(Label$1, { htmlFor: "image-url", children: "Image URL" }),
98054
+ /* @__PURE__ */ jsx(
98055
+ RichTextInputWithMergeFields,
98056
+ {
98057
+ value: imageUrl,
98058
+ onChange: setImageUrl,
98059
+ mergeFieldsResponse,
98060
+ placeholder: "https://example.com/image.jpg",
98061
+ className: "bg-background hover:bg-accent/50 focus:bg-accent transition-colors placeholder:text-muted-foreground/50",
98062
+ ariaLabel: "Insert merge field into image URL"
98063
+ }
98064
+ )
98065
+ ] }),
98066
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
98067
+ /* @__PURE__ */ jsx(
98068
+ Button$1,
98069
+ {
98070
+ variant: "outline",
98071
+ onClick: () => {
98072
+ setImageUrl("");
98073
+ setIsUrlDialogOpen(false);
98074
+ },
98075
+ children: "Cancel"
98076
+ }
98077
+ ),
98078
+ /* @__PURE__ */ jsx(Button$1, { onClick: handleUrlSubmit, disabled: isSavingUrl, children: isSavingUrl ? "Adding..." : "Add Image" })
98079
+ ] })
98080
+ ] })
98081
+ ] }) }),
97434
98082
  /* @__PURE__ */ jsx(
97435
98083
  MultiSelectDialog,
97436
98084
  {
@@ -97439,7 +98087,7 @@ const SMSEditor = ({
97439
98087
  setSelectedValues: () => {
97440
98088
  },
97441
98089
  onValueChange: handleMergeFieldInsert,
97442
- placeholder: "Add merge field",
98090
+ placeholder: `Add ${t$2("engage:merge_field")}`,
97443
98091
  title: "Merge Fields",
97444
98092
  searchPlaceholder: "Search merge fields...",
97445
98093
  emptyMessage: "No merge fields found",
@@ -97702,6 +98350,9 @@ const SMSEditorContent = ({
97702
98350
  const { toast: toast2 } = useToast();
97703
98351
  const { updateCommunicationGroup: updateCommunicationGroup2, isUpdating: isUpdatingCommunicationGroup } = useUpdateCommunicationGroup();
97704
98352
  const { getMergeFields: getMergeFields2 } = useGetMergeFields(communicationGroupId);
98353
+ const hasMergeFieldsInUrl = useCallback((url) => {
98354
+ return /\{\{[^}]+\}\}/.test(url);
98355
+ }, []);
97705
98356
  const [editingMessage, setEditingMessage] = useState(initialMessage);
97706
98357
  const [imagePreview, setImagePreview] = useState(
97707
98358
  initialImageUrls.length > 0 ? initialImageUrls[0] : null
@@ -97718,6 +98369,7 @@ const SMSEditorContent = ({
97718
98369
  const lastSavedStateRef = useRef({
97719
98370
  message: initialMessage
97720
98371
  });
98372
+ const { communicationGroup } = useGetCommunicationGroup(communicationGroupId);
97721
98373
  const debouncedMessage = useDebounce(editingMessage, 1e3);
97722
98374
  const autoSave = useCallback(async () => {
97723
98375
  if (isInitialMountRef.current) {
@@ -97835,6 +98487,16 @@ const SMSEditorContent = ({
97835
98487
  if (fileInputRef.current) {
97836
98488
  fileInputRef.current.value = "";
97837
98489
  }
98490
+ const MAX_FILE_SIZE = 20 * 1024 * 1024;
98491
+ if (file.size > MAX_FILE_SIZE) {
98492
+ console.log("File too large", file.size, MAX_FILE_SIZE);
98493
+ toast2({
98494
+ title: "File too large",
98495
+ description: `Image must be 5MB or smaller. Your file is ${(file.size / (1024 * 1024)).toFixed(1)}MB.`,
98496
+ variant: "destructive"
98497
+ });
98498
+ return;
98499
+ }
97838
98500
  setIsUploadingImage(true);
97839
98501
  try {
97840
98502
  const reader = new FileReader();
@@ -97842,19 +98504,40 @@ const SMSEditorContent = ({
97842
98504
  try {
97843
98505
  const pngDataUrl = await convertToPng(reader.result);
97844
98506
  const base64String = extractBase64Data(pngDataUrl);
97845
- await updateCommunicationGroup2({
97846
- groupId: communicationGroupId,
97847
- params: {
97848
- textMessageMedia: [base64String]
98507
+ updateCommunicationGroup2(
98508
+ {
98509
+ groupId: communicationGroupId,
98510
+ params: {
98511
+ textMessageMedia: [base64String]
98512
+ }
98513
+ },
98514
+ {
98515
+ onSuccess: () => {
98516
+ setImageBase64(base64String);
98517
+ setImagePreview(pngDataUrl);
98518
+ setIsRemovingImage(false);
98519
+ toast2({
98520
+ title: "Image uploaded",
98521
+ description: "The image has been added to your SMS"
98522
+ });
98523
+ },
98524
+ onError: (data) => {
98525
+ if (data.message.includes("Image is still too large")) {
98526
+ toast2({
98527
+ title: "Image too large",
98528
+ description: "The image is too large. Please try again with a smaller image.",
98529
+ variant: "destructive"
98530
+ });
98531
+ } else {
98532
+ toast2({
98533
+ title: "Error uploading image",
98534
+ description: "Failed to process or upload the image",
98535
+ variant: "destructive"
98536
+ });
98537
+ }
98538
+ }
97849
98539
  }
97850
- });
97851
- setImageBase64(base64String);
97852
- setImagePreview(pngDataUrl);
97853
- setIsRemovingImage(false);
97854
- toast2({
97855
- title: "Image uploaded",
97856
- description: "The image has been added to your SMS"
97857
- });
98540
+ );
97858
98541
  } catch (error2) {
97859
98542
  console.error("Failed to convert or upload image:", error2);
97860
98543
  toast2({
@@ -97886,6 +98569,22 @@ const SMSEditorContent = ({
97886
98569
  return /* @__PURE__ */ jsx("div", { className: "flex justify-center items-center p-8", children: /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading editor..." }) });
97887
98570
  }
97888
98571
  return /* @__PURE__ */ jsxs("div", { className: cn$2("w-full @container", className), children: [
98572
+ !communicationGroup?.smsChannelSenderId && /* @__PURE__ */ jsx("div", { className: "mb-6 p-4 bg-amber-50 border border-amber-200 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
98573
+ /* @__PURE__ */ jsx(
98574
+ InfoTooltip,
98575
+ {
98576
+ title: "SMS Sender Required",
98577
+ description: "You need to select an SMS sender before you can send SMS messages. Click 'Edit Sender & Company' above to configure your SMS sender.",
98578
+ alertText: "Action Required",
98579
+ size: "medium",
98580
+ iconColor: "text-amber-500"
98581
+ }
98582
+ ),
98583
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
98584
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-amber-800 mb-1", children: "SMS Sender Not Selected" }),
98585
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-amber-700", children: 'Please select an SMS sender to enable SMS messaging. Click the "Edit Sender & Company" button to configure your sender.' })
98586
+ ] })
98587
+ ] }) }),
97889
98588
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-6", children: /* @__PURE__ */ jsx(
97890
98589
  MemoizedSMSSenderAndCompanyEditor$1,
97891
98590
  {
@@ -97896,7 +98595,20 @@ const SMSEditorContent = ({
97896
98595
  ) }),
97897
98596
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col lg:flex-row gap-6 h-full", children: [
97898
98597
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
97899
- imagePreview && /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsx("div", { className: "relative group w-fit", children: /* @__PURE__ */ jsx(
98598
+ imagePreview && /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsx("div", { className: "relative group w-fit", children: hasMergeFieldsInUrl(imagePreview) ? /* @__PURE__ */ jsx("div", { className: "max-h-48 max-w-xs rounded-xl shadow-lg border border-gray-200 bg-gray-50 p-4 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
98599
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-700 mb-2", children: "Image will be populated at send time based on the following merge fields:" }),
98600
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 justify-center", children: getMergeFieldsFromUrl(
98601
+ imagePreview,
98602
+ getMergeFields2
98603
+ ).map((fieldName, index2) => /* @__PURE__ */ jsx(
98604
+ "span",
98605
+ {
98606
+ className: "inline-flex items-center px-2 py-1 rounded text-xs bg-blue-100 text-blue-800 font-medium border border-blue-200",
98607
+ children: fieldName
98608
+ },
98609
+ index2
98610
+ )) })
98611
+ ] }) }) : /* @__PURE__ */ jsx(
97900
98612
  "img",
97901
98613
  {
97902
98614
  src: imagePreview,
@@ -97932,7 +98644,8 @@ const SMSEditorContent = ({
97932
98644
  initialImageUrls,
97933
98645
  imageBase64,
97934
98646
  isUpdating: isUpdatingCommunicationGroup || isUploadingImage,
97935
- hasUnsavedChanges
98647
+ hasUnsavedChanges,
98648
+ communicationGroupId
97936
98649
  }
97937
98650
  )
97938
98651
  ] }),
@@ -99826,6 +100539,7 @@ const AutomationEditorEmailPreview = ({
99826
100539
  const [showHelpDialog, setShowHelpDialog] = useState(null);
99827
100540
  const [saveClicked, setSaveClicked] = useState(false);
99828
100541
  const [cancelClicked, setCancelClicked] = useState(false);
100542
+ const [isEditorReady, setIsEditorReady] = useState(false);
99829
100543
  const [stripoDialogDimensions, setStripoDialogDimensions] = useState({
99830
100544
  width: 0,
99831
100545
  height: 0
@@ -99968,6 +100682,7 @@ const AutomationEditorEmailPreview = ({
99968
100682
  Button$1,
99969
100683
  {
99970
100684
  onClick: () => setSaveClicked(true),
100685
+ disabled: !isEditorReady,
99971
100686
  className: "text-sm px-3 py-1 rounded",
99972
100687
  children: "Save"
99973
100688
  }
@@ -99986,7 +100701,8 @@ const AutomationEditorEmailPreview = ({
99986
100701
  setSaveClicked,
99987
100702
  stripoDialogDimensions,
99988
100703
  mergeFieldsResponse: getMergeFields2,
99989
- actionId: selectedActionId
100704
+ actionId: selectedActionId,
100705
+ onEditorReadyChange: setIsEditorReady
99990
100706
  }
99991
100707
  )
99992
100708
  ] })
@@ -100087,12 +100803,6 @@ const SMSDetailField = ({ label: label2, value, className }) => /* @__PURE__ */
100087
100803
  ] }),
100088
100804
  /* @__PURE__ */ jsx("span", { className: cn$2("truncate", className), children: value })
100089
100805
  ] });
100090
- const MemoizedTriggerButton = React__default.memo(
100091
- React__default.forwardRef((props2, ref) => /* @__PURE__ */ jsxs(Button$1, { size: "sm", variant: "outline", ref, ...props2, children: [
100092
- /* @__PURE__ */ jsx(IconDefinitions.EditIcon, { className: "w-3 h-3" }),
100093
- "Edit"
100094
- ] }))
100095
- );
100096
100806
  const MemoizedSMSSenderAndCompanyEditor = React__default.memo(
100097
100807
  ({
100098
100808
  trigger,
@@ -100111,7 +100821,7 @@ const MemoizedSMSSenderAndCompanyEditor = React__default.memo(
100111
100821
  );
100112
100822
  }
100113
100823
  );
100114
- const SMSDetailsPreview = ({ disableEditContent }) => {
100824
+ const SMSDetailsPreview = ({ disableEditContent, mergeFieldsResponse }) => {
100115
100825
  const automation2 = useAutomation();
100116
100826
  const communicationGroup = useCommunicationGroup();
100117
100827
  const { channelSenders } = useChannelSender();
@@ -100135,6 +100845,22 @@ const SMSDetailsPreview = ({ disableEditContent }) => {
100135
100845
  return null;
100136
100846
  }
100137
100847
  const messagePreview = messageBody ? messageBody.length > 50 ? messageBody.substring(0, 50) + "..." : messageBody : null;
100848
+ const MemoizedTriggerButton = React__default.memo(
100849
+ React__default.forwardRef((props2, ref) => /* @__PURE__ */ jsxs(
100850
+ Button$1,
100851
+ {
100852
+ size: "sm",
100853
+ variant: "outline",
100854
+ ref,
100855
+ ...props2,
100856
+ disabled: automation2?.status !== AutomationStatus.DRAFT,
100857
+ children: [
100858
+ /* @__PURE__ */ jsx(IconDefinitions.EditIcon, { className: "w-3 h-3" }),
100859
+ "Edit"
100860
+ ]
100861
+ }
100862
+ ))
100863
+ );
100138
100864
  return /* @__PURE__ */ jsx("div", { className: "w-full bg-background rounded-lg p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
100139
100865
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 overflow-hidden flex-1", children: [
100140
100866
  fromPhone && /* @__PURE__ */ jsx(
@@ -100157,7 +100883,15 @@ const SMSDetailsPreview = ({ disableEditContent }) => {
100157
100883
  SMSDetailField,
100158
100884
  {
100159
100885
  label: "Message",
100160
- value: messagePreview,
100886
+ value: /* @__PURE__ */ jsx(
100887
+ MergeFieldRenderer,
100888
+ {
100889
+ content: messagePreview,
100890
+ mergeFieldsResponse,
100891
+ variant: "minimal",
100892
+ className: "text-muted-foreground/50 italic"
100893
+ }
100894
+ ),
100161
100895
  className: "text-muted-foreground/50 italic"
100162
100896
  }
100163
100897
  )
@@ -100200,6 +100934,9 @@ const AutomationEditorSMSPreview = ({
100200
100934
  [communicationGroup?.textMessageMediaUrls]
100201
100935
  );
100202
100936
  const smsBody = communicationGroup?.smsMessageBody || "";
100937
+ const { getMergeFields: getMergeFields2 } = useGetMergeFields(
100938
+ communicationGroup?.id ?? void 0
100939
+ );
100203
100940
  const hasAnySMSsenders = smsChannelSenders.length > 0;
100204
100941
  useEffect(() => {
100205
100942
  if (hasProcessedInitialSetupRef.current) {
@@ -100350,7 +101087,8 @@ const AutomationEditorSMSPreview = ({
100350
101087
  /* @__PURE__ */ jsx("div", { className: "w-full flex-shrink-0 max-w-2xl mx-auto rounded-md", children: /* @__PURE__ */ jsx(
100351
101088
  SMSDetailsPreview,
100352
101089
  {
100353
- disableEditContent
101090
+ disableEditContent,
101091
+ mergeFieldsResponse: getMergeFields2
100354
101092
  }
100355
101093
  ) }),
100356
101094
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-background flex-1 min-h-0 flex flex-col items-center max-w-2xl mx-auto w-full", children: [
@@ -104364,6 +105102,15 @@ const AutomationSelectTimeMain = ({ title: title2 = "Edit Schedule Time" }) => {
104364
105102
  const Schedule = () => {
104365
105103
  return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(AutomationSelectTimeMain, { title: "Set Schedule Time" }) });
104366
105104
  };
105105
+ var AutomationAudienceSelectorType = /* @__PURE__ */ ((AutomationAudienceSelectorType2) => {
105106
+ AutomationAudienceSelectorType2["ALL"] = "all";
105107
+ AutomationAudienceSelectorType2["SEGMENTS"] = "segments";
105108
+ AutomationAudienceSelectorType2["INDIVIDUAL"] = "individual";
105109
+ return AutomationAudienceSelectorType2;
105110
+ })(AutomationAudienceSelectorType || {});
105111
+ const generateOneOffSegmentName = (automationId) => {
105112
+ return `ONE-OFF-SEGMENT-${automationId}`;
105113
+ };
104367
105114
  var DateUnit = /* @__PURE__ */ ((DateUnit2) => {
104368
105115
  DateUnit2["DAYS"] = "days";
104369
105116
  return DateUnit2;
@@ -104431,7 +105178,7 @@ const operatorToHumanReadable = (operator, type) => {
104431
105178
  return "Less than";
104432
105179
  }
104433
105180
  case ConditionOperatorEnumType.EXISTS:
104434
- return "Exists";
105181
+ return "Is Known";
104435
105182
  case ConditionOperatorEnumType.EQUALS_MONTH_DAY:
104436
105183
  return "Month and Day Matches";
104437
105184
  case ConditionOperatorEnumType.EQUALS_MONTH_DAY_YEAR:
@@ -105419,6 +106166,39 @@ const ConditionRow = ({
105419
106166
  ] })
105420
106167
  ] });
105421
106168
  };
106169
+ const BetaTag = React.forwardRef(
106170
+ ({
106171
+ children = "BETA",
106172
+ size: size2 = "default",
106173
+ animated = false,
106174
+ className,
106175
+ ...props2
106176
+ }, ref) => {
106177
+ const sizeClasses = {
106178
+ sm: "px-1.5 py-0.5 text-[10px]",
106179
+ default: "px-2 py-0.5 text-xs",
106180
+ lg: "px-2.5 py-1 text-sm"
106181
+ };
106182
+ return /* @__PURE__ */ jsx(
106183
+ "span",
106184
+ {
106185
+ ref,
106186
+ className: cn$2(
106187
+ "inline-flex items-center justify-center rounded-full font-medium leading-none",
106188
+ "bg-gray-100 text-gray-600 border border-gray-200",
106189
+ "dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700",
106190
+ "relative select-none",
106191
+ sizeClasses[size2],
106192
+ animated && "animate-pulse",
106193
+ className
106194
+ ),
106195
+ ...props2,
106196
+ children
106197
+ }
106198
+ );
106199
+ }
106200
+ );
106201
+ BetaTag.displayName = "BetaTag";
105422
106202
  const PromptBuilderStyle = ({ onSelectStyle }) => {
105423
106203
  const [selectedStyle, setSelectedStyle] = useState(
105424
106204
  null
@@ -105431,7 +106211,10 @@ const PromptBuilderStyle = ({ onSelectStyle }) => {
105431
106211
  setSelectedStyle("ai");
105432
106212
  onSelectStyle("ai");
105433
106213
  },
105434
- title: "Build with AI",
106214
+ title: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2", children: [
106215
+ /* @__PURE__ */ jsx("span", { children: "Build with AI" }),
106216
+ /* @__PURE__ */ jsx(BetaTag, { size: "sm" })
106217
+ ] }),
105435
106218
  subtitle: "Describe your audience in plain language and let AI create the conditions",
105436
106219
  icon: /* @__PURE__ */ jsx("div", { className: "h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-6 h-6 text-[hsl(var(--reach-primary))]", children: /* @__PURE__ */ jsx(Sparkles, { className: "w-full h-full" }) }) }),
105437
106220
  selected: selectedStyle === "ai"
@@ -105511,6 +106294,7 @@ const TextToSegment = ({ setTextToSegment, setSegment }) => {
105511
106294
  const [userInput, setUserInput] = React__default.useState("");
105512
106295
  const [isLoading, setIsLoading] = React__default.useState(false);
105513
106296
  const { toast: toast2 } = useToast();
106297
+ const { sendNotificationAsync } = useSlackNotification();
105514
106298
  const getSegment2 = async () => {
105515
106299
  if (!userInput) {
105516
106300
  toast2({
@@ -105522,9 +106306,14 @@ const TextToSegment = ({ setTextToSegment, setSegment }) => {
105522
106306
  }
105523
106307
  setIsLoading(true);
105524
106308
  const response = await textToSegment({
105525
- userInput,
106309
+ text: userInput,
105526
106310
  userCustomVerb: t$2("engage:user")
105527
106311
  });
106312
+ sendNotificationAsync({
106313
+ message: "💬 New AI Segment requested",
106314
+ product: "engage",
106315
+ details: { userInput, response }
106316
+ });
105528
106317
  if (response.success && response.conditions) {
105529
106318
  toast2({
105530
106319
  title: `${t$2("engage:segment")} conditions built successfully 🚀`,
@@ -106091,9 +106880,14 @@ const TotalExplanationTooltip = () => {
106091
106880
  return /* @__PURE__ */ jsx(
106092
106881
  InfoTooltip,
106093
106882
  {
106094
- title: `These are estimated numbers of ${t$2("engage:user", {
106883
+ title: `The total number of ${t$2("engage:user", {
106095
106884
  count: 2
106096
- }).toLocaleLowerCase()} that are matched. The actual count may change at the time of sending. You can see the breakdown of who have opted into email or sms.`
106885
+ }).toLocaleLowerCase()} may not match your SMS and email counts because some ${t$2(
106886
+ "engage:user",
106887
+ {
106888
+ count: 2
106889
+ }
106890
+ ).toLocaleLowerCase()} may have unsubscribed, invalid contact information, or haven't opted into marketing messages.`
106097
106891
  }
106098
106892
  );
106099
106893
  };
@@ -106104,7 +106898,8 @@ const StatCard$1 = ({
106104
106898
  variant,
106105
106899
  isLoading,
106106
106900
  selected,
106107
- onClick
106901
+ onClick,
106902
+ tooltip
106108
106903
  }) => {
106109
106904
  const variantStyles = {
106110
106905
  default: "bg-gray-100/50 text-gray-700",
@@ -106123,13 +106918,16 @@ const StatCard$1 = ({
106123
106918
  children: [
106124
106919
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 items-start w-full min-w-0", children: [
106125
106920
  /* @__PURE__ */ jsx("div", { className: "flex items-baseline gap-2 w-full min-w-0 @container/num", children: isLoading ? /* @__PURE__ */ jsx("span", { className: "animate-pulse", children: "..." }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
106126
- /* @__PURE__ */ jsx(
106127
- "span",
106128
- {
106129
- className: "font-semibold pl-2 break-words max-w-full min-w-0 truncate\n text-xl @[250px]/card:text-2xl @[350px]/card:text-3xl @[500px]/card:text-4xl text-primary",
106130
- children: value.toLocaleString()
106131
- }
106132
- ),
106921
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
106922
+ /* @__PURE__ */ jsx(
106923
+ "span",
106924
+ {
106925
+ className: "font-semibold pl-2 break-words max-w-full min-w-0 truncate\n text-xl @[250px]/card:text-2xl @[350px]/card:text-3xl @[500px]/card:text-4xl text-primary",
106926
+ children: value.toLocaleString()
106927
+ }
106928
+ ),
106929
+ tooltip && /* @__PURE__ */ jsx(InfoTooltip, { title: tooltip })
106930
+ ] }),
106133
106931
  percentage2 !== void 0 && value.toLocaleString().replace(/,/g, "").length < 5 && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/50 text-sm hidden @[120px]/num:inline", children: [
106134
106932
  "(",
106135
106933
  percentage2,
@@ -106162,11 +106960,28 @@ const EstimatedMatchesView = ({ countResponse, selectedFilter = "all", onFilterC
106162
106960
  );
106163
106961
  }, [countResponse]);
106164
106962
  const calculatePercentage = (value) => Math.round(value / (stats.total || 1) * 100);
106963
+ const showReassuranceBanner = stats.total > Math.max(stats.emails, stats.phones);
106165
106964
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
106166
106965
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
106167
106966
  /* @__PURE__ */ jsx(MinorText, { children: "Estimated Matches" }),
106168
106967
  /* @__PURE__ */ jsx(TotalExplanationTooltip, {})
106169
106968
  ] }),
106969
+ showReassuranceBanner && /* @__PURE__ */ jsx("div", { className: "mt-2 p-3 bg-secondary/50 border border-border rounded-lg", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
106970
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Total vs. Subscribed mismatch?" }),
106971
+ " ",
106972
+ "Some ",
106973
+ t$2("engage:user", { count: 2 }).toLowerCase(),
106974
+ " may have unsubscribed or had hard bounces.",
106975
+ /* @__PURE__ */ jsx(
106976
+ InfoTooltip,
106977
+ {
106978
+ description: "This can happen when customers unsubscribe, emails bounces, or contact info is missing. This is normal and expected.",
106979
+ size: "small",
106980
+ iconColor: "text-muted-foreground",
106981
+ children: /* @__PURE__ */ jsx("span", { className: "underline cursor-help", children: "Learn more" })
106982
+ }
106983
+ )
106984
+ ] }) }),
106170
106985
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 md:mb-6 w-full gap-4 mt-3 px-1", children: [
106171
106986
  /* @__PURE__ */ jsx(
106172
106987
  StatCard$1,
@@ -106176,7 +106991,15 @@ const EstimatedMatchesView = ({ countResponse, selectedFilter = "all", onFilterC
106176
106991
  variant: "default",
106177
106992
  isLoading: countResponse === void 0,
106178
106993
  selected: selectedFilter === "all",
106179
- onClick: onFilterChange ? () => onFilterChange("all") : void 0
106994
+ onClick: onFilterChange ? () => onFilterChange("all") : void 0,
106995
+ tooltip: `Total includes all ${t$2("engage:user", {
106996
+ count: 2
106997
+ }).toLocaleLowerCase()} in your system. This does not factor in ${t$2(
106998
+ "engage:user",
106999
+ {
107000
+ count: 2
107001
+ }
107002
+ ).toLocaleLowerCase()} who might have opted out of email or sms.`
106180
107003
  }
106181
107004
  ),
106182
107005
  /* @__PURE__ */ jsx(
@@ -106206,7 +107029,7 @@ const EstimatedMatchesView = ({ countResponse, selectedFilter = "all", onFilterC
106206
107029
  ] })
106207
107030
  ] });
106208
107031
  };
106209
- const getDisplayName = (recipient) => {
107032
+ const getDisplayName$1 = (recipient) => {
106210
107033
  const fullName = `${recipient.firstName || ""} ${recipient.lastName || ""}`.trim();
106211
107034
  if (fullName) return fullName;
106212
107035
  if (recipient.firstName) return recipient.firstName;
@@ -106215,7 +107038,7 @@ const getDisplayName = (recipient) => {
106215
107038
  if (recipient.phone) return recipient.phone;
106216
107039
  return `Unnamed ${t$2("engage:user")}`;
106217
107040
  };
106218
- const RecipientsTable = ({ recipients, filter: filter2 = "all" }) => {
107041
+ const RecipientsTable = ({ recipients, filter: filter2 = "all", audienceMode }) => {
106219
107042
  const filteredRecipients = recipients?.filter((recipient) => {
106220
107043
  const hasEmail = !!recipient.email;
106221
107044
  const hasPhone = !!recipient.phone;
@@ -106230,14 +107053,45 @@ const RecipientsTable = ({ recipients, filter: filter2 = "all" }) => {
106230
107053
  return true;
106231
107054
  }
106232
107055
  });
106233
- if (!filteredRecipients?.length) return null;
107056
+ if (!filteredRecipients?.length) {
107057
+ return /* @__PURE__ */ jsx("div", { className: "bg-foreground/5 rounded-lg h-full relative overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "text-center max-w-sm", children: /* @__PURE__ */ jsxs("div", { className: "text-gray-400 text-sm mb-2", children: [
107058
+ filter2 === "email" && "No recipients with email addresses",
107059
+ filter2 === "phone" && "No recipients with phone numbers",
107060
+ filter2 === "all" && (audienceMode === "segments" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
107061
+ /* @__PURE__ */ jsxs("div", { className: "text-gray-500 font-medium mb-1", children: [
107062
+ "No ",
107063
+ t$2("engage:segment").toLowerCase(),
107064
+ " selected"
107065
+ ] }),
107066
+ /* @__PURE__ */ jsxs("div", { className: "text-gray-400 text-xs", children: [
107067
+ "Select one or more",
107068
+ " ",
107069
+ t$2("engage:segment", { count: 2 }).toLowerCase(),
107070
+ " from the list above to see your recipients"
107071
+ ] })
107072
+ ] }) : audienceMode === "individual" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
107073
+ /* @__PURE__ */ jsxs("div", { className: "text-gray-500 font-medium mb-1", children: [
107074
+ "No individual",
107075
+ " ",
107076
+ t$2("engage:user", { count: 2 }).toLowerCase(),
107077
+ " selected"
107078
+ ] }),
107079
+ /* @__PURE__ */ jsxs("div", { className: "text-gray-400 text-xs", children: [
107080
+ "Choose specific",
107081
+ " ",
107082
+ t$2("engage:user", { count: 2 }).toLowerCase(),
107083
+ "from the dropdown above to see your recipients"
107084
+ ] })
107085
+ ] }) : "No recipients found")
107086
+ ] }) }) }) });
107087
+ }
106234
107088
  return /* @__PURE__ */ jsx("div", { className: "bg-foreground/5 rounded-lg h-full relative overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 w-full h-full overflow-y-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
106235
107089
  /* @__PURE__ */ jsx("thead", { className: "sticky top-0 bg-foreground/5 backdrop-blur-sm", children: /* @__PURE__ */ jsxs("tr", { children: [
106236
107090
  /* @__PURE__ */ jsx("th", { className: "px-3 sm:px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide", children: "Name" }),
106237
107091
  /* @__PURE__ */ jsx("th", { className: "px-3 sm:px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide", children: "Contact" })
106238
107092
  ] }) }),
106239
107093
  /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-gray-200/50", children: filteredRecipients.map((recipient, index2) => /* @__PURE__ */ jsxs("tr", { className: "hover:bg-white/50 transition-colors", children: [
106240
- /* @__PURE__ */ jsx("td", { className: "px-3 sm:px-4 py-3 text-sm text-gray-900", children: getDisplayName(recipient) }),
107094
+ /* @__PURE__ */ jsx("td", { className: "px-3 sm:px-4 py-3 text-sm text-gray-900", children: getDisplayName$1(recipient) }),
106241
107095
  /* @__PURE__ */ jsx("td", { className: "px-3 sm:px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1.5 sm:gap-2", children: [
106242
107096
  recipient.email && /* @__PURE__ */ jsx("span", { className: "px-2 sm:px-2.5 py-1 text-xs font-medium bg-blue-100/50 text-blue-700 rounded-lg", children: "Email" }),
106243
107097
  recipient.phone && /* @__PURE__ */ jsx("span", { className: "px-2 sm:px-2.5 py-1 text-xs font-medium bg-green-100/50 text-green-700 rounded-lg", children: "Phone" })
@@ -106245,7 +107099,11 @@ const RecipientsTable = ({ recipients, filter: filter2 = "all" }) => {
106245
107099
  ] }, index2)) })
106246
107100
  ] }) }) });
106247
107101
  };
106248
- const AllOrSelectSegmentPicker = ({ onSelectionChange, defaultValue = "all", canEditAudience = true }) => {
107102
+ const AllOrSelectSegmentPicker = ({
107103
+ onSelectionChange,
107104
+ defaultValue = AutomationAudienceSelectorType.ALL,
107105
+ canEditAudience = true
107106
+ }) => {
106249
107107
  const [selected, setSelected] = useState(defaultValue);
106250
107108
  const handleSelect = (value) => {
106251
107109
  if (!canEditAudience) return;
@@ -106257,32 +107115,124 @@ const AllOrSelectSegmentPicker = ({ onSelectionChange, defaultValue = "all", can
106257
107115
  /* @__PURE__ */ jsx(MinorText, { children: "Filter" }),
106258
107116
  !canEditAudience && /* @__PURE__ */ jsx(InfoTooltip, { title: "You cannot edit the audience unless you are in draft mode" })
106259
107117
  ] }),
106260
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4 p-1", children: [
107118
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-4 p-1", children: [
106261
107119
  /* @__PURE__ */ jsx(
106262
107120
  BigSelector,
106263
107121
  {
106264
- onClick: () => handleSelect("all"),
107122
+ onClick: () => handleSelect(AutomationAudienceSelectorType.ALL),
106265
107123
  title: `All ${t$2("engage:user", { count: 2 })}`,
106266
107124
  subtitle: "Send to everyone",
106267
107125
  icon: /* @__PURE__ */ jsx(IconDefinitions.UsersIcon, { className: "w-5 h-5 mb-2 text-gray-600" }),
106268
107126
  selected: selected === "all",
106269
- disabled: !canEditAudience
107127
+ disabled: !canEditAudience,
107128
+ hideSelectedText: true
106270
107129
  }
106271
107130
  ),
106272
107131
  /* @__PURE__ */ jsx(
106273
107132
  BigSelector,
106274
107133
  {
106275
- onClick: () => handleSelect("segments"),
106276
- title: `Select ${t$2("engage:user", { count: 2 })}`,
107134
+ onClick: () => handleSelect(AutomationAudienceSelectorType.SEGMENTS),
107135
+ title: `Select ${t$2("engage:segment", { count: 2 })}`,
106277
107136
  subtitle: `Choose from ${t$2("engage:segment", { count: 2 })}`,
106278
107137
  icon: /* @__PURE__ */ jsx(IconDefinitions.UserEdit, { className: "w-5 h-5 mb-2 text-gray-600" }),
106279
107138
  selected: selected === "segments",
106280
- disabled: !canEditAudience
107139
+ disabled: !canEditAudience,
107140
+ hideSelectedText: true
107141
+ }
107142
+ ),
107143
+ /* @__PURE__ */ jsx(
107144
+ BigSelector,
107145
+ {
107146
+ onClick: () => handleSelect(AutomationAudienceSelectorType.INDIVIDUAL),
107147
+ title: "Select Individual",
107148
+ subtitle: `Pick ${t$2("engage:user", { count: 2 }).toLowerCase()} one by one`,
107149
+ icon: /* @__PURE__ */ jsx(IconDefinitions.UserIcon, { className: "w-5 h-5 mb-2 text-gray-600" }),
107150
+ selected: selected === "individual",
107151
+ disabled: !canEditAudience,
107152
+ hideSelectedText: true
106281
107153
  }
106282
107154
  )
106283
107155
  ] })
106284
107156
  ] });
106285
107157
  };
107158
+ const getDisplayName = (recipient) => {
107159
+ const fullName = `${recipient.firstName || ""} ${recipient.lastName || ""}`.trim();
107160
+ if (fullName) return fullName;
107161
+ if (recipient.firstName) return recipient.firstName;
107162
+ if (recipient.lastName) return recipient.lastName;
107163
+ if (recipient.email) return recipient.email;
107164
+ if (recipient.phone) return recipient.phone;
107165
+ return `Unnamed User`;
107166
+ };
107167
+ const SelectIndividualUsersContent = ({ selectedUserIds, onUserSelectionChange, canEditAudience = true }) => {
107168
+ const [tempSelectedUserIds, setTempSelectedUserIds] = useState(selectedUserIds);
107169
+ const [allUsersForDropdown, setAllUsersForDropdown] = useState();
107170
+ const { segments } = useListSegments();
107171
+ useEffect(() => {
107172
+ setTempSelectedUserIds(selectedUserIds);
107173
+ }, [selectedUserIds]);
107174
+ useEffect(() => {
107175
+ const fetchAllUsers = async () => {
107176
+ if (!segments) return;
107177
+ const allUsersSegment = segments.find(
107178
+ (segment2) => segment2.type === BusinessSegmentTypeEnum.ALL_USERS
107179
+ );
107180
+ if (allUsersSegment) {
107181
+ try {
107182
+ const allUsersResponse = await getCountOfBusinessAutomationRecipients(
107183
+ {
107184
+ includeSegments: [allUsersSegment.id],
107185
+ excludeSegments: []
107186
+ }
107187
+ );
107188
+ setAllUsersForDropdown(allUsersResponse);
107189
+ } catch (error2) {
107190
+ console.error("Error fetching all users for dropdown:", error2);
107191
+ }
107192
+ }
107193
+ };
107194
+ fetchAllUsers();
107195
+ }, [segments]);
107196
+ const userOptions = React__default.useMemo(() => {
107197
+ if (!allUsersForDropdown?.recipients) return [];
107198
+ return allUsersForDropdown.recipients.map((recipient) => ({
107199
+ label: getDisplayName(recipient),
107200
+ value: recipient.externalId
107201
+ }));
107202
+ }, [allUsersForDropdown?.recipients]);
107203
+ const handleUserSelectionChange = (newSelectedUserIds) => {
107204
+ setTempSelectedUserIds(newSelectedUserIds);
107205
+ onUserSelectionChange(newSelectedUserIds);
107206
+ };
107207
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
107208
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
107209
+ /* @__PURE__ */ jsxs("div", { children: [
107210
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900", children: "Select Individual Users" }),
107211
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "Choose specific users to include in your automation" })
107212
+ ] }),
107213
+ selectedUserIds.length > 0 && /* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-500", children: [
107214
+ selectedUserIds.length,
107215
+ " user",
107216
+ selectedUserIds.length !== 1 ? "s" : "",
107217
+ " selected"
107218
+ ] })
107219
+ ] }),
107220
+ /* @__PURE__ */ jsx(
107221
+ MultiSelectDialog,
107222
+ {
107223
+ options: userOptions,
107224
+ onValueChange: handleUserSelectionChange,
107225
+ selectedValues: tempSelectedUserIds,
107226
+ setSelectedValues: setTempSelectedUserIds,
107227
+ placeholder: "Select users...",
107228
+ title: "Select Individual Users",
107229
+ searchPlaceholder: "Search users...",
107230
+ emptyMessage: "No users found",
107231
+ canOpen: canEditAudience
107232
+ }
107233
+ )
107234
+ ] });
107235
+ };
106286
107236
  const SegmentSection = ({
106287
107237
  segments,
106288
107238
  allSegments,
@@ -106313,8 +107263,9 @@ const SegmentSection = ({
106313
107263
  setSelectedValues: setSegmentSelectedIds,
106314
107264
  options,
106315
107265
  onValueChange: handleValueChange,
106316
- placeholder: "Select segments",
107266
+ placeholder: `Select ${t$2("engage:segment", { count: 2 })}`,
106317
107267
  maxCount: 5,
107268
+ title: `Select ${t$2("engage:segment", { count: 2 })}`,
106318
107269
  extraCommandItems: [
106319
107270
  (closePopup) => /* @__PURE__ */ jsxs(
106320
107271
  CommandItem,
@@ -106408,7 +107359,7 @@ const SelectSegmentsContent = ({
106408
107359
  const AutomationAudienceSelectorMain = ({ title: title2 = "Preview Audience" }) => {
106409
107360
  const automation2 = useAutomation();
106410
107361
  const [selectedAudience, setSelectedAudience] = useState(
106411
- "all"
107362
+ AutomationAudienceSelectorType.ALL
106412
107363
  );
106413
107364
  const [includedSegments, setIncludedSegments] = useState(
106414
107365
  automation2?.includeSegmentIds || []
@@ -106416,6 +107367,7 @@ const AutomationAudienceSelectorMain = ({ title: title2 = "Preview Audience" })
106416
107367
  const [excludedSegments, setExcludedSegments] = useState(
106417
107368
  automation2?.excludeSegmentIds || []
106418
107369
  );
107370
+ const [selectedIndividualUserIds, setSelectedIndividualUserIds] = useState([]);
106419
107371
  const [openCreateSegmentDialog, setOpenCreateSegmentDialog] = useState(false);
106420
107372
  const [initialStateSet, setInitialStateSet] = useState(false);
106421
107373
  const [selectedFilter, setSelectedFilter] = useState("all");
@@ -106427,35 +107379,58 @@ const AutomationAudienceSelectorMain = ({ title: title2 = "Preview Audience" })
106427
107379
  const allUsersSelected = includedSegments.length === 1 && excludedSegments.length === 0 && segments?.find(
106428
107380
  (segment2) => segment2.type === BusinessSegmentTypeEnum.ALL_USERS && segment2.id === includedSegments[0]
106429
107381
  ) !== void 0;
107382
+ const segmentIdToFetch = includedSegments.length === 1 && excludedSegments.length === 0 && !allUsersSelected ? includedSegments[0] : "";
107383
+ const { segment: singleSegment } = useGetSegment(segmentIdToFetch);
107384
+ const oneOffSegmentSelected = singleSegment?.type === BusinessSegmentTypeEnum.ONE_OFF;
106430
107385
  useEffect(() => {
106431
- if (automation2 && !initialStateSet && isLoading === false) {
106432
- if (includedSegments.length === 1 && segments.find(
106433
- (segment2) => segment2.type === BusinessSegmentTypeEnum.ALL_USERS && segment2.id === includedSegments[0]
106434
- ) !== void 0) {
106435
- setSelectedAudience("all");
106436
- } else {
106437
- setSelectedAudience("segments");
107386
+ const initializeState = async () => {
107387
+ if (automation2 && !initialStateSet && isLoading === false) {
107388
+ if (includedSegments.length === 1 && segments.find(
107389
+ (segment2) => segment2.type === BusinessSegmentTypeEnum.ALL_USERS && segment2.id === includedSegments[0]
107390
+ ) !== void 0) {
107391
+ setSelectedAudience(AutomationAudienceSelectorType.ALL);
107392
+ } else if (oneOffSegmentSelected && singleSegment) {
107393
+ setSelectedAudience(AutomationAudienceSelectorType.INDIVIDUAL);
107394
+ if (singleSegment.conditions) {
107395
+ const userIds = [];
107396
+ singleSegment.conditions.forEach((condition) => {
107397
+ if (condition.field === "userId" && condition.operator === ConditionOperatorEnumType.EQUALS) {
107398
+ condition.value.forEach((val) => {
107399
+ if (typeof val === "string") {
107400
+ userIds.push(val);
107401
+ }
107402
+ });
107403
+ }
107404
+ });
107405
+ setSelectedIndividualUserIds(userIds);
107406
+ }
107407
+ } else {
107408
+ setSelectedAudience(AutomationAudienceSelectorType.SEGMENTS);
107409
+ }
107410
+ getCountOfBusinessAutomationRecipients({
107411
+ includeSegments: includedSegments,
107412
+ excludeSegments: excludedSegments
107413
+ }).then((response) => {
107414
+ setCountResponse(response);
107415
+ });
107416
+ setInitialStateSet(true);
106438
107417
  }
106439
- getCountOfBusinessAutomationRecipients({
106440
- includeSegments: includedSegments,
106441
- excludeSegments: excludedSegments
106442
- }).then((response) => {
106443
- setCountResponse(response);
106444
- });
106445
- setInitialStateSet(true);
106446
- }
107418
+ };
107419
+ initializeState();
106447
107420
  }, [
106448
107421
  automation2,
106449
107422
  allUsersSelected,
107423
+ oneOffSegmentSelected,
106450
107424
  initialStateSet,
106451
107425
  includedSegments,
106452
107426
  excludedSegments,
106453
107427
  segments,
106454
- isLoading
107428
+ isLoading,
107429
+ singleSegment
106455
107430
  ]);
106456
- const handleAudienceSelectionChange = (value) => {
107431
+ const handleAudienceSelectionChange = async (value) => {
106457
107432
  setSelectedAudience(value);
106458
- if (value === "all") {
107433
+ if (value === AutomationAudienceSelectorType.ALL) {
106459
107434
  const allUsersSegment = segments?.find(
106460
107435
  (segment2) => segment2.type === BusinessSegmentTypeEnum.ALL_USERS
106461
107436
  );
@@ -106465,6 +107440,61 @@ const AutomationAudienceSelectorMain = ({ title: title2 = "Preview Audience" })
106465
107440
  }
106466
107441
  setIncludedSegments([allUsersSegment.id]);
106467
107442
  setExcludedSegments([]);
107443
+ setSelectedIndividualUserIds([]);
107444
+ } else if (value === AutomationAudienceSelectorType.INDIVIDUAL) {
107445
+ if (automation2?.id) {
107446
+ try {
107447
+ const segmentName = generateOneOffSegmentName(automation2.id);
107448
+ let segment2;
107449
+ try {
107450
+ segment2 = await findSegmentByName(segmentName);
107451
+ } catch (error2) {
107452
+ }
107453
+ if (segment2) {
107454
+ setIncludedSegments([segment2.id]);
107455
+ setExcludedSegments([]);
107456
+ if (segment2.conditions) {
107457
+ const userIds = [];
107458
+ segment2.conditions.forEach((condition) => {
107459
+ if (condition.field === "userId" && condition.operator === ConditionOperatorEnumType.EQUALS) {
107460
+ condition.value.forEach((val) => {
107461
+ if (typeof val === "string") {
107462
+ userIds.push(val);
107463
+ }
107464
+ });
107465
+ }
107466
+ });
107467
+ setSelectedIndividualUserIds(userIds);
107468
+ }
107469
+ } else {
107470
+ const newSegment = await createSegment({
107471
+ name: segmentName,
107472
+ description: "One-off segment for individual user selection",
107473
+ type: BusinessSegmentTypeEnum.ONE_OFF,
107474
+ conditions: [
107475
+ {
107476
+ field: "userId",
107477
+ operator: ConditionOperatorEnumType.EQUALS,
107478
+ value: []
107479
+ }
107480
+ ]
107481
+ });
107482
+ if (newSegment?.id) {
107483
+ setIncludedSegments([newSegment.id]);
107484
+ setExcludedSegments([]);
107485
+ }
107486
+ }
107487
+ } catch (error2) {
107488
+ console.error("Error creating ONE_OFF segment:", error2);
107489
+ }
107490
+ }
107491
+ } else if (value === AutomationAudienceSelectorType.SEGMENTS) {
107492
+ setIncludedSegments([]);
107493
+ setExcludedSegments([]);
107494
+ setCountResponse({
107495
+ count: 0,
107496
+ recipients: []
107497
+ });
106468
107498
  }
106469
107499
  };
106470
107500
  const handleAddSegment = (segmentId, type) => {
@@ -106481,8 +107511,75 @@ const AutomationAudienceSelectorMain = ({ title: title2 = "Preview Audience" })
106481
107511
  setExcludedSegments((prev) => prev.filter((id2) => id2 !== segmentId));
106482
107512
  }
106483
107513
  };
107514
+ const handleIndividualUserSelectionChange = async (userIds) => {
107515
+ setSelectedIndividualUserIds(userIds);
107516
+ if (automation2?.id) {
107517
+ try {
107518
+ const segmentName = generateOneOffSegmentName(automation2.id);
107519
+ let segment2;
107520
+ try {
107521
+ segment2 = await findSegmentByName(segmentName);
107522
+ } catch (error2) {
107523
+ }
107524
+ if (segment2) {
107525
+ const updatedSegment = await updateSegment(segment2.id, {
107526
+ conditions: userIds.length > 0 ? [
107527
+ {
107528
+ field: "userId",
107529
+ operator: ConditionOperatorEnumType.EQUALS,
107530
+ value: userIds
107531
+ }
107532
+ ] : [
107533
+ {
107534
+ field: "userId",
107535
+ operator: ConditionOperatorEnumType.EQUALS,
107536
+ value: []
107537
+ }
107538
+ ]
107539
+ });
107540
+ segment2 = updatedSegment;
107541
+ } else {
107542
+ segment2 = await createSegment({
107543
+ name: segmentName,
107544
+ description: "One-off segment for individual user selection",
107545
+ type: BusinessSegmentTypeEnum.ONE_OFF,
107546
+ conditions: userIds.length > 0 ? [
107547
+ {
107548
+ field: "userId",
107549
+ operator: ConditionOperatorEnumType.EQUALS,
107550
+ value: userIds
107551
+ }
107552
+ ] : [
107553
+ {
107554
+ field: "userId",
107555
+ operator: ConditionOperatorEnumType.EQUALS,
107556
+ value: []
107557
+ }
107558
+ ]
107559
+ });
107560
+ }
107561
+ if (segment2?.id) {
107562
+ await updateAutomation2({
107563
+ includeSegments: [segment2.id],
107564
+ excludeSegments: []
107565
+ });
107566
+ await new Promise((resolve) => setTimeout(resolve, 100));
107567
+ const countResponse2 = await getCountOfBusinessAutomationRecipients({
107568
+ includeSegments: [segment2.id],
107569
+ excludeSegments: []
107570
+ });
107571
+ setCountResponse(countResponse2);
107572
+ }
107573
+ } catch (error2) {
107574
+ console.error("Error creating/updating ONE_OFF segment:", error2);
107575
+ }
107576
+ }
107577
+ };
106484
107578
  useEffect(() => {
106485
107579
  if (automation2 && initialStateSet) {
107580
+ if (selectedAudience === AutomationAudienceSelectorType.INDIVIDUAL && includedSegments.length === 0 && excludedSegments.length === 0) {
107581
+ return;
107582
+ }
106486
107583
  if (JSON.stringify(includedSegments) !== JSON.stringify(automation2.includeSegmentIds) || JSON.stringify(excludedSegments) !== JSON.stringify(automation2.excludeSegmentIds)) {
106487
107584
  updateAutomation2({
106488
107585
  includeSegments: includedSegments,
@@ -106501,7 +107598,8 @@ const AutomationAudienceSelectorMain = ({ title: title2 = "Preview Audience" })
106501
107598
  excludedSegments,
106502
107599
  automation2,
106503
107600
  initialStateSet,
106504
- updateAutomation2
107601
+ updateAutomation2,
107602
+ selectedAudience
106505
107603
  ]);
106506
107604
  const onSegmentUpdated = (id2) => {
106507
107605
  if (id2) {
@@ -106528,7 +107626,7 @@ const AutomationAudienceSelectorMain = ({ title: title2 = "Preview Audience" })
106528
107626
  canEditAudience
106529
107627
  }
106530
107628
  ),
106531
- /* @__PURE__ */ jsx(AnimatePresence, { children: selectedAudience === "segments" && /* @__PURE__ */ jsx(
107629
+ /* @__PURE__ */ jsx(AnimatePresence, { children: selectedAudience === AutomationAudienceSelectorType.SEGMENTS && /* @__PURE__ */ jsx(
106532
107630
  motion.div,
106533
107631
  {
106534
107632
  initial: { opacity: 0, height: 0 },
@@ -106550,13 +107648,33 @@ const AutomationAudienceSelectorMain = ({ title: title2 = "Preview Audience" })
106550
107648
  }
106551
107649
  )
106552
107650
  }
107651
+ ) }),
107652
+ /* @__PURE__ */ jsx(AnimatePresence, { children: selectedAudience === AutomationAudienceSelectorType.INDIVIDUAL && /* @__PURE__ */ jsx(
107653
+ motion.div,
107654
+ {
107655
+ initial: { opacity: 0, height: 0 },
107656
+ animate: { opacity: 1, height: "auto" },
107657
+ exit: { opacity: 0, height: 0 },
107658
+ transition: { duration: 0.3, ease: "easeInOut" },
107659
+ className: "w-full pt-4",
107660
+ children: /* @__PURE__ */ jsx(
107661
+ SelectIndividualUsersContent,
107662
+ {
107663
+ countResponse,
107664
+ selectedUserIds: selectedIndividualUserIds,
107665
+ onUserSelectionChange: handleIndividualUserSelectionChange,
107666
+ canEditAudience
107667
+ }
107668
+ )
107669
+ }
106553
107670
  ) })
106554
107671
  ] }),
106555
107672
  /* @__PURE__ */ jsx("div", { className: "flex flex-col flex-1 relative overflow-hidden", children: countResponse?.recipients && /* @__PURE__ */ jsx(
106556
107673
  RecipientsTable,
106557
107674
  {
106558
107675
  recipients: countResponse.recipients,
106559
- filter: selectedFilter
107676
+ filter: selectedFilter,
107677
+ audienceMode: selectedAudience
106560
107678
  }
106561
107679
  ) })
106562
107680
  ] }),
@@ -106997,7 +108115,7 @@ const validateAutomationStep = async (args) => {
106997
108115
  const smsChannelSender = smsChannelSenders.find(
106998
108116
  (sender) => sender.channelSender.id === communicationGroup.smsChannelSenderId
106999
108117
  );
107000
- if (!smsChannelSender) {
108118
+ if (!smsChannelSender && !communicationGroup.emailChannelSenderId) {
107001
108119
  return {
107002
108120
  canMove: false,
107003
108121
  errorMessage: "Please ensure you have selected a text sender"
@@ -107009,7 +108127,7 @@ const validateAutomationStep = async (args) => {
107009
108127
  errorMessage: "Please ensure you have a text body"
107010
108128
  };
107011
108129
  }
107012
- if (smsChannelSender.smsApplication && smsChannelSender.smsApplication.status !== SmsRegistrationApplicationStatus.APPROVED) {
108130
+ if (smsChannelSender && smsChannelSender.smsApplication && smsChannelSender.smsApplication.status !== SmsRegistrationApplicationStatus.APPROVED) {
107013
108131
  return {
107014
108132
  canMove: true,
107015
108133
  errorMessage: null,
@@ -107037,10 +108155,10 @@ const validateAutomationStep = async (args) => {
107037
108155
  errorMessage: "Please ensure you have selected a communication group"
107038
108156
  };
107039
108157
  }
107040
- if (!communicationGroup.emailChannelSenderId) {
108158
+ if (!communicationGroup.emailChannelSenderId && !communicationGroup.smsChannelSenderId) {
107041
108159
  return {
107042
108160
  canMove: false,
107043
- errorMessage: "Please ensure you have selected a email sender"
108161
+ errorMessage: "Please ensure you have selected an email or SMS sender"
107044
108162
  };
107045
108163
  }
107046
108164
  if (!communicationGroup.emailHtmlBody || !communicationGroup.emailSubject) {
@@ -107359,6 +108477,8 @@ const OneTimeWizardMain = ({ onFinish, getExtraMergeFields, onBeforeSchedule })
107359
108477
  if (currentStep > AutomationSteps.SelectAudience) {
107360
108478
  if (currentStep === AutomationSteps.EditSmsContent && selectedChannels.length === 1 && selectedChannels[0] === "sms") {
107361
108479
  setCurrentStep(currentStep - 2);
108480
+ } else if (currentStep === AutomationSteps.Schedule && selectedChannels.length === 1 && selectedChannels[0] === "email") {
108481
+ setCurrentStep(currentStep - 2);
107362
108482
  } else {
107363
108483
  setCurrentStep(currentStep - 1);
107364
108484
  }
@@ -107754,8 +108874,7 @@ function CreateAutomationDialog({
107754
108874
  replyToSettingsText,
107755
108875
  replyToSettingsLink,
107756
108876
  fromNameSettingsText,
107757
- fromNameSettingsLink,
107758
- onBeforeSchedule
108877
+ fromNameSettingsLink
107759
108878
  }) {
107760
108879
  const onCloseInner = (createdAutomation) => {
107761
108880
  onClose?.(createdAutomation);
@@ -107795,8 +108914,7 @@ function CreateAutomationDialog({
107795
108914
  automationType,
107796
108915
  currentStep,
107797
108916
  setCurrentStep,
107798
- getExtraMergeFields,
107799
- onBeforeSchedule
108917
+ getExtraMergeFields
107800
108918
  }
107801
108919
  )
107802
108920
  ]
@@ -108016,14 +109134,20 @@ const generateBrandSettings = async () => {
108016
109134
  return response.data;
108017
109135
  };
108018
109136
  const useBrandSettingsMutation = () => {
109137
+ const { sendNotificationAsync } = useSlackNotification();
108019
109138
  return useMutation({
108020
109139
  mutationFn: generateBrandSettings,
108021
109140
  mutationKey: websiteContentKeys.brandSettings(),
108022
- onSuccess: () => {
109141
+ onSuccess: (data) => {
108023
109142
  toast({
108024
109143
  title: "Brand settings generated successfully!",
108025
109144
  description: "Your brand identity has been analyzed."
108026
109145
  });
109146
+ sendNotificationAsync({
109147
+ message: "💬 New brand settings generated",
109148
+ product: "engage",
109149
+ details: { brandSettingsResponse: data }
109150
+ });
108027
109151
  },
108028
109152
  onError: () => {
108029
109153
  toast({
@@ -109460,6 +110584,123 @@ const EmailChannelStep = ({
109460
110584
  }
109461
110585
  );
109462
110586
  };
110587
+ const MergeFieldForm = forwardRef(({ onSubmit }, ref) => {
110588
+ const { getMergeFields: getMergeFields2, isGetting, getError } = useGetMergeFields("all");
110589
+ const { data: business } = useBusiness();
110590
+ const [fallbacks, setFallbacks] = useState([]);
110591
+ useEffect(() => {
110592
+ if (getMergeFields2?.mergeFields && business) {
110593
+ setFallbacks(
110594
+ getMergeFields2.mergeFields.flatMap(
110595
+ (category) => category.entries.map((entry) => ({
110596
+ label: entry.label,
110597
+ value: entry.value,
110598
+ // Keep the template name (value) for backend
110599
+ fallbackValue: business.merge_field_fallbacks[entry.label] || ""
110600
+ }))
110601
+ )
110602
+ );
110603
+ }
110604
+ }, [getMergeFields2, business]);
110605
+ const handleFallbackChange = (index2, value) => {
110606
+ setFallbacks(
110607
+ (fallbacks2) => fallbacks2.map(
110608
+ (f2, i3) => i3 === index2 ? { ...f2, fallbackValue: value } : f2
110609
+ )
110610
+ );
110611
+ };
110612
+ const handleSubmit = (e4) => {
110613
+ e4.preventDefault();
110614
+ onSubmit(fallbacks);
110615
+ };
110616
+ useImperativeHandle(
110617
+ ref,
110618
+ () => ({
110619
+ submit: () => {
110620
+ onSubmit(fallbacks);
110621
+ },
110622
+ getData: () => fallbacks
110623
+ }),
110624
+ [fallbacks, onSubmit]
110625
+ );
110626
+ const hasMergeFields = Array.isArray(getMergeFields2?.mergeFields) && getMergeFields2.mergeFields.some(
110627
+ (category) => Array.isArray(category.entries) && category.entries.length > 0
110628
+ );
110629
+ if (isGetting) {
110630
+ return /* @__PURE__ */ jsx("div", { className: "py-8", children: /* @__PURE__ */ jsx(BasicLoader, { text: "Loading merge fields..." }) });
110631
+ }
110632
+ if (getError) {
110633
+ return /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-destructive", children: "Error loading merge fields" });
110634
+ }
110635
+ if (!hasMergeFields) {
110636
+ return /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-muted-foreground", children: "No merge fields available" });
110637
+ }
110638
+ return /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, className: "space-y-6 p-8", children: /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full border-separate border-spacing-y-2", children: [
110639
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
110640
+ /* @__PURE__ */ jsx("th", { className: "text-left text-sm font-semibold text-foreground pb-2", children: "Merge Field" }),
110641
+ /* @__PURE__ */ jsx("th", { className: "text-left text-sm font-semibold text-foreground pb-2", children: "Fallback Value" })
110642
+ ] }) }),
110643
+ /* @__PURE__ */ jsx("tbody", { children: fallbacks.map((field, idx) => /* @__PURE__ */ jsxs("tr", { className: "align-top", children: [
110644
+ /* @__PURE__ */ jsx("td", { className: "pr-4 py-1 text-base text-foreground whitespace-nowrap", children: field.label }),
110645
+ /* @__PURE__ */ jsx("td", { className: "py-1", children: /* @__PURE__ */ jsx(
110646
+ Input,
110647
+ {
110648
+ value: field.fallbackValue,
110649
+ onChange: (e4) => handleFallbackChange(idx, e4.target.value),
110650
+ placeholder: "Enter fallback value",
110651
+ className: "w-full max-w-xs"
110652
+ }
110653
+ ) })
110654
+ ] }, field.value)) })
110655
+ ] }) }) });
110656
+ });
110657
+ const MergeFieldSettings = ({
110658
+ onBack,
110659
+ title: title2,
110660
+ description: description2
110661
+ }) => {
110662
+ const { navigation } = useEngageSettingsContext();
110663
+ const { postBusinessMergeFieldFallbacks: postBusinessMergeFieldFallbacks2, isPosting, postError } = usePostBusinessMergeFieldFallbacks();
110664
+ const wizardContext = useContext(
110665
+ WizardContext
110666
+ );
110667
+ const goToPreviousStep = onBack || wizardContext?.goToPreviousStep || navigation?.goToPreviousStep || (() => {
110668
+ });
110669
+ const stepTitle = title2 || ENGAGE_STRINGS.MERGE_FIELDS_TITLE;
110670
+ const stepDescription = description2 || "Manage merge fields for personalizing your communications with dynamic content.";
110671
+ const formRef = useRef(null);
110672
+ const handleFormSubmit = () => {
110673
+ if (formRef.current) {
110674
+ const mergeFields = formRef.current.getData();
110675
+ const mapping = Object.fromEntries(
110676
+ mergeFields.filter((field) => field.fallbackValue.trim()).map((field) => [
110677
+ field.value.replace(/[{}]/g, ""),
110678
+ // Remove {{}} syntax
110679
+ field.fallbackValue.trim()
110680
+ ])
110681
+ );
110682
+ postBusinessMergeFieldFallbacks2({
110683
+ mergeFieldFallbacks: mapping
110684
+ });
110685
+ }
110686
+ };
110687
+ return /* @__PURE__ */ jsxs(
110688
+ WizardFormWrapper,
110689
+ {
110690
+ title: stepTitle,
110691
+ description: stepDescription,
110692
+ onBack: goToPreviousStep,
110693
+ onNext: handleFormSubmit,
110694
+ submitLabel: "Save",
110695
+ mode: "settings",
110696
+ isSubmitting: isPosting,
110697
+ children: [
110698
+ postError && /* @__PURE__ */ jsx("div", { className: "text-destructive text-center py-2", children: "Error saving merge fields" }),
110699
+ /* @__PURE__ */ jsx(MergeFieldForm, { ref: formRef, onSubmit: handleFormSubmit })
110700
+ ]
110701
+ }
110702
+ );
110703
+ };
109463
110704
  const SMSChannelSettings = ({
109464
110705
  hideSubmitButton = false,
109465
110706
  formRef,
@@ -109724,7 +110965,8 @@ const EngageSettings = () => {
109724
110965
  [ENGAGE_STEPS.BRAND]: /* @__PURE__ */ jsx(BrandSettingsStep, {}),
109725
110966
  [ENGAGE_STEPS.BUSINESS]: /* @__PURE__ */ jsx(BusinessSettingsStep, {}),
109726
110967
  [ENGAGE_STEPS.EMAIL]: /* @__PURE__ */ jsx(EmailChannelStep, {}),
109727
- [ENGAGE_STEPS.SMS]: /* @__PURE__ */ jsx(SMSChannelStep, {})
110968
+ [ENGAGE_STEPS.SMS]: /* @__PURE__ */ jsx(SMSChannelStep, {}),
110969
+ [ENGAGE_STEPS.MERGE_FIELDS]: /* @__PURE__ */ jsx(MergeFieldSettings, {})
109728
110970
  }[currentStep] || null
109729
110971
  }
109730
110972
  );
@@ -110108,6 +111350,7 @@ const EngageOnboarding = () => {
110108
111350
  [ENGAGE_STEPS.BUSINESS_SKIP_INTERSTITIAL]: /* @__PURE__ */ jsx(BusinessSkipInterstitialStep, {}),
110109
111351
  [ENGAGE_STEPS.SMS_INTERSTITIAL]: /* @__PURE__ */ jsx(SMSInterstitialStep, {}),
110110
111352
  [ENGAGE_STEPS.SMS]: /* @__PURE__ */ jsx(SMSChannelStep, {}),
111353
+ [ENGAGE_STEPS.MERGE_FIELDS]: /* @__PURE__ */ jsx(MergeFieldSettings, { isOnboarding: true }),
110111
111354
  [ENGAGE_STEPS.COMPLETION]: /* @__PURE__ */ jsx(CompletionStep, {})
110112
111355
  };
110113
111356
  const handleOpenChange = (isOpen) => {
@@ -110809,6 +112052,9 @@ function SegmentList() {
110809
112052
  case BusinessSegmentTypeEnum.MANAGED:
110810
112053
  typeOfSegment = "Managed";
110811
112054
  break;
112055
+ case BusinessSegmentTypeEnum.ONE_OFF:
112056
+ typeOfSegment = "One-off";
112057
+ break;
110812
112058
  default:
110813
112059
  throw UnreachableCaseStatement(
110814
112060
  row.type,
@@ -111219,10 +112465,6 @@ function InlineEditor({
111219
112465
  }
111220
112466
  ) });
111221
112467
  }
111222
- const capitalize = (str) => {
111223
- if (!str) return str;
111224
- return str.charAt(0).toUpperCase() + str.slice(1);
111225
- };
111226
112468
  function AutomationCancelRunningDialog({
111227
112469
  open,
111228
112470
  onOpenChange
@@ -111738,6 +112980,7 @@ const AutomationsEditorHeader = ({ showBackButton, onDuplicationCreated, onBefor
111738
112980
  );
111739
112981
  const noEmailSubjectORnoEmailHtmlBody = communicationGroup?.emailChannelSenderId && (!communicationGroup?.emailSubject || !communicationGroup?.emailHtmlBody);
111740
112982
  const noSmsMessageBody = communicationGroup?.smsChannelSenderId && !communicationGroup?.smsMessageBody;
112983
+ const noIncludeSegments = automation2.includeSegmentIds?.length === 0;
111741
112984
  const missingScheduledAt = automationOneTime && (!automation2.triggerMetadata || !("scheduledAt" in automation2.triggerMetadata) || !automation2.triggerMetadata?.scheduledAt);
111742
112985
  let disableSwitch = false;
111743
112986
  let showSwitch = true;
@@ -111751,7 +112994,7 @@ const AutomationsEditorHeader = ({ showBackButton, onDuplicationCreated, onBefor
111751
112994
  /**
111752
112995
  * If we have a emailChannelSenderId, we need to have a emailSubject and emailHtmlBody
111753
112996
  */
111754
- noEmailANDnoSmsSender || noEmailSubjectORnoEmailHtmlBody || noSmsMessageBody
112997
+ noEmailANDnoSmsSender || noEmailSubjectORnoEmailHtmlBody || noSmsMessageBody || noIncludeSegments
111755
112998
  ) {
111756
112999
  disableSwitch = true;
111757
113000
  if (noEmailSubjectORnoEmailHtmlBody) {
@@ -111760,6 +113003,9 @@ const AutomationsEditorHeader = ({ showBackButton, onDuplicationCreated, onBefor
111760
113003
  if (noSmsMessageBody) {
111761
113004
  infoOnDisabled = "Missing SMS message in SMS communication";
111762
113005
  }
113006
+ if (noIncludeSegments) {
113007
+ infoOnDisabled = "Missing include segment";
113008
+ }
111763
113009
  }
111764
113010
  if (missingScheduledAt) {
111765
113011
  disableSwitch = true;
@@ -111858,7 +113104,7 @@ const AutomationsEditorHeader = ({ showBackButton, onDuplicationCreated, onBefor
111858
113104
  }
111859
113105
  );
111860
113106
  }
111861
- if (noTriggerMetadata || oneTimeNoScheduledAt || noEmailANDnoSmsSender || smsApplicationNotApproved || noEmailSubjectORnoEmailHtmlBody || noSmsMessageBody) {
113107
+ if (noTriggerMetadata || oneTimeNoScheduledAt || noEmailANDnoSmsSender || smsApplicationNotApproved || noEmailSubjectORnoEmailHtmlBody || noSmsMessageBody || noIncludeSegments) {
111862
113108
  const renderInfoTooltip = () => {
111863
113109
  const sendText = automation2.triggerMetadata && "scheduledAt" in automation2.triggerMetadata && automation2.triggerMetadata?.scheduledAt === "now" ? `Start ${automationLanguage}` : "Schedule Send";
111864
113110
  if (automation2.status !== AutomationStatus.DRAFT) {
@@ -111900,6 +113146,13 @@ const AutomationsEditorHeader = ({ showBackButton, onDuplicationCreated, onBefor
111900
113146
  "Please add a message to your SMS communication"
111901
113147
  );
111902
113148
  }
113149
+ if (noIncludeSegments) {
113150
+ alertMessages.push(
113151
+ `Please select at least one ${t$2("engage:segment", {
113152
+ count: 1
113153
+ }).toLowerCase()}`
113154
+ );
113155
+ }
111903
113156
  const formattedMessages = alertMessages.map(
111904
113157
  (msg) => capitalize(msg)
111905
113158
  );
@@ -112174,7 +113427,8 @@ const AutomationRecipientsTable = () => {
112174
113427
  automationId: automation2?.id ?? "",
112175
113428
  cursor: cursorForQuery,
112176
113429
  limit: pageSizeForQuery,
112177
- search: searchQuery || void 0
113430
+ search: searchQuery || void 0,
113431
+ automationStatus: automation2?.status
112178
113432
  });
112179
113433
  const handleQueryParametersChange = useCallback(
112180
113434
  (params) => {
@@ -114584,47 +115838,32 @@ const AutomationAudienceSelector = () => {
114584
115838
  setActionId: setActionId2,
114585
115839
  state: { selectedActionId }
114586
115840
  } = useContext(Context$1);
114587
- const [includeSegmentIds, setIncludeSegmentIds] = useState(
114588
- automation2?.includeSegmentIds || null
114589
- );
114590
- const [excludeSegmentIds, setExcludeSegmentIds] = useState(
114591
- automation2?.excludeSegmentIds || null
114592
- );
114593
115841
  const { segments } = useListSegments();
114594
- const getAllUsersSegment = useCallback(() => {
114595
- return segments?.find(
114596
- (segment2) => segment2.type === BusinessSegmentTypeEnum.ALL_USERS
114597
- );
114598
- }, [segments]);
114599
- useEffect(() => {
114600
- if (!automation2 || !segments) {
114601
- return;
114602
- }
114603
- if (includeSegmentIds === null && excludeSegmentIds === null) {
114604
- setIncludeSegmentIds(automation2?.includeSegmentIds || null);
114605
- setExcludeSegmentIds(automation2?.excludeSegmentIds || null);
114606
- if (automation2.includeSegmentIds.length === 0 && automation2.excludeSegmentIds.length === 0) {
114607
- setIncludeSegmentIds([getAllUsersSegment()?.id || ""]);
114608
- setExcludeSegmentIds([]);
114609
- }
114610
- } else {
114611
- if (JSON.stringify(automation2.includeSegmentIds) !== JSON.stringify(includeSegmentIds)) {
114612
- setIncludeSegmentIds(automation2.includeSegmentIds);
114613
- }
114614
- if (JSON.stringify(automation2.excludeSegmentIds) !== JSON.stringify(excludeSegmentIds)) {
114615
- setExcludeSegmentIds(automation2.excludeSegmentIds);
114616
- }
114617
- }
114618
- }, [
114619
- automation2,
114620
- excludeSegmentIds,
114621
- includeSegmentIds,
114622
- segments,
114623
- getAllUsersSegment
114624
- ]);
114625
- if (!automation2 || !includeSegmentIds || !excludeSegmentIds) {
115842
+ const segmentIdToFetch = automation2?.includeSegmentIds?.length === 1 && automation2?.excludeSegmentIds?.length === 0 ? automation2.includeSegmentIds[0] : "";
115843
+ const { segment: singleSegment } = useGetSegment(segmentIdToFetch);
115844
+ if (!automation2 || !segments) {
114626
115845
  return null;
114627
115846
  }
115847
+ const includeSegmentIds = automation2.includeSegmentIds;
115848
+ let copyText;
115849
+ let showSegmentPills = true;
115850
+ if (singleSegment?.type === BusinessSegmentTypeEnum.ALL_USERS) {
115851
+ copyText = `Send to all ${t$2("engage:user", { count: 2 })}`;
115852
+ showSegmentPills = false;
115853
+ } else if (singleSegment?.type === BusinessSegmentTypeEnum.ONE_OFF) {
115854
+ copyText = `Send to specific ${t$2("engage:user", { count: 2 })}`;
115855
+ showSegmentPills = false;
115856
+ } else {
115857
+ copyText = `Send to all ${t$2("engage:user", {
115858
+ count: 2
115859
+ })} in ${includeSegmentIds.length === 1 ? "this" : "these"} ${t$2(
115860
+ "engage:segment",
115861
+ {
115862
+ count: includeSegmentIds.length
115863
+ }
115864
+ ).toLowerCase()}`;
115865
+ showSegmentPills = true;
115866
+ }
114628
115867
  return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(
114629
115868
  BasicsButton,
114630
115869
  {
@@ -114638,20 +115877,8 @@ const AutomationAudienceSelector = () => {
114638
115877
  children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5 flex-1", children: [
114639
115878
  /* @__PURE__ */ jsx(MinorText, { children: "Audience" }),
114640
115879
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
114641
- /* @__PURE__ */ jsx(
114642
- AutomationFlowLabel,
114643
- {
114644
- copyText: `Send to all ${t$2("engage:user", {
114645
- count: 2
114646
- })} in ${includeSegmentIds.length === 1 ? "this" : "these"} ${t$2(
114647
- "engage:segment",
114648
- {
114649
- count: includeSegmentIds.length
114650
- }
114651
- ).toLowerCase()}`
114652
- }
114653
- ),
114654
- /* @__PURE__ */ jsx("div", { className: "flex gap-1 flex-wrap", children: includeSegmentIds.map((id2) => {
115880
+ /* @__PURE__ */ jsx(AutomationFlowLabel, { copyText }),
115881
+ showSegmentPills && /* @__PURE__ */ jsx("div", { className: "flex gap-1 flex-wrap", children: includeSegmentIds.map((id2) => {
114655
115882
  const segment2 = segments?.find((segment22) => segment22.id === id2);
114656
115883
  if (!segment2) return null;
114657
115884
  return /* @__PURE__ */ jsx(SegmentPill, { segmentName: segment2.name }, id2);
@@ -115028,7 +116255,10 @@ const AutomationEditorSubStep = ({
115028
116255
  return;
115029
116256
  }
115030
116257
  try {
115031
- const params = {};
116258
+ const params = {
116259
+ emailChannelSenderId: communicationGroup.emailChannelSenderId,
116260
+ smsChannelSenderId: communicationGroup.smsChannelSenderId
116261
+ };
115032
116262
  switch (type) {
115033
116263
  case CommunicationSubStepType.EMAIL:
115034
116264
  if (channelEnabled && communicationGroup.emailChannelSenderId) {
@@ -115051,10 +116281,21 @@ const AutomationEditorSubStep = ({
115051
116281
  }
115052
116282
  break;
115053
116283
  }
115054
- await updateCommunicationGroup2({
115055
- groupId: communicationGroup.id,
115056
- params
115057
- });
116284
+ await updateCommunicationGroup2(
116285
+ {
116286
+ groupId: communicationGroup.id,
116287
+ params
116288
+ },
116289
+ {
116290
+ onError: () => {
116291
+ toast2({
116292
+ title: "Error toggling channel",
116293
+ description: "Please contact your administrator",
116294
+ variant: "destructive"
116295
+ });
116296
+ }
116297
+ }
116298
+ );
115058
116299
  } catch (error2) {
115059
116300
  console.error("Error toggling channel:", error2);
115060
116301
  }