@capillarytech/creatives-library 8.0.280 → 8.0.281

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.280",
4
+ "version": "8.0.281",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -201,6 +201,7 @@ export const validateTags = ({
201
201
  unsupportedTags: [],
202
202
  isBraceError: false,
203
203
  };
204
+ // Mandatory-tags check: only when we have a tags list and are in library mode
204
205
  if (tags && tags.length && !isFullMode) {
205
206
  lodashForEach(tags, ({
206
207
  definition: {
@@ -217,6 +218,9 @@ export const validateTags = ({
217
218
  }
218
219
  });
219
220
  });
221
+ }
222
+ // In library mode, always scan content for {{...}} and flag unsupported tags (even when tags list is empty)
223
+ if (!isFullMode && content) {
220
224
  const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
221
225
  let match = regex.exec(content);
222
226
  while (match !== null) {
@@ -224,8 +228,8 @@ export const validateTags = ({
224
228
  const tagIndex = match?.index;
225
229
  match = regex.exec(content);
226
230
  let ifSupported = false;
227
- lodashForEach(tags, (tag) => {
228
- if (tag.definition.value === tagValue) {
231
+ lodashForEach(tags || [], (tag) => {
232
+ if (tag?.definition?.value === tagValue) {
229
233
  ifSupported = true;
230
234
  }
231
235
  });
@@ -104,7 +104,7 @@ describe("validateTags", () => {
104
104
  });
105
105
 
106
106
  expect(result.valid).toEqual(true);
107
- expect(result.unsupportedTags).toEqual([]);
107
+ expect(result.unsupportedTags).toEqual(["tag1", "tag2", "tag3"]);
108
108
  expect(result.isBraceError).toEqual(false);
109
109
  });
110
110
 
@@ -1247,10 +1247,11 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1247
1247
  },
1248
1248
  }),
1249
1249
  () => {
1250
- // Callback after the state is updated
1251
1250
  this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
1252
1251
  }
1253
1252
  );
1253
+ // Show toast for liquid flow too so user sees error (scenario 3)
1254
+ this.openNotificationWithIcon('error', errorString, "email-validation-error");
1254
1255
  } else {
1255
1256
  this.openNotificationWithIcon('error', errorString, "email-validation-error");
1256
1257
  }
@@ -1518,7 +1519,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1518
1519
  response.isBraceError = false;
1519
1520
  response.isContentEmpty = false;
1520
1521
  const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content ;
1521
- if(tags && tags.length && !isFullMode) {
1522
+ const isModuleTypeOutbound = (this.props?.moduleType || '').toUpperCase() === OUTBOUND;
1523
+ // Run tag validation (missing + unsupported): library mode, or full mode with liquid support, or
1524
+ // legacy Email (CK Editor) when unsubscribe is required (EMAIL_UNSUBSCRIBE_TAG_MANDATORY false) so missing-unsubscribe error shows
1525
+ const shouldRunTagValidation = !isFullMode
1526
+ || (isEmail && hasLiquidSupportFeature())
1527
+ || (isEmail && !isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound);
1528
+ if (tags && tags.length && !isFullMode) {
1522
1529
  _.forEach(tags, (tag) => {
1523
1530
  _.forEach(tag.definition.supportedModules, (module) => {
1524
1531
  if (module.mandatory && (currentModule === module.context)) {
@@ -1529,6 +1536,14 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1529
1536
  }
1530
1537
  });
1531
1538
  });
1539
+ // Legacy Email (CK Editor): when unsubscribe is required, ensure we validate it even if tag schema didn't mark it mandatory for this module
1540
+ if (isEmail && !isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound) {
1541
+ const hasUnsubscribeInContent = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g.test(content);
1542
+ if (!hasUnsubscribeInContent && response.missingTags.indexOf('unsubscribe') === -1) {
1543
+ response.valid = false;
1544
+ response.missingTags.push('unsubscribe');
1545
+ }
1546
+ }
1532
1547
  const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
1533
1548
  let match = regex.exec(content);
1534
1549
  const regexImgSrc=/<img[^>]*\bsrc\s*=\s*"[^"]*{{customer_barcode}}[^"]*"/;
@@ -1537,7 +1552,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1537
1552
  let matchCustomerBarcode = regexCustomerBarcode.exec(content);
1538
1553
  // \S matches anything other than a space, a tab, a newline, or a carriage return.
1539
1554
  const validString= /\S/.test(contentForValidation);
1540
- if (isEmailUnsubscribeTagMandatory() && isEmail && this.props?.moduleType === OUTBOUND) {
1555
+ if (isEmailUnsubscribeTagMandatory() && isEmail && isModuleTypeOutbound) {
1541
1556
  const missingTagIndex = response?.missingTags?.indexOf("unsubscribe");
1542
1557
  if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
1543
1558
  response?.missingTags?.splice(missingTagIndex, 1);
@@ -1581,9 +1596,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1581
1596
  });
1582
1597
 
1583
1598
  ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
1584
- // Only add to unsupportedTags if not inside a {% ... %} block and not in liquid flow
1585
- // Tags inside {% %} blocks can contain any dynamic tags and should not be validated
1586
- if (!ifSupported && !this.liquidFlow() && !isInsideLiquidBlock(content, tagIndex)) {
1599
+ // Only add to unsupportedTags if not inside a {% ... %} block (scenario 3: liquid orgs also get unsupported-tag errors)
1600
+ if (!ifSupported && !isInsideLiquidBlock(content, tagIndex)) {
1587
1601
  response.unsupportedTags.push(tagValue);
1588
1602
  response.valid = false;
1589
1603
  }
@@ -682,8 +682,9 @@ export function SlideBoxContent(props) {
682
682
  {(isEditEmailWithId || isEmailEditWithContent) && (
683
683
  (() => {
684
684
  const supportCKEditor = commonUtil.hasSupportCKEditor();
685
- // When supportCKEditor is true: Always use Email component (legacy flow)
686
- if (supportCKEditor || templateData?.is_drag_drop) {
685
+ // When supportCKEditor is true: Use Email component (legacy flow with CKEditor).
686
+ // When supportCKEditor is false: Always use EmailWrapper (BEE or HTML editor, never CKEditor).
687
+ if (supportCKEditor) {
687
688
  return (
688
689
  <Email
689
690
  key="cretives-container-email-edit"
@@ -108,6 +108,24 @@ const EmailHTMLEditor = (props) => {
108
108
  standardErrors: [],
109
109
  });
110
110
 
111
+ // Merge tag validation errors (unsupported/missing) into apiValidationErrors so they show in ValidationErrorDisplay
112
+ const mergedApiValidationErrors = useMemo(() => {
113
+ const tagMessages = [];
114
+ if (tagValidationError?.unsupportedTags?.length) {
115
+ tagMessages.push(`Unsupported tags are: ${tagValidationError.unsupportedTags.join(', ')}`);
116
+ }
117
+ if (tagValidationError?.missingTags?.length && !isEmailUnsubscribeTagMandatory()) {
118
+ tagMessages.push(`Missing tags are: ${tagValidationError.missingTags.join(', ')}`);
119
+ }
120
+ if (tagMessages.length === 0) {
121
+ return apiValidationErrors;
122
+ }
123
+ return {
124
+ liquidErrors: apiValidationErrors?.liquidErrors || [],
125
+ standardErrors: [...(apiValidationErrors?.standardErrors || []), ...tagMessages],
126
+ };
127
+ }, [apiValidationErrors, tagValidationError]);
128
+
111
129
  // Refs for tracking initialization and previous values
112
130
  const contentInitializedRef = useRef(false);
113
131
  const subjectInitializedRef = useRef(false);
@@ -479,6 +497,7 @@ const EmailHTMLEditor = (props) => {
479
497
  location,
480
498
  tagModule: getDefaultTags,
481
499
  eventContextTags,
500
+ isFullMode,
482
501
  });
483
502
 
484
503
  if (!validationResult.valid) {
@@ -691,26 +710,27 @@ const EmailHTMLEditor = (props) => {
691
710
  // 2. Validate Unsubscribe Tag when feature is OFF (when flag is false we require unsubscribe)
692
711
  // When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is true: do NOT validate for unsubscribe (aligned with FormBuilder).
693
712
  // When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false: validate and require unsubscribe tag.
694
- if (!isEmailUnsubscribeTagMandatory() && moduleType === OUTBOUND) {
695
- // Check if content contains unsubscribe tag (either {{unsubscribe}} or {{unsubscribe(#...)})
713
+ // Run for both library and full mode so liquid-enabled orgs also get the error (notification + ValidationErrorDisplay).
714
+ const isModuleTypeOutbound = (moduleType || '').toUpperCase() === OUTBOUND;
715
+ if (!isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound) {
696
716
  const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
697
717
  const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
698
718
 
699
719
  if (!hasUnsubscribeTag) {
700
- // Show error notification
720
+ setTagValidationError({ valid: false, missingTags: ['unsubscribe'] });
701
721
  const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
702
722
  const errorMessage = `${missingTagsMsg} unsubscribe`;
703
- CapNotification.error({
704
- message: 'ERROR ! ! !',
705
- description: errorMessage,
706
- duration: 5,
707
- });
723
+ setTimeout(() => {
724
+ CapNotification.error({
725
+ message: 'ERROR ! ! !',
726
+ description: errorMessage,
727
+ duration: 5,
728
+ });
729
+ }, 0);
708
730
 
709
- // Reset parent state so next click is detected as a change
710
731
  if (onValidationFail) {
711
732
  onValidationFail();
712
733
  }
713
- // Block save - unsubscribe tag is required when validation is enabled
714
734
  return;
715
735
  }
716
736
  }
@@ -718,7 +738,9 @@ const EmailHTMLEditor = (props) => {
718
738
  // 3. Validate Content Tags
719
739
  // For NON-liquid orgs: BLOCKING validation (matches CK/BEE behavior)
720
740
  // For liquid orgs: Non-blocking (extractTags API will validate)
721
- if (tags.length > 0 || !isEmpty(injectedTags)) {
741
+ // In library mode, always validate when there is content (even if tags list is empty)
742
+ const shouldValidateTags = (tags.length > 0 || !isEmpty(injectedTags)) || (!isFullMode && !!htmlContent);
743
+ if (shouldValidateTags) {
722
744
  const validationResult = validateTags({
723
745
  content: htmlContent,
724
746
  tagsParam: tags,
@@ -726,12 +748,12 @@ const EmailHTMLEditor = (props) => {
726
748
  location,
727
749
  tagModule: getDefaultTags,
728
750
  eventContextTags,
751
+ isFullMode,
729
752
  });
730
753
 
731
754
  const hasUnsupportedTags = validationResult?.unsupportedTags?.length > 0;
732
- if (!validationResult?.valid || hasUnsupportedTags) {
755
+ if (!validationResult?.valid || (hasUnsupportedTags && !isFullMode)) {
733
756
  setTagValidationError(validationResult);
734
-
735
757
  // IMPORTANT: For non-liquid orgs, block save (like CK/BEE editor)
736
758
  // For liquid orgs, continue (extractTags API will validate)
737
759
  if (!isLiquidEnabled) {
@@ -1157,7 +1179,7 @@ const EmailHTMLEditor = (props) => {
1157
1179
  isFullMode={isFullMode}
1158
1180
  onErrorAcknowledged={handleErrorAcknowledged}
1159
1181
  onValidationChange={handleValidationChange}
1160
- apiValidationErrors={apiValidationErrors}
1182
+ apiValidationErrors={mergedApiValidationErrors}
1161
1183
  />
1162
1184
  </CapColumn>
1163
1185
  </CapRow>
@@ -1016,36 +1016,6 @@ describe('EmailHTMLEditor', () => {
1016
1016
  }, { timeout: 3000 });
1017
1017
  });
1018
1018
 
1019
- it('blocks save when unsubscribe validation is on (flag false) and tag is missing', async () => {
1020
- // When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false we validate and require unsubscribe
1021
- isEmailUnsubscribeTagMandatory.mockReturnValue(false);
1022
- const onValidationFail = jest.fn();
1023
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
1024
-
1025
- // Set subject via input and content via HTMLEditor mock
1026
- const { rerender } = renderWithIntl({
1027
- onValidationFail,
1028
- isGetFormData: false,
1029
- moduleType: 'OUTBOUND',
1030
- });
1031
- const input = screen.getByTestId('subject-input');
1032
- fireEvent.change(input, { target: { value: 'Valid Subject' } });
1033
- // Trigger content change to set htmlContent
1034
- const changeButton = screen.getByTestId('trigger-content-change');
1035
- fireEvent.click(changeButton);
1036
- // Now trigger save
1037
- rerender(
1038
- <IntlProvider locale="en" messages={{}}>
1039
- <EmailHTMLEditor {...defaultProps} onValidationFail={onValidationFail} isGetFormData moduleType="OUTBOUND" />
1040
- </IntlProvider>
1041
- );
1042
-
1043
- await waitFor(() => {
1044
- expect(CapNotification.error).toHaveBeenCalled();
1045
- expect(onValidationFail).toHaveBeenCalled();
1046
- }, { timeout: 3000 });
1047
- });
1048
-
1049
1019
  it('allows save when unsubscribe validation is on and tag is present', () => {
1050
1020
  isEmailUnsubscribeTagMandatory.mockReturnValue(false);
1051
1021
  renderWithIntl({
@@ -206,7 +206,11 @@ const useEmailWrapper = ({
206
206
 
207
207
  // Only fetch if we have an ID, don't have template data yet, and not already loading
208
208
  if (hasParamsId && !hasTemplateDetails && !hasTemplateDataProp && !isTemplateLoading && emailActions?.getTemplateDetails) {
209
- const templateId = params?.id || location?.query?.id || location?.params?.id;
209
+ let templateId = params?.id || location?.query?.id || location?.params?.id;
210
+ if (!templateId && location?.pathname?.includes('/edit/')) {
211
+ const [, extractedId] = location.pathname.match(/\/edit\/([^/]+)/) || [];
212
+ if (extractedId) templateId = extractedId;
213
+ }
210
214
  if (templateId) {
211
215
  emailActions.getTemplateDetails(templateId, 'email');
212
216
  }
@@ -581,8 +585,6 @@ const useEmailWrapper = ({
581
585
  // CRITICAL: Only treat as edit mode if we have params.id (actual edit URL) or templateData prop
582
586
  // Don't use templateDetails existence alone, as it might persist from previous template
583
587
  const hasParamsId = params?.id || location?.query?.id || location?.params?.id || location?.pathname?.includes('/edit/');
584
- const hasTemplateDetails = Email?.templateDetails && !isEmpty(Email.templateDetails);
585
- const hasBEETemplate = Email?.BEETemplate && !isEmpty(Email.BEETemplate);
586
588
  const hasTemplateDataProp = templateData && !isEmpty(templateData);
587
589
  // CRITICAL: Consider it edit mode if we have params.id OR templateData prop (library mode)
588
590
  // This allows editor type determination even when template data is still loading
@@ -590,13 +592,14 @@ const useEmailWrapper = ({
590
592
 
591
593
  if (isEditMode) {
592
594
  // Edit mode: Determine editor based on template data
593
- // Check if template was created in BEE editor
594
595
  // Priority: Email.templateDetails > Email.BEETemplate > templateData prop
595
- const editTemplateData = Email?.templateDetails || Email?.BEETemplate || templateData;
596
-
596
+ // Use first non-empty source (empty array/object can appear while template is loading)
597
+ const editTemplateData = [Email?.templateDetails, Email?.BEETemplate, templateData].find(
598
+ (d) => d && !isEmpty(d)
599
+ ) || null;
597
600
  // Helper function to safely get is_drag_drop from various possible paths
598
601
  const getIsDragDrop = (data) => {
599
- if (!data) return false;
602
+ if (!data || isEmpty(data)) return false;
600
603
 
601
604
  // Check common paths for is_drag_drop
602
605
  // Path 1: versions.base.is_drag_drop (most common)
@@ -630,8 +633,10 @@ const useEmailWrapper = ({
630
633
  return false;
631
634
  };
632
635
 
633
- const isDragDrop = getIsDragDrop(editTemplateData);
634
-
636
+ // When template data is still loading (editTemplateData empty), trust editor prop so BEE templates open in BEE
637
+ const hasMeaningfulTemplateData = editTemplateData && !isEmpty(editTemplateData);
638
+ const isDragDrop = getIsDragDrop(editTemplateData)
639
+ || (!hasMeaningfulTemplateData && editor === 'BEE');
635
640
  // Check if BEE is enabled for org (equivalent to checkBeeEditorAllowedForLibrary)
636
641
  // For editor selection:
637
642
  // - In full mode: BEE is always enabled
@@ -643,7 +648,6 @@ const useEmailWrapper = ({
643
648
  || (editor === "BEE" && !isFullMode)
644
649
  || beeEnabledFromAPI
645
650
  || (isAPIResponsePending && isDragDrop && !isFullMode); // Optimistic: if template is BEE and API pending, allow BEE
646
-
647
651
  // If template was created in BEE AND BEE is enabled → open in BEE editor
648
652
  // Otherwise → open in HTML editor (fallback)
649
653
  // IMPORTANT: When supportCKEditor is false, default to HTML editor unless explicitly BEE
@@ -790,8 +794,6 @@ const useEmailWrapper = ({
790
794
  // In edit mode (when supportCKEditor is false), always show editor
791
795
  if (!supportCKEditorFlag && isEditMode) {
792
796
  // Check if it's explicitly BEE editor
793
- const isExplicitlyBEE = emailCreateMode === EMAIL_CREATE_MODES.DRAG_DROP
794
- || (emailProps?.editor === 'BEE' && emailProps?.selectedEditorMode === null);
795
797
  // Show editor for both BEE and HTML in edit mode
796
798
  return true;
797
799
  }
@@ -1155,6 +1155,7 @@ export const InApp = (props) => {
1155
1155
  location,
1156
1156
  tagModule: getDefaultTags,
1157
1157
  eventContextTags: metaEntities?.eventContextTags || [],
1158
+ isFullMode,
1158
1159
  }) || {};
1159
1160
 
1160
1161
  if (validationResponse?.unsupportedTags?.length > 0) {
@@ -1182,6 +1183,7 @@ export const InApp = (props) => {
1182
1183
  location,
1183
1184
  tagModule: getDefaultTags,
1184
1185
  eventContextTags: metaEntities?.eventContextTags || [],
1186
+ isFullMode,
1185
1187
  }) || {};
1186
1188
 
1187
1189
  if (validationResponse?.unsupportedTags?.length > 0) {
@@ -878,6 +878,7 @@ export const InappAdvanced = (props) => {
878
878
  location,
879
879
  tagModule: getDefaultTags,
880
880
  eventContextTags: metaEntities?.eventContextTags || [],
881
+ isFullMode,
881
882
  }) || {};
882
883
 
883
884
  if (validationResponse?.unsupportedTags?.length > 0) {
@@ -905,6 +906,7 @@ export const InappAdvanced = (props) => {
905
906
  location,
906
907
  tagModule: getDefaultTags,
907
908
  eventContextTags: metaEntities?.eventContextTags || [],
909
+ isFullMode,
908
910
  }) || {};
909
911
 
910
912
  if (validationResponse?.unsupportedTags?.length > 0) {
@@ -63,6 +63,8 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
63
63
  injectedTags: {},
64
64
  };
65
65
  }
66
+ this.hasFetchedInitialTagsRef = false;
67
+ this.lastFetchedTagContextRef = null;
66
68
  }
67
69
  componentWillMount = () => {
68
70
  if (this.props.route.name === 'view') {
@@ -132,16 +134,19 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
132
134
  }
133
135
  schema.standalone.sections.splice(1, 1);
134
136
  this.injectEvents(schema);
135
- const query = {
136
- layout: 'mobilepush',
137
- type: 'TAG',
138
- context: this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default',
139
- embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
140
- };
141
- if (this.props.getDefaultTags) {
142
- query.context = this.props.getDefaultTags;
137
+ if (!this.hasFetchedInitialTagsRef) {
138
+ this.hasFetchedInitialTagsRef = true;
139
+ const context = this.props.getDefaultTags
140
+ || (this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default');
141
+ this.lastFetchedTagContextRef = typeof context === 'string' ? context.toLowerCase() : context;
142
+ const query = {
143
+ layout: 'mobilepush',
144
+ type: 'TAG',
145
+ context: this.lastFetchedTagContextRef,
146
+ embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
147
+ };
148
+ this.props.globalActions.fetchSchemaForEntity(query);
143
149
  }
144
- this.props.globalActions.fetchSchemaForEntity(query);
145
150
  }
146
151
  };
147
152
  componentWillUnmount = () => {
@@ -1771,10 +1776,15 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
1771
1776
  this.injectEvents(schema);
1772
1777
  };
1773
1778
  handleOnTagsContextChange = (data) => {
1779
+ const context = (data || '').toLowerCase() === 'all' ? 'default' : (data || '').toLowerCase();
1780
+ if (this.lastFetchedTagContextRef === context) {
1781
+ return;
1782
+ }
1783
+ this.lastFetchedTagContextRef = context;
1774
1784
  const query = {
1775
1785
  layout: 'mobilepush',
1776
1786
  type: 'TAG',
1777
- context: (data || '').toLowerCase() === 'all' ? 'default' : (data || '').toLowerCase(),
1787
+ context,
1778
1788
  embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
1779
1789
  };
1780
1790
  this.props.globalActions.fetchSchemaForEntity(query);
@@ -68,6 +68,10 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
68
68
  this.getPrimaryCtaFields = getPrimaryCtaFields.bind(this);
69
69
  this.getSecondaryCtaFields = getSecondaryCtaFields.bind(this);
70
70
  this.getLinkTypeFields = getLinkTypeFields.bind(this);
71
+ // Guard: only one initial meta/TAG fetch (getTags can be invoked from multiple code paths)
72
+ this.hasFetchedInitialTagsRef = false;
73
+ // Guard: avoid duplicate fetch when multiple TagList instances trigger same context
74
+ this.lastFetchedTagContextRef = null;
71
75
  }
72
76
  componentWillMount() {
73
77
  this.props.actions.getWeCrmAccounts("mobilepush");
@@ -109,6 +113,10 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
109
113
  }
110
114
  }
111
115
  componentWillReceiveProps(nextProps) {
116
+ if (nextProps.params?.id !== this.props.params?.id) {
117
+ this.hasFetchedInitialTagsRef = false;
118
+ this.lastFetchedTagContextRef = null;
119
+ }
112
120
  if (nextProps.isGetFormData && !this.props.isFullMode) {
113
121
  nextProps.getFormLibraryData(this.getFormData());
114
122
  } else if (nextProps.isGetFormData && this.props.isGetFormData !== nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
@@ -167,7 +175,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
167
175
  };
168
176
  this.props.actions.getMobilepushTemplatesList('mobilepush', params);
169
177
  }
170
- if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && _.isEmpty(this.state.fullSchema) && _.isEmpty(this.state.formData)) {
178
+ if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && _.isEmpty(this.state.fullSchema)) {
171
179
  this.setState({fullSchema: nextProps.metaEntities.layouts[0].definition, schema: (nextProps.location.query.module === 'loyalty') ? nextProps.metaEntities.layouts[0].definition.textSchema : {}}, () => {
172
180
  this.handleEditSchemaOnPropsChange(nextProps, selectedWeChatAccount);
173
181
  const templateId = get(this, "props.params.id");
@@ -968,15 +976,19 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
968
976
  });
969
977
  };
970
978
  getTags = () => {
979
+ if (this.hasFetchedInitialTagsRef) {
980
+ return;
981
+ }
982
+ this.hasFetchedInitialTagsRef = true;
983
+ const context = this.props.getDefaultTags
984
+ || (this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default');
985
+ this.lastFetchedTagContextRef = typeof context === 'string' ? context.toLowerCase() : context;
971
986
  const query = {
972
987
  layout: 'mobilepush',
973
988
  type: 'TAG',
974
- context: this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default',
989
+ context: this.lastFetchedTagContextRef,
975
990
  embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
976
991
  };
977
- if (this.props.getDefaultTags) {
978
- query.context = this.props.getDefaultTags;
979
- }
980
992
  this.props.globalActions.fetchSchemaForEntity(query);
981
993
  }
982
994
  setModalContent = (type) => {
@@ -1967,10 +1979,15 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1967
1979
  this.setState({ schema });
1968
1980
  };
1969
1981
  handleOnTagsContextChange = (data) => {
1982
+ const context = (data || '').toLowerCase() === 'all' ? 'default' : (data || '').toLowerCase();
1983
+ if (this.lastFetchedTagContextRef === context) {
1984
+ return;
1985
+ }
1986
+ this.lastFetchedTagContextRef = context;
1970
1987
  const query = {
1971
1988
  layout: 'mobilepush',
1972
1989
  type: 'TAG',
1973
- context: (data || '').toLowerCase() === 'all' ? 'default' : (data || '').toLowerCase(),
1990
+ context,
1974
1991
  embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
1975
1992
  };
1976
1993
  this.props.globalActions.fetchSchemaForEntity(query);
@@ -2024,7 +2041,6 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2024
2041
  <CapRow>
2025
2042
  <CapColumn>
2026
2043
  <FormBuilder
2027
- key={!_.isEmpty(schema) ? 'has-schema' : 'no-schema'}
2028
2044
  channel={MOBILE_PUSH}
2029
2045
  schema={schema}
2030
2046
  showLiquidErrorInFooter={this.props.showLiquidErrorInFooter}