@capillarytech/creatives-library 8.0.10 → 8.0.12

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.
Files changed (45) hide show
  1. package/config/app.js +2 -0
  2. package/containers/App/constants.js +1 -0
  3. package/initialState.js +3 -0
  4. package/package.json +1 -1
  5. package/services/api.js +23 -2
  6. package/services/tests/api.test.js +181 -0
  7. package/utils/common.js +7 -0
  8. package/utils/commonUtils.js +7 -1
  9. package/utils/tagValidations.js +99 -1
  10. package/utils/tests/commonUtil.test.js +37 -1
  11. package/utils/tests/tagValidations.test.js +392 -2
  12. package/v2Components/Ckeditor/index.js +12 -10
  13. package/v2Components/ErrorInfoNote/index.js +89 -0
  14. package/v2Components/ErrorInfoNote/messages.js +29 -0
  15. package/v2Components/ErrorInfoNote/style.scss +72 -0
  16. package/v2Components/FormBuilder/_formBuilder.scss +4 -1
  17. package/v2Components/FormBuilder/index.js +172 -74
  18. package/v2Components/FormBuilder/messages.js +8 -0
  19. package/v2Containers/Cap/actions.js +8 -0
  20. package/v2Containers/Cap/constants.js +4 -0
  21. package/v2Containers/Cap/mockData.js +74 -0
  22. package/v2Containers/Cap/reducer.js +12 -0
  23. package/v2Containers/Cap/sagas.js +28 -1
  24. package/v2Containers/Cap/selectors.js +5 -0
  25. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +1 -0
  26. package/v2Containers/Cap/tests/reducer.test.js +50 -0
  27. package/v2Containers/Cap/tests/saga.test.js +81 -1
  28. package/v2Containers/CreativesContainer/SlideBoxContent.js +7 -0
  29. package/v2Containers/CreativesContainer/SlideBoxFooter.js +40 -17
  30. package/v2Containers/CreativesContainer/constants.js +1 -0
  31. package/v2Containers/CreativesContainer/index.js +45 -4
  32. package/v2Containers/CreativesContainer/index.scss +13 -1
  33. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +16 -0
  34. package/v2Containers/Email/_email.scss +3 -0
  35. package/v2Containers/Email/index.js +2 -0
  36. package/v2Containers/EmailWrapper/index.js +3 -0
  37. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +3 -0
  38. package/v2Containers/MobilePush/Edit/constants.js +2 -0
  39. package/v2Containers/MobilePush/Edit/index.js +124 -30
  40. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +93 -0
  41. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +12 -0
  42. package/v2Containers/Viber/constants.js +1 -1
  43. package/v2Containers/Viber/index.js +19 -19
  44. package/v2Containers/Viber/messages.js +0 -4
  45. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +12 -0
@@ -44,19 +44,22 @@ import EDMEditor from "../Edmeditor";
44
44
  import BeeEditor from '../../v2Containers/BeeEditor';
45
45
  import CustomPopOver from '../CustomPopOver';
46
46
  import messages from './messages';
47
- import { selectCurrentOrgDetails } from "../../v2Containers/Cap/selectors";
47
+ import { makeSelectMetaEntities, selectCurrentOrgDetails, selectLiquidStateDetails } from "../../v2Containers/Cap/selectors";
48
+ import * as actions from "../../v2Containers/Cap/actions";
48
49
  import './_formBuilder.scss';
49
50
  import {updateCharCount, checkUnicode} from "../../utils/smsCharCountV2";
50
- import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS,AI_CONTENT_BOT_DISABLED, EMAIL } from '../../v2Containers/CreativesContainer/constants';
51
- import { validateIfTagClosed } from '../../utils/tagValidations';
51
+ import { checkSupport, extractNames, preprocessHtml, validateIfTagClosed } from '../../utils/tagValidations';
52
+ import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS,AI_CONTENT_BOT_DISABLED, EMAIL, LIQUID_SUPPORTED_CHANNELS } from '../../v2Containers/CreativesContainer/constants';
52
53
  import globalMessages from '../../v2Containers/Cap/messages';
53
54
  import { convert } from 'html-to-text';
54
55
  import { OUTBOUND } from './constants';
55
56
  import { GET_TRANSLATION_MAPPED } from '../../containers/TagList/constants';
56
57
  import moment from 'moment';
