@capillarytech/creatives-library 8.0.295 → 8.0.297

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.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +33 -0
  3. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +420 -0
  4. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.scss +36 -0
  5. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +63 -0
  6. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +239 -0
  7. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +111 -0
  8. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +88 -0
  9. package/v2Components/CommonTestAndPreview/SendTestMessage.js +51 -1
  10. package/v2Components/CommonTestAndPreview/actions.js +20 -0
  11. package/v2Components/CommonTestAndPreview/constants.js +15 -0
  12. package/v2Components/CommonTestAndPreview/index.js +200 -16
  13. package/v2Components/CommonTestAndPreview/reducer.js +47 -0
  14. package/v2Components/CommonTestAndPreview/sagas.js +61 -0
  15. package/v2Components/CommonTestAndPreview/selectors.js +51 -0
  16. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +889 -0
  17. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +221 -0
  18. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +235 -0
  19. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +135 -0
  20. package/v2Components/CommonTestAndPreview/tests/actions.test.js +50 -0
  21. package/v2Components/CommonTestAndPreview/tests/constants.test.js +18 -0
  22. package/v2Components/CommonTestAndPreview/tests/index.test.js +783 -2
  23. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +118 -0
  24. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +145 -0
  25. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +146 -0
  26. package/v2Components/FormBuilder/index.js +1 -1
  27. package/v2Components/HtmlEditor/HTMLEditor.js +0 -1
  28. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -1
  29. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +3 -132
  30. package/v2Components/HtmlEditor/hooks/useValidation.js +9 -12
  31. package/v2Components/HtmlEditor/utils/htmlValidator.js +2 -4
  32. package/v2Components/TestAndPreviewSlidebox/index.js +14 -0
  33. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +2 -2
  34. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +18 -110
  35. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +697 -12
  36. package/v2Containers/SmsTrai/Edit/index.js +5 -1
  37. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +201 -0
  38. package/v2Containers/Whatsapp/index.js +1 -1
  39. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +26242 -4225
@@ -77,7 +77,6 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
77
77
  enableSanitization = true,
78
78
  securityLevel = 'standard',
79
79
  apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent
80
- isFullMode = false, // When true, skip liquid validation (standalone/full mode)
81
80
  } = options;
82
81
 
83
82
  // Validation state
@@ -138,7 +137,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
138
137
 
139
138
  try {
140
139
  // 1. HTML Validation
141
- const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage, { skipLiquidValidation: isFullMode });
140
+ const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage);
142
141
 
143
142
  // 2. CSS Validation (extract from HTML)
144
143
  const cssValidation = extractAndValidateCSS(htmlContent, formatValidatorMessage);
@@ -207,7 +206,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
207
206
  },
208
207
  }));
209
208
  }
210
- }, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage, isFullMode]);
209
+ }, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage]);
211
210
 
212
211
  /**
213
212
  * Validates content with debouncing
@@ -340,7 +339,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
340
339
  */
341
340
  const getAllIssues = useCallback(() => {
342
341
  // API errors (liquid + standard) are blocking – they block Save/Update/Preview/Test
343
- const apiLiquidErrors = (isFullMode ? [] : (apiValidationErrors?.liquidErrors || [])).map((errorMessage) => {
342
+ const apiLiquidErrors = (apiValidationErrors?.liquidErrors || []).map((errorMessage) => {
344
343
  const extractedLine = extractLineNumberFromMessage(errorMessage);
345
344
  return {
346
345
  type: VALIDATION_SEVERITY.ERROR,
@@ -421,20 +420,19 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
421
420
 
422
421
  // Ensure we always return an array
423
422
  return Array.isArray(allIssues) ? allIssues : [];
424
- }, [validationState, apiValidationErrors, extractLineNumberFromMessage, content, isFullMode]);
423
+ }, [validationState, apiValidationErrors, extractLineNumberFromMessage, content]);
425
424
 
426
425
  /**
427
426
  * Check if validation is clean (no errors or warnings)
428
427
  * Includes API validation errors in the check
429
428
  */
430
429
  const isClean = useCallback(() => {
431
- const liquidErrorCount = isFullMode ? 0 : (apiValidationErrors?.liquidErrors?.length || 0);
432
- const hasApiErrors = liquidErrorCount + (apiValidationErrors?.standardErrors?.length || 0) > 0;
430
+ const hasApiErrors = (apiValidationErrors?.liquidErrors?.length || 0) + (apiValidationErrors?.standardErrors?.length || 0) > 0;
433
431
  return validationState.summary.totalErrors === 0
434
432
  && validationState.summary.totalWarnings === 0
435
433
  && !validationState.summary.hasSecurityIssues
436
434
  && !hasApiErrors;
437
- }, [validationState.summary, apiValidationErrors, isFullMode]);
435
+ }, [validationState.summary, apiValidationErrors]);
438
436
 
439
437
  // Effect to validate content when it changes
440
438
  useEffect(() => {
@@ -450,12 +448,11 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
450
448
  }
451
449
  }, []);
