@capillarytech/creatives-library 8.0.291 → 8.0.292-alpha.0
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 +3 -1
- package/initialState.js +0 -2
- package/package.json +1 -1
- package/utils/common.js +5 -8
- package/utils/commonUtils.js +4 -85
- 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/index.js +30 -7
- package/v2Containers/Email/index.js +1 -5
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +23 -70
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +29 -137
- package/v2Containers/FTP/index.js +2 -51
- package/v2Containers/FTP/messages.js +0 -4
- package/v2Containers/InApp/index.js +4 -104
- package/v2Containers/InApp/tests/index.test.js +17 -6
- package/v2Containers/InappAdvance/index.js +4 -108
- package/v2Containers/InappAdvance/tests/index.test.js +2 -0
- package/v2Containers/Line/Container/Text/index.js +0 -1
- package/v2Containers/MobilePush/Create/index.js +42 -19
- package/v2Containers/MobilePush/Edit/index.js +42 -19
- 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/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 -2
- package/v2Containers/WebPush/Create/utils/validation.test.js +59 -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
|
|
@@ -2659,20 +2649,6 @@ const MobilePushNew = ({
|
|
|
2659
2649
|
getLiquidTags: globalActionsProps.getLiquidTags,
|
|
2660
2650
|
formatMessage,
|
|
2661
2651
|
messages: formBuilderMessages,
|
|
2662
|
-
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
2663
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
2664
|
-
isLiquidFlow: hasLiquidSupportFeature(),
|
|
2665
|
-
forwardedTags: {},
|
|
2666
|
-
skipTags: (tag) => {
|
|
2667
|
-
const skipRegexes = [
|
|
2668
|
-
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
2669
|
-
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
2670
|
-
/Link_to_[a-zA-z]/,
|
|
2671
|
-
/SURVEY.*\.TOKEN/,
|
|
2672
|
-
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
2673
|
-
];
|
|
2674
|
-
return skipRegexes.some((regex) => regex.test(tag));
|
|
2675
|
-
},
|
|
2676
2652
|
singleTab: getSingleTab(accountData),
|
|
2677
2653
|
});
|
|
2678
2654
|
}, [
|
|
@@ -2681,12 +2657,9 @@ const MobilePushNew = ({
|
|
|
2681
2657
|
activeTab,
|
|
2682
2658
|
globalActionsProps,
|
|
2683
2659
|
formatMessage,
|
|
2684
|
-
metaEntities,
|
|
2685
2660
|
accountData,
|
|
2686
2661
|
]);
|
|
2687
2662
|
|
|
2688
|
-
const isLiquidFlow = hasLiquidSupportFeature();
|
|
2689
|
-
|
|
2690
2663
|
useEffect(() => {
|
|
2691
2664
|
// Always map to { label } for both platforms
|
|
2692
2665
|
const newButtons = Array.isArray(ctaData)
|
|
@@ -2933,16 +2906,22 @@ const MobilePushNew = ({
|
|
|
2933
2906
|
setShowTestAndPreviewSlidebox(false);
|
|
2934
2907
|
}, []);
|
|
2935
2908
|
|
|
2936
|
-
// Add useEffect to handle isGetFormData prop changes
|
|
2909
|
+
// Add useEffect to handle isGetFormData prop changes (e.g. Done clicked in footer)
|
|
2910
|
+
// In library mode: run liquid validation (extractTags) first; on success liquidMiddleWare calls handleSave
|
|
2911
|
+
// In full mode: call handleSave directly
|
|
2937
2912
|
useEffect(() => {
|
|
2938
2913
|
if (isGetFormData) {
|
|
2939
|
-
|
|
2914
|
+
if (!isFullMode) {
|
|
2915
|
+
liquidMiddleWare();
|
|
2916
|
+
} else {
|
|
2917
|
+
handleSave();
|
|
2918
|
+
}
|
|
2940
2919
|
// Reset the flag to prevent infinite loop
|
|
2941
2920
|
if (onValidationFail) {
|
|
2942
2921
|
onValidationFail();
|
|
2943
2922
|
}
|
|
2944
2923
|
}
|
|
2945
|
-
}, [isGetFormData, handleSave, onValidationFail]);
|
|
2924
|
+
}, [isGetFormData, handleSave, onValidationFail, isFullMode, liquidMiddleWare]);
|
|
2946
2925
|
|
|
2947
2926
|
// Add message event listener to handle parent communication (like old MobilePush components)
|
|
2948
2927
|
useEffect(() => {
|
|
@@ -3083,7 +3062,8 @@ const MobilePushNew = ({
|
|
|
3083
3062
|
<CapButton
|
|
3084
3063
|
type="primary"
|
|
3085
3064
|
onClick={() => {
|
|
3086
|
-
|
|
3065
|
+
// Liquid validation (extractTags) only in library mode
|
|
3066
|
+
if (!isFullMode) {
|
|
3087
3067
|
liquidMiddleWare();
|
|
3088
3068
|
} else {
|
|
3089
3069
|
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
|
validateLiquidTemplateContent(content, {
|
|
@@ -306,10 +288,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
306
288
|
messages: formBuilderMessages,
|
|
307
289
|
onError,
|
|
308
290
|
onSuccess,
|
|
309
|
-
tagLookupMap: metaEntities?.tagLookupMap,
|
|
310
|
-
eventContextTags,
|
|
311
|
-
isLiquidFlow: true,
|
|
312
|
-
forwardedTags: {},
|
|
313
291
|
});
|
|
314
292
|
};
|
|
315
293
|
|
|
@@ -548,17 +526,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
548
526
|
return countVarChar;
|
|
549
527
|
};
|
|
550
528
|
|
|
551
|
-
const tagValidationErrorMessage = () => {
|
|
552
|
-
const { unsupportedTags = [] } = tagValidationResponse;
|
|
553
|
-
let tagError = '';
|
|
554
|
-
if (unsupportedTags.length > 0) {
|
|
555
|
-
tagError = formatMessage(messages.unsupportedTagsValidationError, {
|
|
556
|
-
unsupportedTags,
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
return <CapError>{tagError}</CapError>;
|
|
560
|
-
};
|
|
561
|
-
|
|
562
529
|
const disablehandler = () => {
|
|
563
530
|
if (traiData && !isEmpty(traiData)) {
|
|
564
531
|
const msg = get(traiData, `versions.base.sms-editor`, '');
|
|
@@ -604,7 +571,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
604
571
|
setShowTestAndPreviewSlidebox(false);
|
|
605
572
|
};
|
|
606
573
|
|
|
607
|
-
const isLiquidSupportFeatureEnabled = hasLiquidSupportFeature();
|
|
608
574
|
return (
|
|
609
575
|
<>
|
|
610
576
|
<CapSpin spinning={loading || fetchingLiquidTags} tip={fetchingLiquidTags && formatMessage(formBuilderMessages.liquidSpinText)}>
|
|
@@ -662,7 +628,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
662
628
|
<CapRow>
|
|
663
629
|
{smsLengthForVar()}
|
|
664
630
|
</CapRow>
|
|
665
|
-
{isTagValidationError && tagValidationErrorMessage()}
|
|
666
631
|
<CapCheckbox onChange={unicodeHandler} checked={isUnicodeAllowed} disabled={disablehandler()}>
|
|
667
632
|
{formatMessage(messages.unicodeLabel)}
|
|
668
633
|
</CapCheckbox>
|
|
@@ -693,9 +658,8 @@ export const SmsTraiEdit = (props) => {
|
|
|
693
658
|
<FormattedMessage {...messages.testAndPreviewButtonLabel} />
|
|
694
659
|
</CapButton>
|
|
695
660
|
<CapButton
|
|
696
|
-
onClick={
|
|
661
|
+
onClick={onSubmitWrapper}
|
|
697
662
|
className="create-msg"
|
|
698
|
-
disabled={isTagValidationError || (isLiquidSupportFeatureEnabled && !isObject(metaEntities?.tagLookupMap))}
|
|
699
663
|
>
|
|
700
664
|
<FormattedMessage {...messages.saveButtonLabel} />
|
|
701
665
|
</CapButton>
|