@capillarytech/creatives-library 8.0.9 → 8.0.11

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 (46) 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 +20 -0
  6. package/services/tests/api.test.js +148 -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 +171 -70
  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/Line/Container/sagas.js +1 -1
  39. package/v2Containers/MobilePush/Edit/constants.js +2 -0
  40. package/v2Containers/MobilePush/Edit/index.js +91 -24
  41. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +93 -0
  42. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +12 -0
  43. package/v2Containers/Viber/constants.js +1 -1
  44. package/v2Containers/Viber/index.js +19 -19
  45. package/v2Containers/Viber/messages.js +0 -4
  46. 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,11 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
95
98
  customPopoverVisible: false,
96
99
  isDrawerVisible: false,
97
100
  translationLang: 'en',
101
+ showLiquidValidation: false,
102
+ liquidErrorMessage: {
103
+ STANDARD_ERROR_MSG: [],
104
+ LIQUID_ERROR_MSG: [],
105
+ },
98
106
  };
99
107
  this.renderForm = this.renderForm.bind(this);
100
108
  this.updateFormData = this.updateFormData.bind(this);
@@ -126,6 +134,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
126
134
  this.transformInjectedTags = this.transformInjectedTags.bind(this);
127
135
  this.handleSetRadioValue = this.handleSetRadioValue.bind(this);
128
136
  this.formElements = [];
137
+ // Check if the liquid flow feature is supported and the channel is in the supported list.
138
+ this.liquidFlow = hasLiquidSupportFeature() && LIQUID_SUPPORTED_CHANNELS.includes(props?.schema?.channel?.toUpperCase());
129
139
  }
130
140
 
