@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 +1 -1
- package/v2Containers/CreativesContainer/index.js +19 -2
- package/v2Containers/InApp/index.js +29 -3
- package/v2Containers/InappAdvance/index.js +2 -2
- package/v2Containers/MobilePush/Create/index.js +4 -1
- package/v2Containers/MobilePush/Edit/index.js +6 -2
- package/v2Containers/WebPush/Create/utils/validation.js +0 -6
- package/v2Containers/WebPush/Create/utils/validation.test.js +0 -15
package/package.json
CHANGED
|
@@ -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 =
|
|
1783
|
-
const hasStandard =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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);
|