@capillarytech/creatives-library 8.0.310 → 8.0.311
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/constants/unified.js +5 -1
- package/initialState.js +0 -2
- package/package.json +1 -1
- package/utils/common.js +5 -8
- package/utils/commonUtils.js +36 -93
- package/utils/tagValidations.js +83 -223
- package/utils/tests/commonUtil.test.js +147 -124
- package/utils/tests/tagValidations.test.js +441 -358
- package/v2Components/ErrorInfoNote/index.js +2 -5
- package/v2Components/FormBuilder/index.js +137 -203
- package/v2Components/FormBuilder/messages.js +0 -8
- package/v2Components/HtmlEditor/HTMLEditor.js +0 -5
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -15
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +1 -2
- package/v2Containers/Cap/mockData.js +0 -14
- package/v2Containers/Cap/reducer.js +3 -55
- package/v2Containers/Cap/tests/reducer.test.js +0 -102
- package/v2Containers/CreativesContainer/SlideBoxContent.js +5 -1
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -5
- package/v2Containers/CreativesContainer/constants.js +6 -0
- package/v2Containers/CreativesContainer/index.js +47 -7
- package/v2Containers/Email/index.js +1 -5
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +23 -70
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -120
- package/v2Containers/FTP/index.js +2 -51
- package/v2Containers/FTP/messages.js +0 -4
- package/v2Containers/InApp/index.js +35 -107
- package/v2Containers/InApp/tests/index.test.js +17 -6
- package/v2Containers/InappAdvance/index.js +4 -112
- package/v2Containers/InappAdvance/tests/index.test.js +2 -0
- package/v2Containers/Line/Container/Text/index.js +0 -1
- package/v2Containers/MobilePush/Create/index.js +59 -19
- package/v2Containers/MobilePush/Edit/index.js +48 -20
- package/v2Containers/MobilePushNew/index.js +12 -32
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +12 -37
- package/v2Containers/Sms/Create/index.js +39 -3
- package/v2Containers/Sms/Create/messages.js +4 -0
- package/v2Containers/Sms/Edit/index.js +35 -3
- package/v2Containers/Sms/commonMethods.js +3 -6
- package/v2Containers/Sms/tests/commonMethods.test.js +122 -0
- package/v2Containers/SmsTrai/Edit/index.js +11 -47
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/SmsWrapper/index.js +2 -0
- package/v2Containers/TemplatesV2/index.js +28 -13
- package/v2Containers/Viber/index.js +0 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +17 -8
- package/v2Containers/WebPush/Create/utils/validation.test.js +44 -24
- package/v2Containers/Whatsapp/index.js +9 -17
- package/v2Containers/Zalo/index.js +3 -11
|
@@ -77,7 +77,6 @@ import { getContent } from "../MobilePush/commonMethods";
|
|
|
77
77
|
import { getMessageObject } from "../../utils/messageUtils";
|
|
78
78
|
import { gtmPush } from "../../utils/gtmTrackers";
|
|
79
79
|
import mobilePushReducer from "./reducer";
|
|
80
|
-
import { hasLiquidSupportFeature } from "../../utils/common";
|
|
81
80
|
import formBuilderMessages from "../../v2Components/FormBuilder/messages";
|
|
82
81
|
import { validateMobilePushContent } from "../../utils/commonUtils";
|
|
83
82
|
import { getSingleTab } from "../InApp/utils";
|
|
@@ -803,10 +802,9 @@ const MobilePushNew = ({
|
|
|
803
802
|
(value) => {
|
|
804
803
|
let errorTemplateDescMessage = "";
|
|
805
804
|
|
|
806
|
-
const {
|
|
805
|
+
const { isBraceError } = validateTags({
|
|
807
806
|
content: value,
|
|
808
807
|
tagsParam: tags,
|
|
809
|
-
injectedTagsParams: injectedTags,
|
|
810
808
|
location,
|
|
811
809
|
tagModule: getDefaultTags,
|
|
812
810
|
isFullMode,
|
|
@@ -816,14 +814,6 @@ const MobilePushNew = ({
|
|
|
816
814
|
messages.emptyTemplateDescErrorMessage
|
|
817
815
|
);
|
|
818
816
|
}
|
|
819
|
-
if (unsupportedTags?.length > 0) {
|
|
820
|
-
errorTemplateDescMessage = formatMessage(
|
|
821
|
-
globalMessages.unsupportedTagsValidationError,
|
|
822
|
-
{
|
|
823
|
-
unsupportedTags,
|
|
824
|
-
}
|
|
825
|
-
);
|
|
826
|
-
}
|
|
827
817
|
if (isBraceError) {
|
|
828
818
|
errorTemplateDescMessage = formatMessage(
|
|
829
819
|
globalMessages.unbalanacedCurlyBraces
|
|
@@ -2665,20 +2655,6 @@ const MobilePushNew = ({
|
|
|
2665
2655
|
getLiquidTags: globalActionsProps.getLiquidTags,
|
|
2666
2656
|
formatMessage,
|
|
2667
2657
|
messages: formBuilderMessages,
|
|
2668
|
-
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
2669
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
2670
|
-
isLiquidFlow: hasLiquidSupportFeature(),
|
|
2671
|
-
forwardedTags: {},
|
|
2672
|
-
skipTags: (tag) => {
|
|
2673
|
-
const skipRegexes = [
|
|
2674
|
-
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
2675
|
-
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
2676
|
-
/Link_to_[a-zA-z]/,
|
|
2677
|
-
/SURVEY.*\.TOKEN/,
|
|
2678
|
-
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
2679
|
-
];
|
|
2680
|
-
return skipRegexes.some((regex) => regex.test(tag));
|
|
2681
|
-
},
|
|
2682
2658
|
singleTab: getSingleTab(accountData),
|
|
2683
2659
|
});
|
|
2684
2660
|
}, [
|
|
@@ -2687,13 +2663,10 @@ const MobilePushNew = ({
|
|
|
2687
2663
|
activeTab,
|
|
2688
2664
|
globalActionsProps,
|
|
2689
2665
|
formatMessage,
|
|
2690
|
-
metaEntities,
|
|
2691
2666
|
accountData,
|
|
2692
2667
|
isFullMode,
|
|
2693
2668
|
]);
|
|
2694
2669
|
|
|
2695
|
-
const isLiquidFlow = hasLiquidSupportFeature();
|
|
2696
|
-
|
|
2697
2670
|
useEffect(() => {
|
|
2698
2671
|
// Always map to { label } for both platforms
|
|
2699
2672
|
const newButtons = Array.isArray(ctaData)
|
|
@@ -2940,16 +2913,22 @@ const MobilePushNew = ({
|
|
|
2940
2913
|
setShowTestAndPreviewSlidebox(false);
|
|
2941
2914
|
}, []);
|
|
2942
2915
|
|
|
2943
|
-
// Add useEffect to handle isGetFormData prop changes
|
|
2916
|
+
// Add useEffect to handle isGetFormData prop changes (e.g. Done clicked in footer)
|
|
2917
|
+
// In library mode: run liquid validation (extractTags) first; on success liquidMiddleWare calls handleSave
|
|
2918
|
+
// In full mode: call handleSave directly
|
|
2944
2919
|
useEffect(() => {
|
|
2945
2920
|
if (isGetFormData) {
|
|
2946
|
-
|
|
2921
|
+
if (!isFullMode) {
|
|
2922
|
+
liquidMiddleWare();
|
|
2923
|
+
} else {
|
|
2924
|
+
handleSave();
|
|
2925
|
+
}
|
|
2947
2926
|
// Reset the flag to prevent infinite loop
|
|
2948
2927
|
if (onValidationFail) {
|
|
2949
2928
|
onValidationFail();
|
|
2950
2929
|
}
|
|
2951
2930
|
}
|
|
2952
|
-
}, [isGetFormData, handleSave, onValidationFail]);
|
|
2931
|
+
}, [isGetFormData, handleSave, onValidationFail, isFullMode, liquidMiddleWare]);
|
|
2953
2932
|
|
|
2954
2933
|
// Add message event listener to handle parent communication (like old MobilePush components)
|
|
2955
2934
|
useEffect(() => {
|
|
@@ -3090,7 +3069,8 @@ const MobilePushNew = ({
|
|
|
3090
3069
|
<CapButton
|
|
3091
3070
|
type="primary"
|
|
3092
3071
|
onClick={() => {
|
|
3093
|
-
|
|
3072
|
+
// Liquid validation (extractTags) only in library mode
|
|
3073
|
+
if (!isFullMode) {
|
|
3094
3074
|
liquidMiddleWare();
|
|
3095
3075
|
} else {
|
|
3096
3076
|
handleSave();
|
|
@@ -72,7 +72,7 @@ export class MobilepushWrapper extends React.Component { // eslint-disable-line
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
render() {
|
|
75
|
-
const {mobilePushCreateMode, step, getFormData, setIsLoadingContent, isGetFormData, query, isFullMode, showTemplateName, type, onValidationFail, onPreviewContentClicked, onTestContentClicked, templateData, eventContextTags = [], showTestAndPreviewSlidebox, handleTestAndPreview, handleCloseTestAndPreview, restrictPersonalization, isAnonymousType, onPersonalizationTokensChange} = this.props;
|
|
75
|
+
const {mobilePushCreateMode, step, getFormData, getLiquidTags, setIsLoadingContent, isGetFormData, query, isFullMode, showTemplateName, type, onValidationFail, onPreviewContentClicked, onTestContentClicked, templateData, eventContextTags = [], showTestAndPreviewSlidebox, handleTestAndPreview, handleCloseTestAndPreview, restrictPersonalization, isAnonymousType, onPersonalizationTokensChange} = this.props;
|
|
76
76
|
const {templateName} = this.state;
|
|
77
77
|
const isShowMobilepushCreate = !isEmpty(mobilePushCreateMode);
|
|
78
78
|
return (
|
|
@@ -102,6 +102,7 @@ export class MobilepushWrapper extends React.Component { // eslint-disable-line
|
|
|
102
102
|
<div>
|
|
103
103
|
{isShowMobilepushCreate && <MobilepushCreate
|
|
104
104
|
getFormLibraryData={getFormData}
|
|
105
|
+
getLiquidTags={getLiquidTags}
|
|
105
106
|
setIsLoadingContent={setIsLoadingContent}
|
|
106
107
|
defaultData={{"template-name": templateName}}
|
|
107
108
|
location={{
|
|
@@ -143,6 +144,7 @@ MobilepushWrapper.propTypes = {
|
|
|
143
144
|
mobilePushCreateMode: PropTypes.string,
|
|
144
145
|
step: PropTypes.string,
|
|
145
146
|
getFormData: PropTypes.string,
|
|
147
|
+
getLiquidTags: PropTypes.func,
|
|
146
148
|
setIsLoadingContent: PropTypes.func,
|
|
147
149
|
onEnterTemplateName: PropTypes.func,
|
|
148
150
|
onRemoveTemplateName: PropTypes.func,
|
|
@@ -394,23 +394,15 @@ export const Rcs = (props) => {
|
|
|
394
394
|
const validationResponse =
|
|
395
395
|
validateTags({
|
|
396
396
|
content: contentForValidation,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const errorMsg =
|
|
407
|
-
(unsupportedTagsLengthCheck &&
|
|
408
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
409
|
-
unsupportedTags: validationResponse.unsupportedTags,
|
|
410
|
-
})) ||
|
|
411
|
-
(validationResponse.isBraceError &&
|
|
412
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
413
|
-
false;
|
|
397
|
+
tagsParam: tags,
|
|
398
|
+
location,
|
|
399
|
+
tagModule: getDefaultTags,
|
|
400
|
+
isFullMode,
|
|
401
|
+
}) || {};
|
|
402
|
+
const errorMsg =
|
|
403
|
+
(validationResponse?.isBraceError &&
|
|
404
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
405
|
+
false;
|
|
414
406
|
if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
|
|
415
407
|
if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
|
|
416
408
|
};
|
|
@@ -835,10 +827,9 @@ export const Rcs = (props) => {
|
|
|
835
827
|
|
|
836
828
|
const templateDescErrorHandler = (value) => {
|
|
837
829
|
let errorMessage = false;
|
|
838
|
-
const {
|
|
830
|
+
const { isBraceError } = validateTags({
|
|
839
831
|
content: value,
|
|
840
832
|
tagsParam: tags,
|
|
841
|
-
injectedTagsParams: injectedTags,
|
|
842
833
|
location,
|
|
843
834
|
tagModule: getDefaultTags,
|
|
844
835
|
isFullMode,
|
|
@@ -868,26 +859,10 @@ export const Rcs = (props) => {
|
|
|
868
859
|
};
|
|
869
860
|
|
|
870
861
|
const fallbackMessageErrorHandler = (value) => {
|
|
871
|
-
let errorMessage = false;
|
|
872
|
-
const { unsupportedTags } = validateTags({
|
|
873
|
-
content: value,
|
|
874
|
-
tagsParam: tags,
|
|
875
|
-
injectedTagsParams: injectedTags,
|
|
876
|
-
location,
|
|
877
|
-
tagModule: getDefaultTags,
|
|
878
|
-
isFullMode,
|
|
879
|
-
}) || {};
|
|
880
862
|
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
881
|
-
|
|
882
|
-
} else if (unsupportedTags?.length > 0) {
|
|
883
|
-
errorMessage = formatMessage(
|
|
884
|
-
globalMessages.unsupportedTagsValidationError,
|
|
885
|
-
{
|
|
886
|
-
unsupportedTags,
|
|
887
|
-
},
|
|
888
|
-
);
|
|
863
|
+
return formatMessage(messages.fallbackMsgLenError);
|
|
889
864
|
}
|
|
890
|
-
return
|
|
865
|
+
return false;
|
|
891
866
|
};
|
|
892
867
|
|
|
893
868
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
@@ -51,6 +51,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
51
51
|
modalContent: {title: "Alert", body: "Do you really want to delete this version?", type: 'confirm', id: 'sms-version-modal'},
|
|
52
52
|
showTestAndPreviewSlidebox: false,
|
|
53
53
|
isTestAndPreviewMode: false,
|
|
54
|
+
pendingGetFormData: false,
|
|
54
55
|
};
|
|
55
56
|
this.saveFormData = this.saveFormData.bind(this);
|
|
56
57
|
this.onFormDataChange = this.onFormDataChange.bind(this);
|
|
@@ -140,8 +141,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
componentWillReceiveProps(nextProps) {
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
// Only trigger on actual Done click (isGetFormData false -> true). Prevents auto-submit after user fixes brace error.
|
|
145
|
+
if (!nextProps.isFullMode && nextProps.isGetFormData && !this.props.isGetFormData) {
|
|
146
|
+
this.setState({ startValidation: true, pendingGetFormData: true });
|
|
145
147
|
} else if (nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
|
|
146
148
|
this.startValidation();
|
|
147
149
|
}
|
|
@@ -171,6 +173,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
componentWillUnmount() {
|
|
176
|
+
if (this.pendingGetFormDataTimeout) {
|
|
177
|
+
clearTimeout(this.pendingGetFormDataTimeout);
|
|
178
|
+
}
|
|
174
179
|
if (this.props.setIsLoadingContent) {
|
|
175
180
|
this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
|
|
176
181
|
}
|
|
@@ -199,6 +204,10 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
199
204
|
if (currentTab) {
|
|
200
205
|
this.setState({currentTab});
|
|
201
206
|
}
|
|
207
|
+
// Clear footer validation errors on input change so they refresh on next validation
|
|
208
|
+
if (this.props.showLiquidErrorInFooter) {
|
|
209
|
+
this.props.showLiquidErrorInFooter({ STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] });
|
|
210
|
+
}
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
onTagSelect(data, currentTab) {
|
|
@@ -349,7 +358,25 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
349
358
|
}
|
|
350
359
|
|
|
351
360
|
setFormValidity(isFormValid, errorData) {
|
|
352
|
-
this.setState({isFormValid, errorData})
|
|
361
|
+
this.setState({ isFormValid, errorData }, () => {
|
|
362
|
+
if (this.state.pendingGetFormData && !isFormValid) {
|
|
363
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
364
|
+
// Reset parent's Done state so next Done click is a fresh attempt
|
|
365
|
+
if (this.props.onValidationFail) {
|
|
366
|
+
this.props.onValidationFail();
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
// In library mode with SMS, submit only when FormBuilder calls onSubmit (after liquid validation).
|
|
371
|
+
if (this.state.pendingGetFormData && this.props.getFormSubscriptionData && this.props.isFullMode) {
|
|
372
|
+
if (this.pendingGetFormDataTimeout) {
|
|
373
|
+
clearTimeout(this.pendingGetFormDataTimeout);
|
|
374
|
+
this.pendingGetFormDataTimeout = null;
|
|
375
|
+
}
|
|
376
|
+
this.props.getFormSubscriptionData(this.getFormData());
|
|
377
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
378
|
+
}
|
|
379
|
+
});
|
|
353
380
|
}
|
|
354
381
|
|
|
355
382
|
injectMessages(elem) {
|
|
@@ -970,6 +997,15 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
970
997
|
}
|
|
971
998
|
|
|
972
999
|
saveFormData() {
|
|
1000
|
+
// In library mode: FormBuilder calls onSubmit only after liquid validation succeeds.
|
|
1001
|
+
// Submit to parent here so the slidebox can close with valid data.
|
|
1002
|
+
if (!this.props.isFullMode) {
|
|
1003
|
+
if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
|
|
1004
|
+
this.props.getFormSubscriptionData(this.getFormData());
|
|
1005
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
1006
|
+
}
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
973
1009
|
//Logic to save in db etc
|
|
974
1010
|
const formData = _.cloneDeep(this.state.formData);
|
|
975
1011
|
const obj = {};
|
|
@@ -90,6 +90,10 @@ export default defineMessages({
|
|
|
90
90
|
id: 'creatives.containersV2.Create.validationError',
|
|
91
91
|
defaultMessage: 'Validation error',
|
|
92
92
|
},
|
|
93
|
+
"unbalancedCurlyBraces": {
|
|
94
|
+
id: 'creatives.containersV2.Create.unbalancedCurlyBraces',
|
|
95
|
+
defaultMessage: 'Please close all curly braces in the message.',
|
|
96
|
+
},
|
|
93
97
|
"smsTemplateCreatedSuccess": {
|
|
94
98
|
id: 'creatives.containersV2.Create.smsTemplateCreatedSuccess',
|
|
95
99
|
defaultMessage: 'SMS Template Created Successfully',
|
|
@@ -52,6 +52,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
52
52
|
modalContent: {title: "Alert", body: "Do you really want to delete this version?", type: 'confirm', id: 'sms-version-modal'},
|
|
53
53
|
showTestAndPreviewSlidebox: false,
|
|
54
54
|
isTestAndPreviewMode: false,
|
|
55
|
+
pendingGetFormData: false,
|
|
55
56
|
};
|
|
56
57
|
this.saveFormData = this.saveFormData.bind(this);
|
|
57
58
|
this.onFormDataChange = this.onFormDataChange.bind(this);
|
|
@@ -134,8 +135,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
componentWillReceiveProps(nextProps) {
|
|
137
|
-
if (nextProps.
|
|
138
|
-
|
|
138
|
+
if (!nextProps.isFullMode && nextProps.isGetFormData && !this.props.isGetFormData) {
|
|
139
|
+
this.setState({ startValidation: true, pendingGetFormData: true });
|
|
139
140
|
}
|
|
140
141
|
if ( nextProps.location.query.module === 'library' && nextProps.subscriptionTemplateDetails && nextProps.subscriptionTemplateDetails.name && _.isEmpty(this.state.editData) && !_.isEmpty(this.state.schema)) {
|
|
141
142
|
this.setEditState(nextProps.subscriptionTemplateDetails);
|
|
@@ -189,6 +190,9 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
componentWillUnmount() {
|
|
193
|
+
if (this.pendingGetFormDataTimeout) {
|
|
194
|
+
clearTimeout(this.pendingGetFormDataTimeout);
|
|
195
|
+
}
|
|
192
196
|
if (this.props.setIsLoadingContent) {
|
|
193
197
|
this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
|
|
194
198
|
}
|
|
@@ -214,6 +218,10 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
214
218
|
if (currentTab) {
|
|
215
219
|
this.setState({currentTab});
|
|
216
220
|
}
|
|
221
|
+
// Clear footer validation errors on input change so they refresh on next validation
|
|
222
|
+
if (this.props.showLiquidErrorInFooter) {
|
|
223
|
+
this.props.showLiquidErrorInFooter({ STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] });
|
|
224
|
+
}
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
onVersionNameChange() {
|
|
@@ -317,7 +325,23 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
317
325
|
}
|
|
318
326
|
|
|
319
327
|
setFormValidity(isFormValid, errorData) {
|
|
320
|
-
this.setState({isFormValid, errorData})
|
|
328
|
+
this.setState({ isFormValid, errorData }, () => {
|
|
329
|
+
if (this.state.pendingGetFormData && !isFormValid) {
|
|
330
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
331
|
+
if (this.props.onValidationFail) {
|
|
332
|
+
this.props.onValidationFail();
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (this.state.pendingGetFormData && this.props.getFormSubscriptionData && this.props.isFullMode) {
|
|
337
|
+
if (this.pendingGetFormDataTimeout) {
|
|
338
|
+
clearTimeout(this.pendingGetFormDataTimeout);
|
|
339
|
+
this.pendingGetFormDataTimeout = null;
|
|
340
|
+
}
|
|
341
|
+
this.props.getFormSubscriptionData(this.getFormData());
|
|
342
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
343
|
+
}
|
|
344
|
+
});
|
|
321
345
|
}
|
|
322
346
|
|
|
323
347
|
getFormData(e, value) {
|
|
@@ -923,6 +947,14 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
923
947
|
this.setState({startValidation: false});
|
|
924
948
|
}
|
|
925
949
|
saveFormData() {
|
|
950
|
+
// In library mode: FormBuilder calls onSubmit only after liquid validation succeeds.
|
|
951
|
+
if (!this.props.isFullMode) {
|
|
952
|
+
if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
|
|
953
|
+
this.props.getFormSubscriptionData(this.getFormData());
|
|
954
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
955
|
+
}
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
926
958
|
//Logic to save in db etc
|
|
927
959
|
//saveFormData gets called only when validation result is true
|
|
928
960
|
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import isEmpty from 'lodash/isEmpty';
|
|
2
|
-
import
|
|
2
|
+
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
3
3
|
import messages from './Create/messages';
|
|
4
4
|
export function showError() {
|
|
5
5
|
const {intl} = this.props;
|
|
6
6
|
const {errorData} = this.state;
|
|
7
7
|
const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
|
|
8
8
|
if (!isEmpty(this.state.formData) && !this.state.isFormValid) {
|
|
9
|
-
const
|
|
9
|
+
const err0 = errorData[0] || {};
|
|
10
|
+
const isSmsInvalid = Object.values(err0).includes(true);
|
|
10
11
|
if (isSmsInvalid) {
|
|
11
|
-
const invalidTags = errorData[0]['invalid-tags'];
|
|
12
|
-
if (!isEmpty(invalidTags)) {
|
|
13
|
-
errorMessage.description = `${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
|
|
14
|
-
}
|
|
15
12
|
CapNotification.error(errorMessage);
|
|
16
13
|
}
|
|
17
14
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
2
|
+
import { showError } from '../commonMethods';
|
|
3
|
+
|
|
4
|
+
jest.mock('@capillarytech/cap-ui-library/CapNotification', () => ({
|
|
5
|
+
error: jest.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
jest.mock('../Create/messages', () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
default: {
|
|
11
|
+
validationError: { defaultMessage: 'Validation error' },
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
describe('Sms commonMethods', () => {
|
|
16
|
+
describe('showError', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should call CapNotification.error when formData is not empty, isFormValid is false, and errorData has at least one true value', () => {
|
|
22
|
+
const context = {
|
|
23
|
+
props: {
|
|
24
|
+
intl: { formatMessage: jest.fn((msg) => msg.defaultMessage || 'Validation error') },
|
|
25
|
+
},
|
|
26
|
+
state: {
|
|
27
|
+
formData: { message: 'test' },
|
|
28
|
+
isFormValid: false,
|
|
29
|
+
errorData: [{ message: true }],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
showError.call(context);
|
|
33
|
+
expect(CapNotification.error).toHaveBeenCalledWith({
|
|
34
|
+
key: 'validation-error',
|
|
35
|
+
message: 'Validation error',
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should not call CapNotification.error when formData is empty', () => {
|
|
40
|
+
const context = {
|
|
41
|
+
props: { intl: { formatMessage: jest.fn() } },
|
|
42
|
+
state: {
|
|
43
|
+
formData: {},
|
|
44
|
+
isFormValid: false,
|
|
45
|
+
errorData: [{ message: true }],
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
showError.call(context);
|
|
49
|
+
expect(CapNotification.error).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should not call CapNotification.error when isFormValid is true', () => {
|
|
53
|
+
const context = {
|
|
54
|
+
props: { intl: { formatMessage: jest.fn() } },
|
|
55
|
+
state: {
|
|
56
|
+
formData: { message: 'test' },
|
|
57
|
+
isFormValid: true,
|
|
58
|
+
errorData: [{ message: true }],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
showError.call(context);
|
|
62
|
+
expect(CapNotification.error).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should not call CapNotification.error when no errorData entry has a true value', () => {
|
|
66
|
+
const context = {
|
|
67
|
+
props: { intl: { formatMessage: jest.fn() } },
|
|
68
|
+
state: {
|
|
69
|
+
formData: { message: 'test' },
|
|
70
|
+
isFormValid: false,
|
|
71
|
+
errorData: [{ message: false, title: false }],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
showError.call(context);
|
|
75
|
+
expect(CapNotification.error).not.toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should not call CapNotification.error when errorData is empty', () => {
|
|
79
|
+
const context = {
|
|
80
|
+
props: { intl: { formatMessage: jest.fn() } },
|
|
81
|
+
state: {
|
|
82
|
+
formData: { message: 'test' },
|
|
83
|
+
isFormValid: false,
|
|
84
|
+
errorData: [],
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
showError.call(context);
|
|
88
|
+
expect(CapNotification.error).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not call CapNotification.error when errorData[0] is undefined', () => {
|
|
92
|
+
const context = {
|
|
93
|
+
props: { intl: { formatMessage: jest.fn() } },
|
|
94
|
+
state: {
|
|
95
|
+
formData: { message: 'test' },
|
|
96
|
+
isFormValid: false,
|
|
97
|
+
errorData: [],
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
showError.call(context);
|
|
101
|
+
expect(CapNotification.error).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should call CapNotification.error when errorData[0] has multiple keys and one is true', () => {
|
|
105
|
+
const context = {
|
|
106
|
+
props: {
|
|
107
|
+
intl: { formatMessage: jest.fn((msg) => msg.defaultMessage || 'Validation error') },
|
|
108
|
+
},
|
|
109
|
+
state: {
|
|
110
|
+
formData: { message: 'hello' },
|
|
111
|
+
isFormValid: false,
|
|
112
|
+
errorData: [{ message: false, title: true }],
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
showError.call(context);
|
|
116
|
+
expect(CapNotification.error).toHaveBeenCalledWith({
|
|
117
|
+
key: 'validation-error',
|
|
118
|
+
message: 'Validation error',
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -39,10 +39,8 @@ import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
|
|
|
39
39
|
import UnifiedPreview from '../../../v2Components/CommonTestAndPreview/UnifiedPreview';
|
|
40
40
|
import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
|
|
41
41
|
import withCreatives from '../../../hoc/withCreatives';
|
|
42
|
-
import { validateTags } from '../../../utils/tagValidations';
|
|
43
42
|
import {
|
|
44
43
|
CHARLIMIT,
|
|
45
|
-
SMS,
|
|
46
44
|
SMS_TRAI_VAR,
|
|
47
45
|
TAG,
|
|
48
46
|
EMBEDDED,
|
|
@@ -51,16 +49,15 @@ import {
|
|
|
51
49
|
ALL,
|
|
52
50
|
LIBRARY,
|
|
53
51
|
} from './constants';
|
|
52
|
+
import { SMS } from '../../CreativesContainer/constants';
|
|
54
53
|
import v2EditSmsReducer from '../../Sms/Edit/reducer';
|
|
55
54
|
import { v2SmsEditSagas } from '../../Sms/Edit/sagas';
|
|
56
55
|
import ErrorInfoNote from '../../../v2Components/ErrorInfoNote';
|
|
57
56
|
import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
|
|
58
|
-
import { hasLiquidSupportFeature } from '../../../utils/common';
|
|
59
57
|
import { ANDROID } from '../../../v2Components/CommonTestAndPreview/constants';
|
|
60
58
|
|
|
61
59
|
let varMap = {};
|
|
62
60
|
let traiData = {};
|
|
63
|
-
let tagValidationResponse = {};
|
|
64
61
|
const { TextArea } = CapInput;
|
|
65
62
|
const { CapLabelInline } = CapLabel;
|
|
66
63
|
|
|
@@ -94,7 +91,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
94
91
|
const [tags, updateTags] = useState([]);
|
|
95
92
|
const [textAreaId, updateTextAreaId] = useState();
|
|
96
93
|
const [isValidationError, updateIsValidationError] = useState(false);
|
|
97
|
-
const [isTagValidationError, updateIsTagValidationError] = useState(false);
|
|
98
94
|
const [totalMessageLength, setTotalMessageLength] = useState(0);
|
|
99
95
|
const [isUnicodeAllowed, updateIsUnicodeAllowed] = useState(true);
|
|
100
96
|
const [showMsgLengthNote, updateshowMsgLengthNote] = useState(false);
|
|
@@ -229,29 +225,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
229
225
|
}
|
|
230
226
|
}, []);
|
|
231
227
|
|
|
232
|
-
//performs tag validation
|
|
233
|
-
useEffect(() => {
|
|
234
|
-
if (
|
|
235
|
-
!isFullMode &&
|
|
236
|
-
updatedSmsEditor?.length > 0 &&
|
|
237
|
-
!updatedSmsEditor.includes(SMS_TRAI_VAR)
|
|
238
|
-
) {
|
|
239
|
-
tagValidationResponse =
|
|
240
|
-
validateTags({
|
|
241
|
-
content: updatedSmsEditor.join(''),
|
|
242
|
-
tagsParam: tags,
|
|
243
|
-
injectedTagsParams: injectedTags,
|
|
244
|
-
location,
|
|
245
|
-
tagModule: getDefaultTags,
|
|
246
|
-
eventContextTags,
|
|
247
|
-
isFullMode,
|
|
248
|
-
}) || {};
|
|
249
|
-
updateIsTagValidationError(
|
|
250
|
-
tagValidationResponse.unsupportedTags.length > 0,
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
}, [updatedSmsEditor, tags]);
|
|
254
|
-
|
|
255
228
|
const computeUpdatedSmsEditor = () => {
|
|
256
229
|
const arr = [...tempMsgArray];
|
|
257
230
|
const varMapKeys = Object.keys(varMap)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
|
|
@@ -288,6 +261,13 @@ export const SmsTraiEdit = (props) => {
|
|
|
288
261
|
};
|
|
289
262
|
|
|
290
263
|
const onSubmitWrapper = () => {
|
|
264
|
+
setIsLiquidValidationError(false);
|
|
265
|
+
setLiquidErrorMessages({});
|
|
266
|
+
// Liquid validation (extractTags) only in library mode
|
|
267
|
+
if (isFullMode) {
|
|
268
|
+
onDoneCallback();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
291
271
|
const content = updatedSmsEditor.join('');
|
|
292
272
|
const onError = ({ standardErrors, liquidErrors }) => {
|
|
293
273
|
setLiquidErrorMessages({
|
|
@@ -298,6 +278,8 @@ export const SmsTraiEdit = (props) => {
|
|
|
298
278
|
};
|
|
299
279
|
|
|
300
280
|
const onSuccess = () => {
|
|
281
|
+
setIsLiquidValidationError(false);
|
|
282
|
+
setLiquidErrorMessages({});
|
|
301
283
|
onDoneCallback();
|
|
302
284
|
};
|
|
303
285
|
|
|
@@ -313,10 +295,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
313
295
|
messages: formBuilderMessages,
|
|
314
296
|
onError,
|
|
315
297
|
onSuccess,
|
|
316
|
-
tagLookupMap: metaEntities?.tagLookupMap,
|
|
317
|
-
eventContextTags,
|
|
318
|
-
isLiquidFlow: true,
|
|
319
|
-
forwardedTags: {},
|
|
320
298
|
});
|
|
321
299
|
};
|
|
322
300
|
|
|
@@ -555,17 +533,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
555
533
|
return countVarChar;
|
|
556
534
|
};
|
|
557
535
|
|
|
558
|
-
const tagValidationErrorMessage = () => {
|
|
559
|
-
const { unsupportedTags = [] } = tagValidationResponse;
|
|
560
|
-
let tagError = '';
|
|
561
|
-
if (unsupportedTags.length > 0) {
|
|
562
|
-
tagError = formatMessage(messages.unsupportedTagsValidationError, {
|
|
563
|
-
unsupportedTags,
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
return <CapError>{tagError}</CapError>;
|
|
567
|
-
};
|
|
568
|
-
|
|
569
536
|
const disablehandler = () => {
|
|
570
537
|
if (traiData && !isEmpty(traiData)) {
|
|
571
538
|
const msg = get(traiData, `versions.base.sms-editor`, '');
|
|
@@ -615,7 +582,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
615
582
|
setShowTestAndPreviewSlidebox(false);
|
|
616
583
|
};
|
|
617
584
|
|
|
618
|
-
const isLiquidSupportFeatureEnabled = hasLiquidSupportFeature();
|
|
619
585
|
return (
|
|
620
586
|
<>
|
|
621
587
|
<CapSpin spinning={loading || fetchingLiquidTags} tip={fetchingLiquidTags && formatMessage(formBuilderMessages.liquidSpinText)}>
|
|
@@ -673,7 +639,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
673
639
|
<CapRow>
|
|
674
640
|
{smsLengthForVar()}
|
|
675
641
|
</CapRow>
|
|
676
|
-
{isTagValidationError && tagValidationErrorMessage()}
|
|
677
642
|
<CapCheckbox onChange={unicodeHandler} checked={isUnicodeAllowed} disabled={disablehandler()}>
|
|
678
643
|
{formatMessage(messages.unicodeLabel)}
|
|
679
644
|
</CapCheckbox>
|
|
@@ -704,9 +669,8 @@ export const SmsTraiEdit = (props) => {
|
|
|
704
669
|
<FormattedMessage {...messages.testAndPreviewButtonLabel} />
|
|
705
670
|
</CapButton>
|
|
706
671
|
<CapButton
|
|
707
|
-
onClick={
|
|
672
|
+
onClick={onSubmitWrapper}
|
|
708
673
|
className="create-msg"
|
|
709
|
-
disabled={isTagValidationError || (isLiquidSupportFeatureEnabled && !isObject(metaEntities?.tagLookupMap))}
|
|
710
674
|
>
|
|
711
675
|
<FormattedMessage {...messages.saveButtonLabel} />
|
|
712
676
|
</CapButton>
|