@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 +1 -1
- package/utils/tagValidations.js +6 -2
- package/utils/tests/tagValidations.test.js +1 -1
- package/v2Components/FormBuilder/index.js +20 -6
- package/v2Containers/CreativesContainer/SlideBoxContent.js +3 -2
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +36 -14
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +0 -30
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +14 -12
- package/v2Containers/InApp/index.js +2 -0
- package/v2Containers/InappAdvance/index.js +2 -0
- package/v2Containers/MobilePush/Create/index.js +20 -10
- package/v2Containers/MobilePush/Edit/index.js +23 -7
package/package.json
CHANGED
package/utils/tagValidations.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 &&
|
|
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
|
|
1585
|
-
|
|
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:
|
|
686
|
-
|
|
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
|
-
|
|
695
|
-
|
|
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
|
-
|
|
720
|
+
setTagValidationError({ valid: false, missingTags: ['unsubscribe'] });
|
|
701
721
|
const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
|
|
702
722
|
const errorMessage = `${missingTagsMsg} unsubscribe`;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
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)
|
|
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.
|
|
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
|
|
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}
|