57
58
  import { CUSTOMER_BARCODE_TAG , COPY_OF} from '../../containers/App/constants';
58
- import { isEmailUnsubscribeTagMandatory } from '../../utils/common';
59
+ import { hasLiquidSupportFeature, isEmailUnsubscribeTagMandatory } from '../../utils/common';
59
60
  import { isUrl } from '../../v2Containers/Line/Container/Wrapper/utils';
61
+ import { bindActionCreators } from 'redux';
62
+
60
63
  const TabPane = Tabs.TabPane;
61
64
  const {Column} = Table;
62
65
  const {TextArea} = CapInput;
@@ -95,6 +98,10 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
95
98
  customPopoverVisible: false,
96
99
  isDrawerVisible: false,
97
100
  translationLang: 'en',
101
+ liquidErrorMessage: {
102
+ STANDARD_ERROR_MSG: [],
103
+ LIQUID_ERROR_MSG: [],
104
+ },
98
105
  };
99
106
  this.renderForm = this.renderForm.bind(this);
100
107
  this.updateFormData = this.updateFormData.bind(this);
@@ -126,6 +133,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
126
133
  this.transformInjectedTags = this.transformInjectedTags.bind(this);
127
134
  this.handleSetRadioValue = this.handleSetRadioValue.bind(this);
128
135
  this.formElements = [];
136
+ // Check if the liquid flow feature is supported and the channel is in the supported list.
137
+ this.liquidFlow = hasLiquidSupportFeature() && LIQUID_SUPPORTED_CHANNELS.includes(props?.schema?.channel?.toUpperCase());
129
138
  }
130
139
 
