@capillarytech/creatives-library 8.0.279 → 8.0.280

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.279",
4
+ "version": "8.0.280",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -201,7 +201,6 @@ export const validateTags = ({
201
201
  unsupportedTags: [],
202
202
  isBraceError: false,
203
203
  };
204
- // Mandatory-tags check: only when we have a tags list and are in library mode
205
204
  if (tags && tags.length && !isFullMode) {
206
205
  lodashForEach(tags, ({
207
206
  definition: {
@@ -218,9 +217,6 @@ export const validateTags = ({
218
217
  }
219
218
  });
220
219
  });
221
- }
222
- // In library mode, always scan content for {{...}} and flag unsupported tags (even when tags list is empty)
223
- if (!isFullMode && content) {
224
220
  const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
225
221
  let match = regex.exec(content);
226
222
  while (match !== null) {
@@ -228,8 +224,8 @@ export const validateTags = ({
228
224
  const tagIndex = match?.index;
229
225
  match = regex.exec(content);
230
226
  let ifSupported = false;
231
- lodashForEach(tags || [], (tag) => {
232
- if (tag?.definition?.value === tagValue) {
227
+ lodashForEach(tags, (tag) => {
228
+ if (tag.definition.value === tagValue) {
233
229
  ifSupported = true;
234
230
  }
235
231
  });
@@ -104,7 +104,7 @@ describe("validateTags", () => {
104
104
  });
105
105
 
106
106
  expect(result.valid).toEqual(true);
107
- expect(result.unsupportedTags).toEqual(["tag1", "tag2", "tag3"]);
107
+ expect(result.unsupportedTags).toEqual([]);
108
108
  expect(result.isBraceError).toEqual(false);
109
109
  });
110
110
 
@@ -1247,11 +1247,10 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1247
1247
  },
1248
1248
  }),
1249
1249
  () => {
1250
+ // Callback after the state is updated
1250
1251
  this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
1251
1252
  }
1252
1253
  );
1253
- // Show toast for liquid flow too so user sees error (scenario 3)
1254
- this.openNotificationWithIcon('error', errorString, "email-validation-error");
1255
1254
  } else {
1256
1255
  this.openNotificationWithIcon('error', errorString, "email-validation-error");
1257
1256
  }
@@ -1519,8 +1518,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1519
1518
  response.isBraceError = false;
1520
1519
  response.isContentEmpty = false;
1521
1520
  const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content ;
1522
- // Run tag validation (missing + unsupported) for library mode OR for email channel (scenario 4: full mode email with CK)
1523
- if(tags && tags.length && (!isFullMode || isEmail)) {
1521
+ if(tags && tags.length && !isFullMode) {
1524
1522
  _.forEach(tags, (tag) => {
1525
1523
  _.forEach(tag.definition.supportedModules, (module) => {
1526
1524
  if (module.mandatory && (currentModule === module.context)) {
@@ -1583,8 +1581,9 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1583
1581
  });
1584
1582
 
1585
1583
  ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
1586
- // Only add to unsupportedTags if not inside a {% ... %} block (scenario 3: liquid orgs also get unsupported-tag errors)
1587
- if (!ifSupported && !isInsideLiquidBlock(content, tagIndex)) {
1584
+ // Only add to unsupportedTags if not inside a {% ... %} block and not in liquid flow
1585
+ // Tags inside {% %} blocks can contain any dynamic tags and should not be validated
1586
+ if (!ifSupported && !this.liquidFlow() && !isInsideLiquidBlock(content, tagIndex)) {
1588
1587
  response.unsupportedTags.push(tagValue);
1589
1588
  response.valid = false;
1590
1589
  }
@@ -108,24 +108,6 @@ const EmailHTMLEditor = (props) => {
108
108
  standardErrors: [],
109
109
  });
110
110
 
111
- // Merge tag validation errors (unsupported/missing) into apiValidationErrors so they show in ValidationErrorDisplay
112
- const mergedApiValidationErrors = useMemo(() => {
113
- const tagMessages = [];
114
- if (tagValidationError?.unsupportedTags?.length) {
115
- tagMessages.push(`Unsupported tags are: ${tagValidationError.unsupportedTags.join(', ')}`);
116
- }
117
- if (tagValidationError?.missingTags?.length && !isEmailUnsubscribeTagMandatory()) {
118
- tagMessages.push(`Missing tags are: ${tagValidationError.missingTags.join(', ')}`);
119
- }
120
- if (tagMessages.length === 0) {
121
- return apiValidationErrors;
122
- }
123
- return {
124
- liquidErrors: apiValidationErrors?.liquidErrors || [],
125
- standardErrors: [...(apiValidationErrors?.standardErrors || []), ...tagMessages],
126
- };
127
- }, [apiValidationErrors, tagValidationError]);
128
-
129
111
  // Refs for tracking initialization and previous values
130
112
  const contentInitializedRef = useRef(false);
131
113
  const subjectInitializedRef = useRef(false);
@@ -497,7 +479,6 @@ const EmailHTMLEditor = (props) => {
497
479
  location,
498
480
  tagModule: getDefaultTags,
499
481
  eventContextTags,
500
- isFullMode,
501
482
  });
502
483
 
503
484
  if (!validationResult.valid) {
@@ -710,7 +691,7 @@ const EmailHTMLEditor = (props) => {
710
691
  // 2. Validate Unsubscribe Tag when feature is OFF (when flag is false we require unsubscribe)
711
692
  // When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is true: do NOT validate for unsubscribe (aligned with FormBuilder).
712
693
  // When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false: validate and require unsubscribe tag.
713
- if (!isFullMode && !isEmailUnsubscribeTagMandatory() && moduleType === OUTBOUND) {
694
+ if (!isEmailUnsubscribeTagMandatory() && moduleType === OUTBOUND) {
714
695
  // Check if content contains unsubscribe tag (either {{unsubscribe}} or {{unsubscribe(#...)})
715
696
  const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
716
697
  const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
@@ -719,13 +700,11 @@ const EmailHTMLEditor = (props) => {
719
700
  // Show error notification
720
701
  const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
721
702
  const errorMessage = `${missingTagsMsg} unsubscribe`;
722
- setTimeout(() => {
723
- CapNotification.error({
724
- message: 'ERROR ! ! !',
725
- description: errorMessage,
726
- duration: 5,
727
- });
728
- }, 0);
703
+ CapNotification.error({
704
+ message: 'ERROR ! ! !',
705
+ description: errorMessage,
706
+ duration: 5,
707
+ });
729
708
 
730
709
  // Reset parent state so next click is detected as a change
731
710
  if (onValidationFail) {
@@ -739,9 +718,7 @@ const EmailHTMLEditor = (props) => {
739
718
  // 3. Validate Content Tags
740
719
  // For NON-liquid orgs: BLOCKING validation (matches CK/BEE behavior)
741
720
  // For liquid orgs: Non-blocking (extractTags API will validate)
742
- // In library mode, always validate when there is content (even if tags list is empty)
743
- const shouldValidateTags = (tags.length > 0 || !isEmpty(injectedTags)) || (!isFullMode && !!htmlContent);
744
- if (shouldValidateTags) {
721
+ if (tags.length > 0 || !isEmpty(injectedTags)) {
745
722
  const validationResult = validateTags({
746
723
  content: htmlContent,
747
724
  tagsParam: tags,
@@ -749,12 +726,12 @@ const EmailHTMLEditor = (props) => {
749
726
  location,
750
727
  tagModule: getDefaultTags,
751
728
  eventContextTags,
752
- isFullMode,
753
729
  });
754
730
 
755
731
  const hasUnsupportedTags = validationResult?.unsupportedTags?.length > 0;
756
- if (!validationResult?.valid || (hasUnsupportedTags && !isFullMode)) {
732
+ if (!validationResult?.valid || hasUnsupportedTags) {
757
733
  setTagValidationError(validationResult);
734
+
758
735
  // IMPORTANT: For non-liquid orgs, block save (like CK/BEE editor)
759
736
  // For liquid orgs, continue (extractTags API will validate)
760
737
  if (!isLiquidEnabled) {
@@ -1180,7 +1157,7 @@ const EmailHTMLEditor = (props) => {
1180
1157
  isFullMode={isFullMode}
1181
1158
  onErrorAcknowledged={handleErrorAcknowledged}
1182
1159
  onValidationChange={handleValidationChange}
1183
- apiValidationErrors={mergedApiValidationErrors}
1160
+ apiValidationErrors={apiValidationErrors}
1184
1161
  />
1185
1162
  </CapColumn>
1186
1163
  </CapRow>
@@ -1016,6 +1016,36 @@ describe('EmailHTMLEditor', () => {
1016
1016
  }, { timeout: 3000 });
1017
1017
  });
1018
1018
 
1019
+ it('blocks save when unsubscribe validation is on (flag false) and tag is missing', async () => {
1020
+ // When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false we validate and require unsubscribe
1021
+ isEmailUnsubscribeTagMandatory.mockReturnValue(false);
1022
+ const onValidationFail = jest.fn();
1023
+ const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
1024
+
1025
+ // Set subject via input and content via HTMLEditor mock
1026
+ const { rerender } = renderWithIntl({
1027
+ onValidationFail,
1028
+ isGetFormData: false,
1029
+ moduleType: 'OUTBOUND',
1030
+ });
1031
+ const input = screen.getByTestId('subject-input');
1032
+ fireEvent.change(input, { target: { value: 'Valid Subject' } });
1033
+ // Trigger content change to set htmlContent
1034
+ const changeButton = screen.getByTestId('trigger-content-change');
1035
+ fireEvent.click(changeButton);
1036
+ // Now trigger save
1037
+ rerender(
1038
+ <IntlProvider locale="en" messages={{}}>
1039
+ <EmailHTMLEditor {...defaultProps} onValidationFail={onValidationFail} isGetFormData moduleType="OUTBOUND" />
1040
+ </IntlProvider>
1041
+ );
1042
+
1043
+ await waitFor(() => {
1044
+ expect(CapNotification.error).toHaveBeenCalled();
1045
+ expect(onValidationFail).toHaveBeenCalled();
1046
+ }, { timeout: 3000 });
1047
+ });
1048
+
1019
1049
  it('allows save when unsubscribe validation is on and tag is present', () => {
1020
1050
  isEmailUnsubscribeTagMandatory.mockReturnValue(false);
1021
1051
  renderWithIntl({
@@ -73,7 +73,7 @@ export const Advertisement = (props) => {
73
73
  const FbAdFooter = styled.div`
74
74
  background-color: ${CAP_WHITE};
75
75
  position: fixed;
76
- bottom: 0;
76
+ bottom: 20px;
77
77
  width: 100%;
78
78
  margin-left: -32px;
79
79
  padding: ${CAP_SPACE_32} ${CAP_SPACE_24};
@@ -1155,7 +1155,6 @@ export const InApp = (props) => {
1155
1155
  location,
1156
1156
  tagModule: getDefaultTags,
1157
1157
  eventContextTags: metaEntities?.eventContextTags || [],
1158
- isFullMode,
1159
1158
  }) || {};
1160
1159
 
1161
1160
  if (validationResponse?.unsupportedTags?.length > 0) {
@@ -1183,7 +1182,6 @@ export const InApp = (props) => {
1183
1182
  location,
1184
1183
  tagModule: getDefaultTags,
1185
1184
  eventContextTags: metaEntities?.eventContextTags || [],
1186
- isFullMode,
1187
1185
  }) || {};
1188
1186
 
1189
1187
  if (validationResponse?.unsupportedTags?.length > 0) {
@@ -878,7 +878,6 @@ export const InappAdvanced = (props) => {
878
878
  location,
879
879
  tagModule: getDefaultTags,
880
880
  eventContextTags: metaEntities?.eventContextTags || [],
881
- isFullMode,
882
881
  }) || {};
883
882
 
884
883
  if (validationResponse?.unsupportedTags?.length > 0) {
@@ -906,7 +905,6 @@ export const InappAdvanced = (props) => {
906
905
  location,
907
906
  tagModule: getDefaultTags,
908
907
  eventContextTags: metaEntities?.eventContextTags || [],
909
- isFullMode,
910
908
  }) || {};
911
909
 
912
910
  if (validationResponse?.unsupportedTags?.length > 0) {
@@ -40,7 +40,6 @@
40
40
  .create-msg,
41
41
  .cancel-msg {
42
42
  position: fixed;
43
- bottom: 20px;
44
43
  }
45
44
  .cancel-msg {
46
45
  margin-left: 100px;
@@ -38,7 +38,7 @@ export default css`
38
38
  .action-section {
39
39
  background-color: ${CAP_WHITE};
40
40
  position: fixed;
41
- bottom: 0;
41
+ bottom: 20px;
42
42
  width: 100%;
43
43
  margin-left: -32px;
44
44
  padding: ${CAP_SPACE_32} ${CAP_SPACE_24};
@@ -98,5 +98,5 @@
98
98
  }
99
99
 
100
100
  .create-dlt-msg {
101
- margin-left: 170px;
101
+ margin-left: 120px;
102
102
  }
@@ -106,6 +106,7 @@ export const SmsTraiEdit = (props) => {
106
106
  padding: ${CAP_SPACE_32} ${CAP_SPACE_24};
107
107
  position: fixed;
108
108
  bottom: 2rem;
109
+ margin-left: -2rem;
109
110
  .ant-btn {
110
111
  margin-right: ${CAP_SPACE_16};
111
112
  }
@@ -687,24 +688,17 @@ export const SmsTraiEdit = (props) => {
687
688
  <CapButton
688
689
  onClick={handleTestAndPreview}
689
690
  type="secondary"
690
- className="create-msg"
691
+ className="create-msg create-dlt-msg"
691
692
  >
692
693
  <FormattedMessage {...messages.testAndPreviewButtonLabel} />
693
694
  </CapButton>
694
695
  <CapButton
695
696
  onClick={isLiquidSupportFeatureEnabled ? onSubmitWrapper : onDoneCallback}
696
- className="create-msg create-dlt-msg"
697
+ className="create-msg"
697
698
  disabled={isTagValidationError || (isLiquidSupportFeatureEnabled && !isObject(metaEntities?.tagLookupMap))}
698
699
  >
699
700
  <FormattedMessage {...messages.saveButtonLabel} />
700
701
  </CapButton>
701
- <CapButton
702
- onClick={handleClose}
703
- className="cancel-dlt-msg"
704
- type="secondary"
705
- >
706
- <FormattedMessage {...messages.cancelButtonLabel} />
707
- </CapButton>
708
702
  </SMSTraiFooter>
709
703
  <TestAndPreviewSlidebox
710
704
  show={showTestAndPreviewSlidebox}