@capillarytech/creatives-library 8.0.279 → 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/v2Components/FormBuilder/index.js +16 -3
- package/v2Containers/CreativesContainer/SlideBoxContent.js +3 -2
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +4 -5
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +14 -12
- package/v2Containers/Facebook/Advertisement/index.js +1 -1
- package/v2Containers/Line/Container/_lineCreate.scss +0 -1
- package/v2Containers/Line/Container/style.js +1 -1
- package/v2Containers/MobilePush/Create/index.js +20 -10
- package/v2Containers/MobilePush/Edit/index.js +23 -7
- package/v2Containers/SmsTrai/Create/index.scss +1 -1
- package/v2Containers/SmsTrai/Edit/index.js +3 -9
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +86 -11682
- package/v2Containers/SmsTrai/Edit/tests/index.test.js +0 -5
- package/v2Containers/Viber/index.js +0 -7
- package/v2Containers/Viber/index.scss +1 -4
- package/v2Containers/Viber/style.js +2 -0
package/package.json
CHANGED
|
@@ -1519,8 +1519,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1519
1519
|
response.isBraceError = false;
|
|
1520
1520
|
response.isContentEmpty = false;
|
|
1521
1521
|
const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content ;
|
|
1522
|
-
|
|
1523
|
-
|
|
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) {
|
|
1524
1529
|
_.forEach(tags, (tag) => {
|
|
1525
1530
|
_.forEach(tag.definition.supportedModules, (module) => {
|
|
1526
1531
|
if (module.mandatory && (currentModule === module.context)) {
|
|
@@ -1531,6 +1536,14 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1531
1536
|
}
|
|
1532
1537
|
});
|
|
1533
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
|
+
}
|
|
1534
1547
|
const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
|
|
1535
1548
|
let match = regex.exec(content);
|
|
1536
1549
|
const regexImgSrc=/<img[^>]*\bsrc\s*=\s*"[^"]*{{customer_barcode}}[^"]*"/;
|
|
@@ -1539,7 +1552,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1539
1552
|
let matchCustomerBarcode = regexCustomerBarcode.exec(content);
|
|
1540
1553
|
// \S matches anything other than a space, a tab, a newline, or a carriage return.
|
|
1541
1554
|
const validString= /\S/.test(contentForValidation);
|
|
1542
|
-
if (isEmailUnsubscribeTagMandatory() && isEmail &&
|
|
1555
|
+
if (isEmailUnsubscribeTagMandatory() && isEmail && isModuleTypeOutbound) {
|
|
1543
1556
|
const missingTagIndex = response?.missingTags?.indexOf("unsubscribe");
|
|
1544
1557
|
if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
|
|
1545
1558
|
response?.missingTags?.splice(missingTagIndex, 1);
|
|
@@ -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"
|
|
@@ -710,13 +710,14 @@ const EmailHTMLEditor = (props) => {
|
|
|
710
710
|
// 2. Validate Unsubscribe Tag when feature is OFF (when flag is false we require unsubscribe)
|
|
711
711
|
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is true: do NOT validate for unsubscribe (aligned with FormBuilder).
|
|
712
712
|
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false: validate and require unsubscribe tag.
|
|
713
|
-
|
|
714
|
-
|
|
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) {
|
|
715
716
|
const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
|
|
716
717
|
const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
|
|
717
718
|
|
|
718
719
|
if (!hasUnsubscribeTag) {
|
|
719
|
-
|
|
720
|
+
setTagValidationError({ valid: false, missingTags: ['unsubscribe'] });
|
|
720
721
|
const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
|
|
721
722
|
const errorMessage = `${missingTagsMsg} unsubscribe`;
|
|
722
723
|
setTimeout(() => {
|
|
@@ -727,11 +728,9 @@ const EmailHTMLEditor = (props) => {
|
|
|
727
728
|
});
|
|
728
729
|
}, 0);
|
|
729
730
|
|
|
730
|
-
// Reset parent state so next click is detected as a change
|
|
731
731
|
if (onValidationFail) {
|
|
732
732
|
onValidationFail();
|
|
733
733
|
}
|
|
734
|
-
// Block save - unsubscribe tag is required when validation is enabled
|
|
735
734
|
return;
|
|
736
735
|
}
|
|
737
736
|
}
|
|
@@ -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
|
}
|
|
@@ -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}
|
|
@@ -106,6 +106,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
106
106
|
padding: ${CAP_SPACE_32} ${CAP_SPACE_24};
|
|
107
107
|
position: fixed;
|
|
108
108
|
bottom: 2rem;
|
|
109
|
+
margin-left: -2rem;
|
|
109
110
|
.ant-btn {
|
|
110
111
|
margin-right: ${CAP_SPACE_16};
|
|
111
112
|
}
|
|
@@ -687,24 +688,17 @@ export const SmsTraiEdit = (props) => {
|
|
|
687
688
|
<CapButton
|
|
688
689
|
onClick={handleTestAndPreview}
|
|
689
690
|
type="secondary"
|
|
690
|
-
className="create-msg"
|
|
691
|
+
className="create-msg create-dlt-msg"
|
|
691
692
|
>
|
|
692
693
|
<FormattedMessage {...messages.testAndPreviewButtonLabel} />
|
|
693
694
|
</CapButton>
|
|
694
695
|
<CapButton
|
|
695
696
|
onClick={isLiquidSupportFeatureEnabled ? onSubmitWrapper : onDoneCallback}
|
|
696
|
-
className="create-msg
|
|
697
|
+
className="create-msg"
|
|
697
698
|
disabled={isTagValidationError || (isLiquidSupportFeatureEnabled && !isObject(metaEntities?.tagLookupMap))}
|
|
698
699
|
>
|
|
699
700
|
<FormattedMessage {...messages.saveButtonLabel} />
|
|
700
701
|
</CapButton>
|
|
701
|
-
<CapButton
|
|
702
|
-
onClick={handleClose}
|
|
703
|
-
className="cancel-dlt-msg"
|
|
704
|
-
type="secondary"
|
|
705
|
-
>
|
|
706
|
-
<FormattedMessage {...messages.cancelButtonLabel} />
|
|
707
|
-
</CapButton>
|
|
708
702
|
</SMSTraiFooter>
|
|
709
703
|
<TestAndPreviewSlidebox
|
|
710
704
|
show={showTestAndPreviewSlidebox}
|