131
140
  componentWillMount() {
@@ -480,10 +489,10 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
480
489
  validateForm(tags, injectedTags, isSave, showMessages, onValidationComplete) {
481
490
  const channel = _.get(this, "props.schema.channel");
482
491
  let isValid = true;
492
+ let isLiquidValid = true;
483
493
  const type = (this.props.location && this.props.location.query.type) ? this.props.location.query.type : 'full';
484
494
  const currentModule = (this.props.location && this.props.location.query.module) ? this.props.location.query.module : 'default';
485
495
  let errorData = _.cloneDeep(this.state.errorData);
486
- const stateFormData = _.cloneDeep(this.state.formData);
487
496
  if (channel && channel.toUpperCase() === SMS) {
488
497
  for (let count = 0; count < this.state.tabCount; count += 1) {
489
498
  if (_.isEmpty(errorData[count])) {
@@ -915,10 +924,10 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
915
924
  }
916
925
 
917
926
  // Form Data Validation for Email Channel
918
- if (this.props.schema && this.props.schema.channel && this.props.schema.channel.toUpperCase() === "EMAIL") {
927
+ if (this.props?.schema?.channel?.toUpperCase() === EMAIL) {
919
928
  // Validating Email By iterating all elements of FormBuilder
920
929
  const isEmail = true;
921
- _.forEach(this.state.formData, (data, index) => {
930
+ _.forEach(this.state.formData,(data, index) => {
922
931
 
923
932
  if (!this.state.formData["template-subject"] && type.toLowerCase() === 'embedded' && (currentModule.toLowerCase() === 'loyalty' || currentModule.toLowerCase() === 'dvs' || currentModule.toLowerCase() === 'timeline' || currentModule.toLowerCase() === 'library')) {
924
933
  errorData["template-subject"] = true;
@@ -950,7 +959,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
950
959
  } else if(index === 'template-version' && data !== '') {
951
960
  errorData[index] = false;
952
961
  }
953
-
962
+ isLiquidValid = isValid; // Existing validations support for liquid enabled orgs
954
963
  // Check error for the versions
955
964
  if (!isNaN(index) || index === 'base') {
956
965
  if (errorData[index] === undefined) {
@@ -963,37 +972,57 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
963
972
  if (!content) {
964
973
  return false;
965
974
  }
966
- const tagValidationResponse = this.validateTags(content, tags, injectedTags, isEmail);;
975
+ const tagValidationResponse = this.validateTags(content, tags, injectedTags, isEmail);
967
976
  if (errorData[index][currentLang] === undefined) {
968
977
  errorData[index][currentLang] = {};
969
978
  }
970
-
971
- if (tagValidationResponse.valid) {
979
+ if (tagValidationResponse?.valid) {
972
980
  errorData[index][currentLang]['template-content'] = false;
973
981
  } else {
974
982
  errorData[index][currentLang]['template-content'] = true;
975
983
  isValid = false;
976
- if (showMessages && !isNaN(index)) {
977
- if (tagValidationResponse.missingTags.length > 0 || tagValidationResponse.unsupportedTags.length > 0) {
984
+ isLiquidValid = false;
985
+ if ((showMessages && !isNaN(index)) || this.liquidFlow) {
986
+ if (tagValidationResponse?.missingTags?.length > 0 || tagValidationResponse?.unsupportedTags?.length > 0) {
978
987
  errorString += `${this.props.intl.formatMessage(messages.contentNotValidLanguage)} ${currentLang}\n`;
979
988
  }
980
- if (tagValidationResponse.missingTags.length > 0) {
989
+ if (tagValidationResponse?.missingTags?.length > 0) {
981
990
  errorString += `${this.props.intl.formatMessage(messages.missingTags)} ${tagValidationResponse.missingTags.toString()}\n`;
982
991
  }
983
- if (tagValidationResponse.unsupportedTags.length > 0) {
992
+ if (tagValidationResponse?.unsupportedTags?.length > 0) {
984
993
  errorString += `${this.props.intl.formatMessage(messages.unsupportedTags)} ${tagValidationResponse.unsupportedTags.toString()}\n`;
985
994
  }
986
- if (tagValidationResponse.isBraceError){
995
+ if (tagValidationResponse?.isBraceError){
987
996
  errorString += this.props.intl.formatMessage(globalMessages.unbalanacedCurlyBraces);
988
997
  }
989
998
  if (tagValidationResponse?.isContentEmpty) {
990
999
  errorString += this.props.intl.formatMessage(messages.emailBodyEmptyError);
1000
+ // Adds a bypass for cases where content is initially empty in the creation flow.
1001
+ if(this.liquidFlow && !this.props.isEdit){
1002
+ errorString = "";
1003
+ isLiquidValid = true;
1004
+ }
991
1005
  }
992
1006
  }
993
1007
  }
994
1008
  }
995
1009
  if (errorString !== "") {
996
- this.openNotificationWithIcon('error', errorString, "email-validation-error");
1010
+ if (this.liquidFlow) {
1011
+ this.setState(
1012
+ (prevState) => ({
1013
+ liquidErrorMessage: {
1014
+ ...prevState.liquidErrorMessage,
1015
+ STANDARD_ERROR_MSG: [errorString],
1016
+ },
1017
+ }),
1018
+ () => {
1019
+ // Callback after the state is updated
1020
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
1021
+ }
1022
+ );
1023
+ } else {
1024
+ this.openNotificationWithIcon('error', errorString, "email-validation-error");
1025
+ }
997
1026
  }
998
1027
  _.forEach(errorData[index], (versionData, key) => {
999
1028
  if (data.selectedLanguages.indexOf(key) === -1) {
@@ -1002,39 +1031,28 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1002
1031
  });
1003
1032
  }
1004
1033
  });
1005
- // for (let count = 0; count < this.state.tabCount; count += 1) {
1006
- // const index = count + 1;
1007
- // // const content = this.state.formData[count][`template-content${index > 1 ? index : ''}`];
1008
- // // const tagValidationResponse = this.validateTags(content, tags, injectedTags);
1009
- // //
1010
- // // if (tagValidationResponse.valid) {
1011
- // // errorData[count][`template-content${index > 1 ? index : ''}`] = false;
1012
- // // } else {
1013
- // // errorData[count][`template-content${index > 1 ? index : ''}`] = true;
1014
- // // isValid = false;
1015
- // // }
1016
- // }
1017
- // if (type.toLowerCase() === 'embedded' && (currentModule.toLowerCase() === 'loyalty' || currentModule.toLowerCase() === 'dvs' || currentModule.toLowerCase() === 'timeline')) {
1018
- // if (this.state.formData['template-subject'] && this.state.formData['template-subject'].trim() === '') {
1019
- // errorData['template-subject'] = true;
1020
- // isValid = false;
1021
- // }
1022
- // }
1023
- // if(type !== 'embedded' && (!this.state.formData['template-name'] || this.state.formData['template-name'] === '')) {
1024
- // errorData['template-name'] = true;
1025
- // isValid = false;
1026
- // } else {
1027
- // errorData['template-name'] = false;
1028
- // }
1029
1034
  }
1030
- this.setState({isFormValid : isValid, errorData}, () => {
1035
+
1036
+ const isTemplateValid = this.liquidFlow ? isLiquidValid : isValid;
1037
+ //Updating the state with the error data
1038
+ this.setState((prevState) => ({
1039
+ isFormValid: isTemplateValid,
1040
+ liquidErrorMessage: {
1041
+ //Do not update the LIQUID_ERROR_MSG validation error message
1042
+ ...prevState.liquidErrorMessage,
1043
+ //update Standard error message based on the updated content
1044
+ STANDARD_ERROR_MSG: isTemplateValid ? [] : prevState.liquidErrorMessage?.STANDARD_ERROR_MSG,
1045
+ },
1046
+ errorData,
1047
+ }), () => {
1048
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
1031
1049
  if (onValidationComplete) {
1032
1050
  onValidationComplete();
1033
1051
  }
1034
1052
  });
1035
1053
 
1036
- this.props.onFormValidityChange(isValid, errorData);
1037
- return isValid;
1054
+ this.props.onFormValidityChange(isTemplateValid, errorData);
1055
+ return isTemplateValid;
1038
1056
  }
1039
1057
 
1040
1058
  indexOfEnd(targetString, string) {
@@ -1049,8 +1067,70 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1049
1067
  this.props.getValidationData();
1050
1068
  return;
1051
1069
  }
1052
- if (this.state.isFormValid) {
1053
- this.props.onSubmit(this.state.formData);
1070
+ if ( this.state.isFormValid ) {
1071
+ if (this.liquidFlow) {
1072
+ //Converts given HTML content to plain text string.
1073
+ const content = convert(
1074
+ this.state.formData?.base?.en?.["template-content"]
1075
+ );
1076
+ /*
1077
+ The `handleResult` function is used as a callback for `getLiquidTags` to handle the results post-processing.
1078
+ It checks for errors and unsupported tags in the fetched liquid tags, displays any necessary validation messages,
1079
+ and proceeds with form submission if all checks pass.
1080
+ */
1081
+ const handleResult = (result) => {
1082
+ const {formatMessage} = this.props.intl;
1083
+ // Checks for errors in the result and displays them if any are found.
1084
+ if (result?.errors?.length > 0) {
1085
+ this.setState((prevState) => ({
1086
+ liquidErrorMessage: {
1087
+ ...prevState.liquidErrorMessage,
1088
+ LIQUID_ERROR_MSG: result?.errors?.map(error => error?.message) ?? [formatMessage(messages.somethingWentWrong)],
1089
+ },
1090
+ }) , () => {
1091
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
1092
+ });
1093
+ this.props.stopValidation();
1094
+ return;
1095
+ }
1096
+ // Extracts the supported liquid tags from the given content and do the necessary checks.
1097
+ else {
1098
+ const extractedLiquidTags = extractNames(result?.data || []);
1099
+ // Extracts the supported liquid tags from the given content.
1100
+ const supportedLiquidTags = checkSupport(
1101
+ result,
1102
+ this.props?.metaEntities?.tagLookupMap
1103
+ );
1104
+ const unsupportedLiquidTags = extractedLiquidTags?.filter(
1105
+ (tag) => !supportedLiquidTags?.includes(tag) && !this.skipTags(tag)
1106
+ );
1107
+ if (
1108
+ unsupportedLiquidTags?.length > 0
1109
+ ) {
1110
+ this.setState(
1111
+ (prevState) => ({
1112
+ liquidErrorMessage: {
1113
+ ...prevState.liquidErrorMessage,
1114
+ LIQUID_ERROR_MSG: [formatMessage(messages.unsupportedTagsValidationError, {
1115
+ unsupportedTags: unsupportedLiquidTags.join(", ")})],
1116
+ },
1117
+ }),
1118
+ () => {
1119
+ this.props.showLiquidErrorInFooter(
1120
+ this.state.liquidErrorMessage
1121
+ );
1122
+ }
1123
+ );
1124
+ this.props.stopValidation();
1125
+ return;
1126
+ }
1127
+ this.props.onSubmit(this.state.formData);
1128
+ }
1129
+ };
1130
+ this.props.actions.getLiquidTags(preprocessHtml(content), handleResult);
1131
+ } else {
1132
+ this.props.onSubmit(this.state.formData);
1133
+ }
1054
1134
  } else {
1055
1135
  this.setState({checkValidation: true});
1056
1136
  this.validateForm(null, null, true);
@@ -1092,7 +1172,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1092
1172
  });
1093
1173
 
1094
1174
  return result;
1095
- }
1175
+ };
1096
1176
 
1097
1177
  skipTags(tag) {
1098
1178
  const regexGroups = ["dynamic_expiry_date_after_\\d+_days.FORMAT_\\d", "unsubscribe\\(#[a-zA-Z\\d]{6}\\)","Link_to_[a-zA-z]","SURVEY.*.TOKEN","^[A-Za-z].*\\([a-zA-Z\\d]*\\)"];
@@ -1151,12 +1231,12 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1151
1231
  if (isEmailUnsubscribeTagMandatory() && isEmail && this.props?.moduleType === OUTBOUND) {
1152
1232
  const missingTagIndex = response?.missingTags?.indexOf("unsubscribe");
1153
1233
  if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
1154
- response?.missingTags?.splice(missingTagIndex, 1);
1155
- if (validString) {
1156
- response.valid = true;
1157
- } else {
1158
- response.isContentEmpty = true;
1159
- }
1234
+ response?.missingTags?.splice(missingTagIndex, 1);
1235
+ }
1236
+ if (validString) {
1237
+ response.valid = true;
1238
+ } else {
1239
+ response.isContentEmpty = true;
1160
1240
  }
1161
1241
  }
1162
1242
  while (match !== null ) {
@@ -1183,11 +1263,12 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1183
1263
  }
1184
1264
  }
1185
1265
  ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
1186
- if (!ifSupported) {
1266
+ if (!ifSupported && !this.liquidFlow) {
1187
1267
  response.unsupportedTags.push(tagValue);
1188
1268
  response.valid = false;
1189
1269
  }
1190
- if (response.unsupportedTags.length == 0 && response.missingTags.length == 0 ) {
1270
+
1271
+ if (response?.unsupportedTags?.length == 0 && response?.missingTags?.length == 0 ) {
1191
1272
  response.valid = true;
1192
1273
  }
1193
1274
  }
@@ -3338,7 +3419,11 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3338
3419
  const currentLang = ((!_.isEmpty(this.state.formData) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`]) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages) && this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndex]) ? this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndex] : this.props.baseLanguage) || 'en';
3339
3420
  const content = (!_.isEmpty(this.state.formData) && this.state.formData[`${this.state.currentTab - 1}`] && this.state.formData[`${this.state.currentTab - 1}`][currentLang] && this.state.formData[`${this.state.currentTab - 1}`][currentLang]['template-content'] || '');
3340
3421
  columns.push(
3341
- <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width}>
3422
+ <CapColumn
3423
+ style={val.colStyle ? val.colStyle : {border : ""}}
3424
+ span={val.width}
3425
+ className={`${(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.liquidFlow && "error-boundary"} `}
3426
+ >
3342
3427
  <CKEditor
3343
3428
  id={val.id}
3344
3429
  content={content}
@@ -3381,7 +3466,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3381
3466
  isModuleFilterEnabled = this.props.isFullMode;
3382
3467
  }
3383
3468
  columns.push(
3384
- <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width}>
3469
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={`${(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.liquidFlow && "error-boundary"}`}>
3385
3470
  <BeeEditor
3386
3471
  uid={uuid}
3387
3472
  tokenData={beeToken}
@@ -3485,7 +3570,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3485
3570
  fields.push(row);
3486
3571
  }
3487
3572
  });
3488
- if (!this.props.isEmailLoading && this.props.schema.channel.toUpperCase() === 'EMAIL' && !fields.length) {
3573
+ if (!this.props.isEmailLoading && this.props.schema.channel.toUpperCase() === EMAIL && !fields.length) {
3489
3574
  return (
3490
3575
  <div style={{ textAlign: 'center' }}>
3491
3576
  <CapSpin spinning />
@@ -3666,30 +3751,32 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3666
3751
 
3667
3752
 
3668
3753
  return (
3754
+ <CapSpin spinning={this.liquidFlow && this.props.liquidExtractionInProgress} tip={this.props.intl.formatMessage(messages.liquidSpinText)} >
3669
3755
  <CapRow>
3670
- {
3671
- this.props.schema ? this.renderForm() : ''
3672
- }
3756
+ {this.props.schema && this.renderForm()}
3673
3757
  <SlideBox
3674
- header={(<h3>{this.props.intl.formatMessage(messages.layoutSelection)}</h3>)}
3758
+ header={
3759
+ <h3>{this.props.intl.formatMessage(messages.layoutSelection)}</h3>
3760
+ }
3675
3761
  width={60}
3676
3762
  content={cmsTemplateSelectionContent}
3677
3763
  show={this.props.showEdmEmailTemplates}
3678
3764
  handleClose={this.props.toggleEdmEmailTemplateSelection}
3679
- loadingText={this.props.intl.formatMessage(messages.loadingEDMTemplates)}
3765
+ loadingText={this.props.intl.formatMessage(
3766
+ messages.loadingEDMTemplates
3767
+ )}
3680
3768
  isLoading={this.props.getCmsTemplatesInProgress}
3681
3769
  />
3682
- {
3683
- this.props.capDrawerContent && (
3684
- <CapDrawer
3685
- content={this.props.capDrawerContent}
3686
- visible={this.state.isDrawerVisible}
3687
- width={380}
3688
- onClose={() => this.props.setDrawerVisibility(false)}
3689
- />
3690
- )
3691
- }
3770
+ {this.props.capDrawerContent && (
3771
+ <CapDrawer
3772
+ content={this.props.capDrawerContent}
3773
+ visible={this.state.isDrawerVisible}
3774
+ width={380}
3775
+ onClose={() => this.props.setDrawerVisibility(false)}
3776
+ />
3777
+ )}
3692
3778
  </CapRow>
3779
+ </CapSpin>
3693
3780
  );
3694
3781
  }
3695
3782
  }
@@ -3697,6 +3784,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3697
3784
  FormBuilder.defaultProps = {
3698
3785
  isNewVersionFlow: false,
3699
3786
  userLocale: localStorage.getItem('jlocale') || 'en',
3787
+ showLiquidErrorInFooter: () => {},
3700
3788
  };
3701
3789
 
3702
3790
  FormBuilder.propTypes = {
@@ -3743,10 +3831,20 @@ FormBuilder.propTypes = {
3743
3831
  capDrawerContent: PropTypes.array,
3744
3832
  isFullMode: PropTypes.bool,
3745
3833
  currentOrgDetails: PropTypes.object,
3834
+ liquidExtractionInProgress: PropTypes.bool,
3835
+ showLiquidErrorInFooter: PropTypes.func,
3746
3836
  };
3747
3837
 
3748
3838
  const mapStateToProps = createStructuredSelector({
3749
3839
  currentOrgDetails: selectCurrentOrgDetails(),
3840
+ liquidExtractionInProgress: selectLiquidStateDetails(),
3841
+ metaEntities: makeSelectMetaEntities(),
3750
3842
  });
3751
3843
 
3752
- export default connect(mapStateToProps)(injectIntl(FormBuilder));
3844
+ function mapDispatchToProps(dispatch) {
3845
+ return {
3846
+ actions: bindActionCreators(actions, dispatch),
3847
+ };
3848
+ }
3849
+
3850
+ export default connect(mapStateToProps,mapDispatchToProps)(injectIntl(FormBuilder));
@@ -90,4 +90,12 @@ export default defineMessages({
90
90
  id: 'creatives.componentsV2.FormBuilder.emailBodyEmptyError',
91
91
  defaultMessage: 'Email body cannot be empty',
92
92
  },
93
+ somethingWentWrong: {
94
+ id: 'creatives.componentsV2.FormBuilder.somethingWentWrong',
95
+ defaultMessage: 'Something went wrong while validating content, please try again later',
96
+ },
97
+ liquidSpinText:{
98
+ id: 'creatives.componentsV2.FormBuilder.liquidSpinText',
99
+ defaultMessage: 'Validating the template, it might take a few seconds',
100
+ },
93
101
  });
@@ -77,3 +77,11 @@ export function clearTopbarMenuData() {
77
77
  export const getSupportVideosConfig = () => ({
78
78
  type: types.GET_SUPPORT_VIDEOS_CONFIG_REQUEST,
79
79
  });
80
+
81
+ export const getLiquidTags = (data,callback) => {
82
+ return{
83
+ type: types.GET_LIQUID_TAGS_REQUEST,
84
+ data,
85
+ callback,
86
+ };
87
+ };
@@ -59,3 +59,7 @@ export const FAILURE = 'FAILURE';
59
59
  export const ENABLE_PRODUCT_SUPPORT_VIDEOS = 'ENABLE_PRODUCT_SUPPORT_VIDEOS';
60
60
  export const DEFAULT = 'default';
61
61
  export const DEFAULT_MODULE = 'creatives';
62
+
63
+ export const GET_LIQUID_TAGS_FAILURE = 'cap/GET_LIQUID_TAGS_FAILURE_V2';
64
+ export const GET_LIQUID_TAGS_REQUEST = 'cap/GET_LIQUID_TAGS_REQUEST_V2';
65
+ export const GET_LIQUID_TAGS_SUCCESS = 'cap/GET_LIQUID_TAGS_SUCCESS_V2';
@@ -0,0 +1,74 @@
1
+ export const expectedStateGetLiquidTagsRequest = {
2
+ fetchingLiquidTags: true,
3
+ fetchingSchema: true,
4
+ fetchingSchemaError: "",
5
+ liquidTags: [],
6
+ messages: [],
7
+ metaEntities: {
8
+ layouts: [],
9
+ tagLookupMap: {},
10
+ tags: []
11
+ },
12
+ orgID: "",
13
+ token: ""
14
+ };
15
+
16
+ export const expectedStateGetLiquidTagsFailure = {
17
+ fetchingLiquidTags: false,
18
+ fetchingSchema: true,
19
+ fetchingSchemaError: "",
20
+ liquidTags: [],
21
+ messages: [],
22
+ metaEntities: {
23
+ layouts: [],
24
+ tagLookupMap: {},
25
+ tags: []
26
+ },
27
+ orgID: "",
28
+ token: ""
29
+ };
30
+
31
+ export const expectedStateGetLiquidTagsSuccess = {
32
+ fetchingLiquidTags: false,
33
+ fetchingSchema: true,
34
+ fetchingSchemaError: "",
35
+ liquidTags: [],
36
+ messages: [],
37
+ metaEntities: {
38
+ layouts: [],
39
+ tagLookupMap: {},
40
+ tags: []
41
+ },
42
+ orgID: "",
43
+ token: ""
44
+ };
45
+
46
+ export const expectedStateGetSchemaForEntitySuccessTAG = {
47
+ fetchingLiquidTags: false,
48
+ fetchingSchema: false,
49
+ fetchingSchemaError: false,
50
+ liquidTags: [],
51
+ messages: [],
52
+ metaEntities: {
53
+ layouts: undefined,
54
+ tagLookupMap: { undefined: "32" },
55
+ tags: { standard: { random: "32" } }
56
+ },
57
+ orgID: "",
58
+ token: ""
59
+ };
60
+
61
+ export const expectedStateGetSchemaForEntitySuccess = {
62
+ fetchingLiquidTags: false,
63
+ fetchingSchema: false,
64
+ fetchingSchemaError: false,
65
+ liquidTags: [],
66
+ messages: [],
67
+ metaEntities: {
68
+ layouts: undefined,
69
+ tagLookupMap: undefined,
70
+ tags: undefined
71
+ },
72
+ orgID: "",
73
+ token: ""
74
+ };
@@ -6,6 +6,7 @@ import _ from 'lodash';
6
6
  import * as types from './constants';
7
7
  import initialState from '../../initialState';
8
8
  import { FAILURE } from '../App/constants';
9
+ import { TAG } from '../Whatsapp/constants';
9
10
 
10
11
  function capReducer(state = fromJS(initialState.cap), action) {
11
12
  switch (action.type) {
@@ -85,13 +86,24 @@ function capReducer(state = fromJS(initialState.cap), action) {
85
86
  return state
86
87
  .set('fetchingSchema', false)
87
88
  .set('fetchingSchemaError', true);
89
+ case types.GET_LIQUID_TAGS_REQUEST:
90
+ return state
91
+ .set('fetchingLiquidTags', true)
92
+ case types.GET_LIQUID_TAGS_FAILURE:
93
+ return state
94
+ .set('fetchingLiquidTags', false)
95
+ case types.GET_LIQUID_TAGS_SUCCESS:
96
+ return state
97
+ .set('fetchingLiquidTags', false)
88
98
  case types.GET_SCHEMA_FOR_ENTITY_SUCCESS: {
89
99
  const stateMeta = state.get('metaEntities');
100
+ const standardTagMap = _.keyBy(action?.data?.metaEntities?.standard, item => item?.definition?.value);
90
101
  return state
91
102
  .set('fetchingSchema', false)
92
103
  .set('metaEntities', {
93
104
  layouts: action.data && action.entityType === 'LAYOUT' ? action.data.metaEntities : stateMeta.layouts,
94
105
  tags: action.data && action.entityType === 'TAG' ? action.data.metaEntities : stateMeta.tags,
106
+ tagLookupMap: action?.data && action?.entityType === TAG ? standardTagMap : stateMeta?.tagLookupMap,
95
107
  })
96
108
  .set('fetchingSchemaError', false);
97
109
  }
@@ -149,6 +149,30 @@ export function* fetchSchemaForEntity(queryParams) {
149
149
  }
150
150
  }
151
151
 
152
+ export function* getLiquidTags(action) {
153
+ try {
154
+ let result = yield call(Api.getLiquidTags, action?.data);
155
+ if (result) {
156
+ if (result?.errors?.length > 0) {
157
+ const getAskAiraErrorResponse = yield call(Api.askAiraForLiquid, {
158
+ templateContent: action?.data,
159
+ errorMessage: result?.errors?.[0]?.message,
160
+ });
161
+ if (getAskAiraErrorResponse?.result?.errors?.length) {
162
+ result = getAskAiraErrorResponse?.result;
163
+ }
164
+ }
165
+ if (action?.callback) {
166
+ yield call(action?.callback, result);
167
+ }
168
+ yield put({ type: types.GET_LIQUID_TAGS_SUCCESS });
169
+ } else {
170
+ yield put({ type: types.GET_LIQUID_TAGS_FAILURE });
171
+ }
172
+ } catch (error) {
173
+ yield put({ type: types.GET_LIQUID_TAGS_FAILURE });
174
+ }
175
+ }
152
176
  const getTopbarData = (parentModule) => {
153
177
  switch (parentModule) {
154
178
  case LOYALTY:
@@ -193,7 +217,9 @@ export function* getSupportVideosConfig({ callback }) {
193
217
  export function* watchFetchSchemaForEntity() {
194
218
  yield takeLatest(types.GET_SCHEMA_FOR_ENTITY_REQUEST, fetchSchemaForEntity);
195
219
  }
196
-
220
+ export function* watchLiquidEntity() {
221
+ yield takeLatest(types.GET_LIQUID_TAGS_REQUEST, getLiquidTags);
222
+ }
197
223
 
198
224
  function* watchForOrgChange() {
199
225
  yield takeLatest(types.SWITCH_ORG_REQUEST, switchOrg);
@@ -226,6 +252,7 @@ export default [
226
252
  watchFetchSchemaForEntity,
227
253
  watchGetTopbarMenuData,
228
254
  watchForGetVideosConfig,
255
+ watchLiquidEntity,
229
256
  ];
230
257
 
231
258
 
@@ -67,6 +67,10 @@ const selectCurrentOrgDetails = () => createSelector(
67
67
  return currentOrgDetails ? currentOrgDetails.toJS() : null;
68
68
  }
69
69
  );
70
+ const selectLiquidStateDetails = () => createSelector(
71
+ selectCapDomain,
72
+ (globalState) => globalState.get('fetchingLiquidTags')
73
+ );
70
74
 
71
75
  const makeSelectFetchingSchema = () => createSelector(
72
76
  selectCapDomain,
@@ -108,4 +112,5 @@ export {
108
112
  makeSelectLoyaltyPromotionDisplay,
109
113
  makeSelectFetchingSchemaError,
110
114
  makeSelectDemoVideoAndLink,
115
+ selectLiquidStateDetails,
111
116
  };
@@ -41,6 +41,7 @@ exports[`<Cap /> should render correct component 1`] = `
41
41
  "clearMetaEntities": [Function],
42
42
  "clearTopbarMenuData": [Function],
43
43
  "fetchSchemaForEntity": [Function],
44
+ "getLiquidTags": [Function],
44
45
  "getSupportVideosConfig": [Function],
45
46
  "getTopbarMenuData": [Function],
46
47
  "getUserData": [Function],