@capillarytech/creatives-library 8.0.292-alpha.0 → 8.0.292-alpha.1

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.292-alpha.0",
4
+ "version": "8.0.292-alpha.1",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -69,6 +69,23 @@ import {
69
69
  import { MANUAL_CAROUSEL } from '../MobilePushNew/constants';
70
70
  import { BIG_HTML } from '../InApp/constants';
71
71
 
72
+ /**
73
+ * Returns true if value is "deep empty": no errors present.
74
+ * - null/undefined: empty
75
+ * - string: empty if length === 0
76
+ * - array: empty if length === 0
77
+ * - plain object (e.g. { android: [], ios: [], generic: [] }): empty only if every value is deep-empty
78
+ */
79
+ function isDeepEmpty(value) {
80
+ if (value == null) return true;
81
+ if (typeof value === 'string') return value.length === 0;
82
+ if (Array.isArray(value)) return value.length === 0;
83
+ if (typeof value === 'object') {
84
+ return Object.values(value).every(isDeepEmpty);
85
+ }
86
+ return false;
87
+ }
88
+
72
89
  const classPrefix = 'add-creatives-section';
73
90
  const CREATIVES_CONTAINER = 'creativesContainer';
74
91
 
@@ -1779,8 +1796,8 @@ export class Creatives extends React.Component {
1779
1796
  showLiquidErrorInFooter = (errorMessagesFromFormBuilder, currentFormBuilderTab) => {
1780
1797
  const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
1781
1798
  const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
1782
- const hasLiquid = Array.isArray(liquidMsgs) ? liquidMsgs.length > 0 : !isEmpty(liquidMsgs);
1783
- const hasStandard = Array.isArray(standardMsgs) ? standardMsgs.length > 0 : !isEmpty(standardMsgs);
1799
+ const hasLiquid = !isDeepEmpty(liquidMsgs);
1800
+ const hasStandard = !isDeepEmpty(standardMsgs);
1784
1801
  const isLiquidValidationError = hasLiquid || hasStandard;
1785
1802
  // Don't overwrite existing liquid error with empty only for Mobile Push OLD (FormBuilder/clear calls empty there); SMS/others clear on input change
1786
1803
  const isMobilePush = this.state.currentChannel?.toUpperCase() === constants.MOBILE_PUSH;
@@ -50,7 +50,7 @@ import {
50
50
  LAYOUT_RADIO_OPTIONS,
51
51
  IOS_CAPITAL,
52
52
  } from "./constants";
53
- import { INAPP, SMS } from "../CreativesContainer/constants";
53
+ import { GENERIC, INAPP, SMS } from "../CreativesContainer/constants";
54
54
  import {
55
55
  ALL, TAG, EMBEDDED, DEFAULT, FULL, LIBRARY,
56
56
  } from "../Whatsapp/constants";
@@ -1079,10 +1079,36 @@ export const InApp = (props) => {
1079
1079
  // Validation middleware for tag validation (both liquid and non-liquid flow)
1080
1080
  // Liquid validation (extractTags) runs only in library mode
1081
1081
  const validationMiddleWare = async () => {
1082
+ // Normalize validator bucket keys to component state keys (ANDROID, IOS_CAPITAL, GENERIC)
1083
+ // so we don't merge e.g. 'android'/'ios'/'generic' with ANDROID/IOS/GENERIC and get duplicate/stale keys
1084
+ const normalizeErrorBuckets = (errors) => {
1085
+ const normalized = {
1086
+ [ANDROID]: [],
1087
+ [IOS_CAPITAL]: [],
1088
+ [GENERIC]: [],
1089
+ };
1090
+ if (!errors || typeof errors !== 'object') return normalized;
1091
+ const keyMap = {
1092
+ ANDROID,
1093
+ android: ANDROID,
1094
+ [IOS_CAPITAL]: IOS_CAPITAL,
1095
+ [IOS]: IOS_CAPITAL,
1096
+ ios: IOS_CAPITAL,
1097
+ GENERIC,
1098
+ generic: GENERIC,
1099
+ };
1100
+ for (const [key, value] of Object.entries(errors)) {
1101
+ const targetKey = keyMap[key];
1102
+ if (targetKey != null && Array.isArray(value)) {
1103
+ normalized[targetKey] = value;
1104
+ }
1105
+ }
1106
+ return normalized;
1107
+ };
1082
1108
  const onError = ({ standardErrors, liquidErrors }) => {
1083
1109
  setErrorMessage((prev) => ({
1084
- STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...standardErrors },
1085
- LIQUID_ERROR_MSG: { ...prev.LIQUID_ERROR_MSG, ...liquidErrors },
1110
+ STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...normalizeErrorBuckets(standardErrors) },
1111
+ LIQUID_ERROR_MSG: { ...prev.LIQUID_ERROR_MSG, ...normalizeErrorBuckets(liquidErrors) },
1086
1112
  }));
1087
1113
  };
1088
1114
  const onSuccess = () => {
@@ -817,7 +817,7 @@ export const InappAdvanced = (props) => {
817
817
 
818
818
  // Liquid validation (extractTags) only in library mode
819
819
  if (!isFullMode) {
820
- validateInAppContent(payload, {
820
+ await validateInAppContent(payload, {
821
821
  currentTab: panes === ANDROID ? 1 : 2,
822
822
  onError,
823
823
  onSuccess,
@@ -837,7 +837,7 @@ export const InappAdvanced = (props) => {
837
837
  singleTab: getSingleTab(accountData),
838
838
  });
839
839
  } else {
840
- onSuccess();
840
+ await onSuccess();
841
841
  }
842
842
  };
843
843
 
@@ -119,7 +119,10 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
119
119
  { STANDARD_ERROR_MSG, LIQUID_ERROR_MSG },
120
120
  this.state.currentTab
121
121
  );
122
- nextProps.onValidationFail();
122
+ // Only trigger onValidationFail when there are actual errors; skip when helper called onError with empty arrays (reset case)
123
+ if (STANDARD_ERROR_MSG.length > 0 || LIQUID_ERROR_MSG.length > 0) {
124
+ nextProps.onValidationFail();
125
+ }
123
126
  },
124
127
  onSuccess: () => {
125
128
  nextProps.getFormLibraryData(this.getFormData());
@@ -154,7 +154,11 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
154
154
  { STANDARD_ERROR_MSG, LIQUID_ERROR_MSG },
155
155
  this.state.currentTab
156
156
  );
157
- nextProps.onValidationFail();
157
+ // Only treat as validation failure when there are actual errors; skip when helper called onError with empty reset payload
158
+ const hasErrors = STANDARD_ERROR_MSG.length > 0 || LIQUID_ERROR_MSG.length > 0;
159
+ if (hasErrors && nextProps.onValidationFail) {
160
+ nextProps.onValidationFail();
161
+ }
158
162
  },
159
163
  onSuccess: () => {
160
164
  nextProps.getFormLibraryData(this.getFormData());
@@ -2329,7 +2333,7 @@ Edit.propTypes = {
2329
2333
  getFormLibraryData: PropTypes.func,
2330
2334
  isGetFormData: PropTypes.bool,
2331
2335
  Create: PropTypes.object,
2332
- onValidationFail: PropTypes.bool,
2336
+ onValidationFail: PropTypes.func,
2333
2337
  onPreviewContentClicked: PropTypes.func,
2334
2338
  onTestContentClicked: PropTypes.func,
2335
2339
  creativesMode: PropTypes.string,
@@ -80,12 +80,6 @@ export const validateMessageContent = (value, formatMessage, messages, validatio
80
80
  isFullMode,
81
81
  }) || {};
82
82
 
83
- if (validationResponse?.unsupportedTags?.length) {
84
- return formatMessage(globalMessages.unsupportedTagsValidationError, {
85
- unsupportedTags: validationResponse.unsupportedTags.join(', '),
86
- });
87
- }
88
-
89
83
  if (validationResponse?.isBraceError) {
90
84
  return formatMessage(globalMessages.unbalanacedCurlyBraces);
91
85
  }
@@ -317,21 +317,6 @@ describe('validation', () => {
317
317
  expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.personalizationTokensErrorMessage);
318
318
  });
319
319
 
320
- it('should return unsupported tags error when validateTags returns unsupportedTags', () => {
321
- validateTags.mockReturnValue({ unsupportedTags: ['invalidTag', 'otherTag'] });
322
- const result = validateMessageContent(
323
- 'Hello {{invalidTag}}',
324
- mockFormatMessage,
325
- mockMessages,
326
- mockValidationConfig
327
- );
328
- expect(result).toBe('Unsupported tags: invalidTag, otherTag');
329
- expect(mockFormatMessage).toHaveBeenCalledWith(
330
- globalMessages.unsupportedTagsValidationError,
331
- { unsupportedTags: 'invalidTag, otherTag' }
332
- );
333
- });
334
-
335
320
  it('should pass isFullMode to validateTags when provided', () => {
336
321
  validateTags.mockReturnValue({});
337
322
  validateMessageContent('Valid message', mockFormatMessage, mockMessages, mockValidationConfig, true);