452
450
 
453
- const hasApiLiquidErrors = isFullMode ? false : (apiValidationErrors?.liquidErrors?.length || 0) > 0;
454
- const hasApiErrors = hasApiLiquidErrors || (apiValidationErrors?.standardErrors?.length || 0) > 0;
451
+ const hasApiErrors = (apiValidationErrors?.liquidErrors?.length || 0) + (apiValidationErrors?.standardErrors?.length || 0) > 0;
455
452
 
456
453
  const protocolTypes = ['JavaScript Protocol', 'Data URL', 'VBScript Protocol'];
457
- // Client-side Liquid validation errors are blocking (genuine syntax errors) - skip in full mode
458
- const hasClientSideLiquidErrors = isFullMode ? false : (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
454
+ // Client-side Liquid validation errors are blocking (genuine syntax errors)
455
+ const hasClientSideLiquidErrors = (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
459
456
  const hasBlockingErrors = (validationState.sanitizationWarnings || []).some((w) => BLOCKING_ERROR_RULE_IDS.includes(w.rule)) || (validationState.securityIssues || []).some((s) => protocolTypes.includes(s?.type)) || hasApiErrors || hasClientSideLiquidErrors;
460
457
 
461
458
  return {
@@ -76,7 +76,7 @@ const CUSTOM_VALIDATIONS = {
76
76
  * @param {Function} formatMessage - Message formatter function for internationalization
77
77
  * @returns {Object} Validation result with errors and warnings
78
78
  */
79
- export const validateHTML = (html, variant = 'email', formatMessage = defaultMessageFormatter, options = {}) => {
79
+ export const validateHTML = (html, variant = 'email', formatMessage = defaultMessageFormatter) => {
80
80
  if (!html || typeof html !== 'string') {
81
81
  return {
82
82
  isValid: true,
@@ -133,9 +133,7 @@ export const validateHTML = (html, variant = 'email', formatMessage = defaultMes
133
133
  // Always run custom validations and Liquid validation, even if HTMLHint failed
134
134
  // This ensures unsafe protocol detection and other critical validations still run
135
135
  runCustomValidations(html, variant, results, formatMessage);
136
- if (!options.skipLiquidValidation) {
137
- runLiquidValidation(html, variant, results, formatMessage);
138
- }
136
+ runLiquidValidation(html, variant, results, formatMessage);
139
137
 
140
138
  return results;
141
139
  };
@@ -40,6 +40,9 @@ import {
40
40
  makeSelectUpdatePreviewErrors,
41
41
  makeSelectFetchPrefilledValuesError,
42
42
  makeSelectFetchPrefilledValuesErrors,
43
+ makeSelectSenderDetailsByChannel,
44
+ makeSelectWeCrmAccounts,
45
+ makeSelectIsLoadingSenderDetails,
43
46
  } from '../CommonTestAndPreview/selectors';
44
47
 
45
48
  /**
@@ -91,6 +94,10 @@ TestAndPreviewSlidebox.propTypes = {
91
94
  prefilledValues: PropTypes.object,
92
95
  isSendingTestMessage: PropTypes.bool.isRequired,
93
96
  intl: PropTypes.object.isRequired,
97
+ senderDetailsByChannel: PropTypes.object,
98
+ wecrmAccounts: PropTypes.array,
99
+ isLoadingSenderDetails: PropTypes.bool,
100
+ orgUnitId: PropTypes.number,
94
101
  };
95
102
 
96
103
  TestAndPreviewSlidebox.defaultProps = {
@@ -102,6 +109,10 @@ TestAndPreviewSlidebox.defaultProps = {
102
109
  currentTab: 1,
103
110
  messageMetaConfigId: null,
104
111
  prefilledValues: {},
112
+ senderDetailsByChannel: {},
113
+ wecrmAccounts: [],
114
+ isLoadingSenderDetails: false,
115
+ orgUnitId: -1,
105
116
  };
106
117
 
107
118
  const mapStateToProps = createStructuredSelector({
@@ -122,6 +133,9 @@ const mapStateToProps = createStructuredSelector({
122
133
  fetchPrefilledValuesError: makeSelectFetchPrefilledValuesError(),
123
134
  fetchPrefilledValuesErrors: makeSelectFetchPrefilledValuesErrors(),
124
135
  isSendingTestMessage: makeSelectIsSendingTestMessage(),
136
+ senderDetailsByChannel: makeSelectSenderDetailsByChannel(),
137
+ wecrmAccounts: makeSelectWeCrmAccounts(),
138
+ isLoadingSenderDetails: makeSelectIsLoadingSenderDetails(),
125
139
  });
126
140
 
127
141
  function mapDispatchToProps(dispatch) {
@@ -956,8 +956,8 @@ const EmailHTMLEditor = (props) => {
956
956
  }
957
957
  };
958
958
 
959
- // If liquid enabled, validate first using extractTags API (skip in full/standalone mode)
960
- if (isLiquidEnabled && getLiquidTags && !isFullMode) {
959
+ // If liquid enabled, validate first using extractTags API
960
+ if (isLiquidEnabled && getLiquidTags) {
961
961
  // Note: API validation errors are already cleared at the start of handleSave
962
962
  // This ensures fresh validation on every save attempt
963
963
 
@@ -1176,10 +1176,9 @@ describe('EmailHTMLEditor', () => {
1176
1176
  mockGetAllIssues.mockReturnValue([]);
1177
1177
 
1178
1178
  // Set subject and content via component interactions
1179
- // Use isFullMode: false (library mode) to test liquid validation path
1180
1179
  const { rerender } = renderWithIntl({
1181
1180
  isGetFormData: false,
1182
- isFullMode: false,
1181
+ isFullMode: true,
1183
1182
  metaEntities: {
1184
1183
  tags: {
1185
1184
  standard: [{ name: 'customer.name' }],
@@ -1207,7 +1206,7 @@ describe('EmailHTMLEditor', () => {
1207
1206
  <EmailHTMLEditor
1208
1207
  {...defaultProps}
1209
1208
  isGetFormData
1210
- isFullMode={false}
1209
+ isFullMode
1211
1210
  metaEntities={{
1212
1211
  tags: {
1213
1212
  standard: [{ name: 'customer.name' }],
@@ -1232,10 +1231,9 @@ describe('EmailHTMLEditor', () => {
1232
1231
  mockGetAllIssues.mockReturnValue([]);
1233
1232
 
1234
1233
  // Set subject and content via component interactions
1235
- // Use isFullMode: false (library mode) to test liquid validation path
1236
1234
  const { rerender } = renderWithIntl({
1237
1235
  isGetFormData: false,
1238
- isFullMode: false,
1236
+ isFullMode: true,
1239
1237
  isLiquidEnabled: true,
1240
1238
  getLiquidTags,
1241
1239
  });
@@ -1255,7 +1253,7 @@ describe('EmailHTMLEditor', () => {
1255
1253
  await act(async () => {
1256
1254
  rerender(
1257
1255
  <IntlProvider locale="en" messages={{}}>
1258
- <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} />
1256
+ <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} />
1259
1257
  </IntlProvider>
1260
1258
  );
1261
1259
  });
@@ -1283,10 +1281,9 @@ describe('EmailHTMLEditor', () => {
1283
1281
  mockGetAllIssues.mockReturnValue([]);
1284
1282
 
1285
1283
  // Set subject and content via component interactions
1286
- // Use isFullMode: false (library mode) to test liquid validation path
1287
1284
  const { rerender } = renderWithIntl({
1288
1285
  isGetFormData: false,
1289
- isFullMode: false,
1286
+ isFullMode: true,
1290
1287
  isLiquidEnabled: true,
1291
1288
  getLiquidTags,
1292
1289
  showLiquidErrorInFooter,
@@ -1308,7 +1305,7 @@ describe('EmailHTMLEditor', () => {
1308
1305
  await act(async () => {
1309
1306
  rerender(
1310
1307
  <IntlProvider locale="en" messages={{}}>
1311
- <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} showLiquidErrorInFooter={showLiquidErrorInFooter} onValidationFail={onValidationFail} />
1308
+ <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} showLiquidErrorInFooter={showLiquidErrorInFooter} onValidationFail={onValidationFail} />
1312
1309
  </IntlProvider>
1313
1310
  );
1314
1311
  });
@@ -1328,7 +1325,13 @@ describe('EmailHTMLEditor', () => {
1328
1325
  return Promise.resolve(true);
1329
1326
  });
1330
1327
 
1331
- const getFormdata = jest.fn();
1328
+ const emailActions = {
1329
+ ...defaultProps.emailActions,
1330
+ transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
1331
+ createTemplate: jest.fn((obj, callback) => {
1332
+ callback({ templateId: { _id: '123', versions: {} } });
1333
+ }),
1334
+ };
1332
1335
  const getLiquidTags = jest.fn((content, callback) => {
1333
1336
  callback({ askAiraResponse: { data: [] }, isError: false });
1334
1337
  });
@@ -1336,12 +1339,12 @@ describe('EmailHTMLEditor', () => {
1336
1339
  mockGetAllIssues.mockReturnValue([]);
1337
1340
 
1338
1341
  // Set subject and content via component interactions
1339
- // Use isFullMode: false (library mode) to test liquid validation path
1340
1342
  const { rerender } = renderWithIntl({
1341
1343
  isGetFormData: false,
1342
- isFullMode: false,
1344
+ isFullMode: true,
1345
+ isLiquidEnabled: true,
1343
1346
  getLiquidTags,
1344
- getFormdata,
1347
+ emailActions,
1345
1348
  templateName: 'New Template',
1346
1349
  });
1347
1350
  const input = screen.getByTestId('subject-input');
@@ -1360,7 +1363,7 @@ describe('EmailHTMLEditor', () => {
1360
1363
  await act(async () => {
1361
1364
  rerender(
1362
1365
  <IntlProvider locale="en" messages={{}}>
1363
- <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} getFormdata={getFormdata} templateName="New Template" />
1366
+ <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} emailActions={emailActions} templateName="New Template" />
1364
1367
  </IntlProvider>
1365
1368
  );
1366
1369
  });
@@ -1369,9 +1372,8 @@ describe('EmailHTMLEditor', () => {
1369
1372
  expect(validateLiquidTemplateContent).toHaveBeenCalled();
1370
1373
  }, { timeout: 5000 });
1371
1374
 
1372
- // In library mode (isFullMode=false), save proceeds via getFormdata
1373
1375
  await waitFor(() => {
1374
- expect(getFormdata).toHaveBeenCalled();
1376
+ expect(emailActions.createTemplate).toHaveBeenCalled();
1375
1377
  }, { timeout: 5000 });
1376
1378
  });
1377
1379
 
@@ -2709,98 +2711,4 @@ describe('EmailHTMLEditor', () => {
2709
2711
  expect(screen.getByTestId('html-editor')).toBeInTheDocument();
2710
2712
  });
2711
2713
  });
2712
-
2713
- describe('isFullMode - skip liquid validation on save', () => {
2714
- it('skips liquid validation when isFullMode is true', async () => {
2715
- validateLiquidTemplateContent.mockClear();
2716
- const getLiquidTags = jest.fn((content, callback) => {
2717
- callback({ askAiraResponse: { data: [] }, isError: false });
2718
- });
2719
- const getFormdata = jest.fn();
2720
- // Ensure no HTML/Label/Liquid errors from HtmlEditor
2721
- mockGetAllIssues.mockReturnValue([]);
2722
-
2723
- const { rerender } = renderWithIntl({
2724
- isGetFormData: false,
2725
- isFullMode: true,
2726
- isLiquidEnabled: true,
2727
- getLiquidTags,
2728
- getFormdata,
2729
- });
2730
- const input = screen.getByTestId('subject-input');
2731
- await act(async () => {
2732
- fireEvent.change(input, { target: { value: 'Valid Subject' } });
2733
- });
2734
- const changeButton = screen.getByTestId('trigger-content-change');
2735
- await act(async () => {
2736
- fireEvent.click(changeButton);
2737
- });
2738
- await act(async () => {
2739
- await new Promise((resolve) => setTimeout(resolve, 100));
2740
- });
2741
- // Trigger save
2742
- await act(async () => {
2743
- rerender(
2744
- <IntlProvider locale="en" messages={{}}>
2745
- <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} getFormdata={getFormdata} />
2746
- </IntlProvider>
2747
- );
2748
- });
2749
-
2750
- // In full mode, liquid validation should be skipped entirely
2751
- await act(async () => {
2752
- await new Promise((resolve) => setTimeout(resolve, 200));
2753
- });
2754
- expect(validateLiquidTemplateContent).not.toHaveBeenCalled();
2755
- });
2756
-
2757
- it('proceeds directly to save without liquid validation in full mode', async () => {
2758
- validateLiquidTemplateContent.mockClear();
2759
- const getLiquidTags = jest.fn((content, callback) => {
2760
- callback({ askAiraResponse: { data: [] }, isError: false });
2761
- });
2762
- const emailActions = {
2763
- ...defaultProps.emailActions,
2764
- transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
2765
- createTemplate: jest.fn((obj, callback) => {
2766
- callback({ templateId: { _id: '123', versions: {} } });
2767
- }),
2768
- };
2769
- // Ensure no HTML/Label/Liquid errors from HtmlEditor
2770
- mockGetAllIssues.mockReturnValue([]);
2771
-
2772
- const { rerender } = renderWithIntl({
2773
- isGetFormData: false,
2774
- isFullMode: true,
2775
- getLiquidTags,
2776
- emailActions,
2777
- templateName: 'New Template',
2778
- });
2779
- const input = screen.getByTestId('subject-input');
2780
- await act(async () => {
2781
- fireEvent.change(input, { target: { value: 'Valid Subject' } });
2782
- });
2783
- const changeButton = screen.getByTestId('trigger-content-change');
2784
- await act(async () => {
2785
- fireEvent.click(changeButton);
2786
- });
2787
- await act(async () => {
2788
- await new Promise((resolve) => setTimeout(resolve, 100));
2789
- });
2790
- // Trigger save
2791
- await act(async () => {
2792
- rerender(
2793
- <IntlProvider locale="en" messages={{}}>
2794
- <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} emailActions={emailActions} templateName="New Template" />
2795
- </IntlProvider>
2796
- );
2797
- });
2798
-
2799
- // Should skip liquid validation and proceed to save directly
2800
- await waitFor(() => {
2801
- expect(emailActions.createTemplate).toHaveBeenCalled();
2802
- }, { timeout: 5000 });
2803
- expect(validateLiquidTemplateContent).not.toHaveBeenCalled();
2804
- });
2805
- });
2806
2714
  });