131
141
  componentWillMount() {
@@ -480,6 +490,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
480
490
  validateForm(tags, injectedTags, isSave, showMessages, onValidationComplete) {
481
491
  const channel = _.get(this, "props.schema.channel");
482
492
  let isValid = true;
493
+ let isLiquidValid = true;
483
494
  const type = (this.props.location && this.props.location.query.type) ? this.props.location.query.type : 'full';
484
495
  const currentModule = (this.props.location && this.props.location.query.module) ? this.props.location.query.module : 'default';
485
496
  let errorData = _.cloneDeep(this.state.errorData);
@@ -915,10 +926,10 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
915
926
  }
916
927
 
917
928
  // Form Data Validation for Email Channel
918
- if (this.props.schema && this.props.schema.channel && this.props.schema.channel.toUpperCase() === "EMAIL") {
929
+ if (this.props?.schema?.channel?.toUpperCase() === EMAIL) {
919
930
  // Validating Email By iterating all elements of FormBuilder
920
931
  const isEmail = true;
921
- _.forEach(this.state.formData, (data, index) => {
932
+ _.forEach(this.state.formData,(data, index) => {
922
933
 
923
934
  if (!this.state.formData["template-subject"] && type.toLowerCase() === 'embedded' && (currentModule.toLowerCase() === 'loyalty' || currentModule.toLowerCase() === 'dvs' || currentModule.toLowerCase() === 'timeline' || currentModule.toLowerCase() === 'library')) {
924
935
  errorData["template-subject"] = true;
@@ -963,37 +974,59 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
963
974
  if (!content) {
964
975
  return false;
965
976
  }
966
- const tagValidationResponse = this.validateTags(content, tags, injectedTags, isEmail);;
977
+ const tagValidationResponse = this.validateTags(content, tags, injectedTags, isEmail);
967
978
  if (errorData[index][currentLang] === undefined) {
968
979
  errorData[index][currentLang] = {};
969
980
  }
970
-
971
- if (tagValidationResponse.valid) {
981
+ if (tagValidationResponse?.valid) {
972
982
  errorData[index][currentLang]['template-content'] = false;
973
983
  } else {
974
984
  errorData[index][currentLang]['template-content'] = true;
975
985
  isValid = false;
976
- if (showMessages && !isNaN(index)) {
977
- if (tagValidationResponse.missingTags.length > 0 || tagValidationResponse.unsupportedTags.length > 0) {
986
+ isLiquidValid = false;
987
+ if ((showMessages && !isNaN(index)) || this.liquidFlow) {
988
+ if (tagValidationResponse?.missingTags?.length > 0 || tagValidationResponse?.unsupportedTags?.length > 0) {
978
989
  errorString += `${this.props.intl.formatMessage(messages.contentNotValidLanguage)} ${currentLang}\n`;
979
990
  }
980
- if (tagValidationResponse.missingTags.length > 0) {
991
+ if (tagValidationResponse?.missingTags?.length > 0) {
981
992
  errorString += `${this.props.intl.formatMessage(messages.missingTags)} ${tagValidationResponse.missingTags.toString()}\n`;
982
993
  }
983
- if (tagValidationResponse.unsupportedTags.length > 0) {
994
+ if (tagValidationResponse?.unsupportedTags?.length > 0) {
984
995
  errorString += `${this.props.intl.formatMessage(messages.unsupportedTags)} ${tagValidationResponse.unsupportedTags.toString()}\n`;
985
996
  }
986
- if (tagValidationResponse.isBraceError){
997
+ if (tagValidationResponse?.isBraceError){
987
998
  errorString += this.props.intl.formatMessage(globalMessages.unbalanacedCurlyBraces);
988
999
  }
989
1000
  if (tagValidationResponse?.isContentEmpty) {
990
1001
  errorString += this.props.intl.formatMessage(messages.emailBodyEmptyError);
1002
+ // Adds a bypass for cases where content is initially empty in the creation flow.
1003
+ if(this.liquidFlow && !this.props.isEdit){
1004
+ errorString = "";
1005
+ isValid = true;
1006
+ isLiquidValid = true;
1007
+ }
991
1008
  }
992
1009
  }
993
1010
  }
994
1011
  }
995
1012
  if (errorString !== "") {
996
- this.openNotificationWithIcon('error', errorString, "email-validation-error");
1013
+ if (this.liquidFlow) {
1014
+ this.setState(
1015
+ (prevState) => ({
1016
+ showLiquidValidation: true,
1017
+ liquidErrorMessage: {
1018
+ ...prevState.liquidErrorMessage,
1019
+ STANDARD_ERROR_MSG: [errorString],
1020
+ },
1021
+ }),
1022
+ () => {
1023
+ // Callback after the state is updated
1024
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
1025
+ }
1026
+ );
1027
+ } else {
1028
+ this.openNotificationWithIcon('error', errorString, "email-validation-error");
1029
+ }
997
1030
  }
998
1031
  _.forEach(errorData[index], (versionData, key) => {
999
1032
  if (data.selectedLanguages.indexOf(key) === -1) {
@@ -1002,32 +1035,20 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1002
1035
  });
1003
1036
  }
1004
1037
  });
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
1038
  }
1030
- this.setState({isFormValid : isValid, errorData}, () => {
1039
+ //Updating the state with the error data
1040
+ this.setState((prevState) => ({
1041
+ isFormValid: isValid,
1042
+ showLiquidValidation: prevState.liquidErrorMessage?.LIQUID_ERROR_MSG?.length > 0 ? true : !isLiquidValid,
1043
+ liquidErrorMessage: {
1044
+ //Do not update the LIQUID_ERROR_MSG validation error message
1045
+ ...prevState.liquidErrorMessage,
1046
+ //update Standard error message based on the updated content
1047
+ STANDARD_ERROR_MSG: isValid ? [] : prevState.liquidErrorMessage?.STANDARD_ERROR_MSG,
1048
+ },
1049
+ errorData,
1050
+ }), () => {
1051
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
1031
1052
  if (onValidationComplete) {
1032
1053
  onValidationComplete();
1033
1054
  }
@@ -1049,8 +1070,70 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1049
1070
  this.props.getValidationData();
1050
1071
  return;
1051
1072
  }
1052
- if (this.state.isFormValid) {
1053
- this.props.onSubmit(this.state.formData);
1073
+ if ( this.state.isFormValid ) {
1074
+ if (this.liquidFlow) {
1075
+ //Converts given HTML content to plain text string.
1076
+ const content = convert(
1077
+ this.state.formData?.base?.en?.["template-content"]
1078
+ );
1079
+ /*
1080
+ The `handleResult` function is used as a callback for `getLiquidTags` to handle the results post-processing.
1081
+ It checks for errors and unsupported tags in the fetched liquid tags, displays any necessary validation messages,
1082
+ and proceeds with form submission if all checks pass.
1083
+ */
1084
+ const handleResult = (result) => {
1085
+ const {formatMessage} = this.props.intl;
1086
+ if (result?.errors?.length > 0) {
1087
+ this.setState((prevState) => ({
1088
+ showLiquidValidation: true,
1089
+ liquidErrorMessage: {
1090
+ ...prevState.liquidErrorMessage,
1091
+ LIQUID_ERROR_MSG: result?.errors?.map(error => error?.message) ?? [formatMessage(messages.somethingWentWrong)],
1092
+ },
1093
+ }) , () => {
1094
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
1095
+ });
1096
+ this.props.stopValidation();
1097
+ return;
1098
+ }
1099
+ else if (result?.data?.length > 0) {
1100
+ const extractedLiquidTags = extractNames(result?.data);
1101
+ // Extracts the supported liquid tags from the given content.
1102
+ const supportedLiquidTags = checkSupport(
1103
+ result,
1104
+ this.props?.metaEntities?.tagLookupMap
1105
+ );
1106
+ const unsupportedLiquidTags = extractedLiquidTags?.filter(
1107
+ (tag) => !supportedLiquidTags?.includes(tag)
1108
+ );
1109
+ if (
1110
+ unsupportedLiquidTags?.length > 0
1111
+ ) {
1112
+ this.setState(
1113
+ (prevState) => ({
1114
+ showLiquidValidation: true,
1115
+ liquidErrorMessage: {
1116
+ ...prevState.liquidErrorMessage,
1117
+ LIQUID_ERROR_MSG: [formatMessage(messages.unsupportedTagsValidationError, {
1118
+ unsupportedTags: unsupportedLiquidTags.join(", ")})],
1119
+ },
1120
+ }),
1121
+ () => {
1122
+ this.props.showLiquidErrorInFooter(
1123
+ this.state.liquidErrorMessage
1124
+ );
1125
+ }
1126
+ );
1127
+ this.props.stopValidation();
1128
+ return;
1129
+ }
1130
+ this.props.onSubmit(this.state.formData);
1131
+ }
1132
+ };
1133
+ this.props.actions.getLiquidTags(preprocessHtml(content), handleResult);
1134
+ } else {
1135
+ this.props.onSubmit(this.state.formData);
1136
+ }
1054
1137
  } else {
1055
1138
  this.setState({checkValidation: true});
1056
1139
  this.validateForm(null, null, true);
@@ -1092,7 +1175,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1092
1175
  });
1093
1176
 
1094
1177
  return result;
1095
- }
1178
+ };
1096
1179
 
1097
1180
  skipTags(tag) {
1098
1181
  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 +1234,12 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1151
1234
  if (isEmailUnsubscribeTagMandatory() && isEmail && this.props?.moduleType === OUTBOUND) {
1152
1235
  const missingTagIndex = response?.missingTags?.indexOf("unsubscribe");
1153
1236
  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
- }
1237
+ response?.missingTags?.splice(missingTagIndex, 1);
1238
+ }
1239
+ if (validString) {
1240
+ response.valid = true;
1241
+ } else {
1242
+ response.isContentEmpty = true;
1160
1243
  }
1161
1244
  }
1162
1245
  while (match !== null ) {
@@ -1183,11 +1266,12 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1183
1266
  }
1184
1267
  }
1185
1268
  ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
1186
- if (!ifSupported) {
1269
+ if (!ifSupported && !this.liquidFlow) {
1187
1270
  response.unsupportedTags.push(tagValue);
1188
1271
  response.valid = false;
1189
1272
  }
1190
- if (response.unsupportedTags.length == 0 && response.missingTags.length == 0 ) {
1273
+
1274
+ if (response?.unsupportedTags?.length == 0 && response?.missingTags?.length == 0 ) {
1191
1275
  response.valid = true;
1192
1276
  }
1193
1277
  }
@@ -3338,7 +3422,11 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3338
3422
  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
3423
  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
3424
  columns.push(
3341
- <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width}>
3425
+ <CapColumn
3426
+ style={val.colStyle ? val.colStyle : {border : ""}}
3427
+ span={val.width}
3428
+ className={`${this.state.showLiquidValidation && this.liquidFlow && "error-boundary"} `}
3429
+ >
3342
3430
  <CKEditor
3343
3431
  id={val.id}
3344
3432
  content={content}
@@ -3381,7 +3469,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3381
3469
  isModuleFilterEnabled = this.props.isFullMode;
3382
3470
  }
3383
3471
  columns.push(
3384
- <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width}>
3472
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={`${this.state.showLiquidValidation && this.liquidFlow && "error-boundary"}`}>
3385
3473
  <BeeEditor
3386
3474
  uid={uuid}
3387
3475
  tokenData={beeToken}
@@ -3485,7 +3573,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3485
3573
  fields.push(row);
3486
3574
  }
3487
3575
  });
3488
- if (!this.props.isEmailLoading && this.props.schema.channel.toUpperCase() === 'EMAIL' && !fields.length) {
3576
+ if (!this.props.isEmailLoading && this.props.schema.channel.toUpperCase() === EMAIL && !fields.length) {
3489
3577
  return (
3490
3578
  <div style={{ textAlign: 'center' }}>
3491
3579
  <CapSpin spinning />
@@ -3666,30 +3754,32 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3666
3754
 
3667
3755
 
3668
3756
  return (
3757
+ <CapSpin spinning={this.liquidFlow && this.props.liquidExtractionInProgress} tip={this.props.intl.formatMessage(messages.liquidSpinText)} >
3669
3758
  <CapRow>
3670
- {
3671
- this.props.schema ? this.renderForm() : ''
3672
- }
3759
+ {this.props.schema && this.renderForm()}
3673
3760
  <SlideBox
3674
- header={(<h3>{this.props.intl.formatMessage(messages.layoutSelection)}</h3>)}
3761
+ header={
3762
+ <h3>{this.props.intl.formatMessage(messages.layoutSelection)}</h3>
3763
+ }
3675
3764
  width={60}
3676
3765
  content={cmsTemplateSelectionContent}
3677
3766
  show={this.props.showEdmEmailTemplates}
3678
3767
  handleClose={this.props.toggleEdmEmailTemplateSelection}
3679
- loadingText={this.props.intl.formatMessage(messages.loadingEDMTemplates)}
3768
+ loadingText={this.props.intl.formatMessage(
3769
+ messages.loadingEDMTemplates
3770
+ )}
3680
3771
  isLoading={this.props.getCmsTemplatesInProgress}
3681
3772
  />
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
- }
3773
+ {this.props.capDrawerContent && (
3774
+ <CapDrawer
3775
+ content={this.props.capDrawerContent}
3776
+ visible={this.state.isDrawerVisible}
3777
+ width={380}
3778
+ onClose={() => this.props.setDrawerVisibility(false)}
3779
+ />
3780
+ )}
3692
3781
  </CapRow>
3782
+ </CapSpin>
3693
3783
  );
3694
3784
  }
3695
3785
  }
@@ -3697,6 +3787,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3697
3787
  FormBuilder.defaultProps = {
3698
3788
  isNewVersionFlow: false,
3699
3789
  userLocale: localStorage.getItem('jlocale') || 'en',
3790
+ showLiquidErrorInFooter: () => {},
3700
3791
  };
3701
3792
 
3702
3793
  FormBuilder.propTypes = {
@@ -3743,10 +3834,20 @@ FormBuilder.propTypes = {
3743
3834
  capDrawerContent: PropTypes.array,
3744
3835
  isFullMode: PropTypes.bool,
3745
3836
  currentOrgDetails: PropTypes.object,
3837
+ liquidExtractionInProgress: PropTypes.bool,
3838
+ showLiquidErrorInFooter: PropTypes.func,
3746
3839
  };
3747
3840
 
3748
3841
  const mapStateToProps = createStructuredSelector({
3749
3842
  currentOrgDetails: selectCurrentOrgDetails(),
3843
+ liquidExtractionInProgress: selectLiquidStateDetails(),
3844
+ metaEntities: makeSelectMetaEntities(),
3750
3845
  });
3751
3846
 
3752
- export default connect(mapStateToProps)(injectIntl(FormBuilder));
3847
+ function mapDispatchToProps(dispatch) {
3848
+ return {
3849
+ actions: bindActionCreators(actions, dispatch),
3850
+ };
3851
+ }
3852
+
3853
+ 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],