@capillarytech/creatives-library 8.0.279 → 8.0.280
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 +2 -6
- package/utils/tests/tagValidations.test.js +1 -1
- package/v2Components/FormBuilder/index.js +5 -6
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +10 -33
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +30 -0
- package/v2Containers/Facebook/Advertisement/index.js +1 -1
- package/v2Containers/InApp/index.js +0 -2
- package/v2Containers/InappAdvance/index.js +0 -2
- package/v2Containers/Line/Container/_lineCreate.scss +0 -1
- package/v2Containers/Line/Container/style.js +1 -1
- 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
package/utils/tagValidations.js
CHANGED
|
@@ -201,7 +201,6 @@ 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
|
|
205
204
|
if (tags && tags.length && !isFullMode) {
|
|
206
205
|
lodashForEach(tags, ({
|
|
207
206
|
definition: {
|
|
@@ -218,9 +217,6 @@ export const validateTags = ({
|
|
|
218
217
|
}
|
|
219
218
|
});
|
|
220
219
|
});
|
|
221
|
-
}
|
|
222
|
-
// In library mode, always scan content for {{...}} and flag unsupported tags (even when tags list is empty)
|
|
223
|
-
if (!isFullMode && content) {
|
|
224
220
|
const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
|
|
225
221
|
let match = regex.exec(content);
|
|
226
222
|
while (match !== null) {
|
|
@@ -228,8 +224,8 @@ export const validateTags = ({
|
|
|
228
224
|
const tagIndex = match?.index;
|
|
229
225
|
match = regex.exec(content);
|
|
230
226
|
let ifSupported = false;
|
|
231
|
-
lodashForEach(tags
|
|
232
|
-
if (tag
|
|
227
|
+
lodashForEach(tags, (tag) => {
|
|
228
|
+
if (tag.definition.value === tagValue) {
|
|
233
229
|
ifSupported = true;
|
|
234
230
|
}
|
|
235
231
|
});
|
|
@@ -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([]);
|
|
108
108
|
expect(result.isBraceError).toEqual(false);
|
|
109
109
|
});
|
|
110
110
|
|
|
@@ -1247,11 +1247,10 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1247
1247
|
},
|
|
1248
1248
|
}),
|
|
1249
1249
|
() => {
|
|
1250
|
+
// Callback after the state is updated
|
|
1250
1251
|
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
|
|
1251
1252
|
}
|
|
1252
1253
|
);
|
|
1253
|
-
// Show toast for liquid flow too so user sees error (scenario 3)
|
|
1254
|
-
this.openNotificationWithIcon('error', errorString, "email-validation-error");
|
|
1255
1254
|
} else {
|
|
1256
1255
|
this.openNotificationWithIcon('error', errorString, "email-validation-error");
|
|
1257
1256
|
}
|
|
@@ -1519,8 +1518,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1519
1518
|
response.isBraceError = false;
|
|
1520
1519
|
response.isContentEmpty = false;
|
|
1521
1520
|
const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content ;
|
|
1522
|
-
|
|
1523
|
-
if(tags && tags.length && (!isFullMode || isEmail)) {
|
|
1521
|
+
if(tags && tags.length && !isFullMode) {
|
|
1524
1522
|
_.forEach(tags, (tag) => {
|
|
1525
1523
|
_.forEach(tag.definition.supportedModules, (module) => {
|
|
1526
1524
|
if (module.mandatory && (currentModule === module.context)) {
|
|
@@ -1583,8 +1581,9 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1583
1581
|
});
|
|
1584
1582
|
|
|
1585
1583
|
ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
|
|
1586
|
-
// Only add to unsupportedTags if not inside a {% ... %} block
|
|
1587
|
-
|
|
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)) {
|
|
1588
1587
|
response.unsupportedTags.push(tagValue);
|
|
1589
1588
|
response.valid = false;
|
|
1590
1589
|
}
|
|
@@ -108,24 +108,6 @@ 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
|
-
|
|
129
111
|
// Refs for tracking initialization and previous values
|
|
130
112
|
const contentInitializedRef = useRef(false);
|
|
131
113
|
const subjectInitializedRef = useRef(false);
|
|
@@ -497,7 +479,6 @@ const EmailHTMLEditor = (props) => {
|
|
|
497
479
|
location,
|
|
498
480
|
tagModule: getDefaultTags,
|
|
499
481
|
eventContextTags,
|
|
500
|
-
isFullMode,
|
|
501
482
|
});
|
|
502
483
|
|
|
503
484
|
if (!validationResult.valid) {
|
|
@@ -710,7 +691,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
710
691
|
// 2. Validate Unsubscribe Tag when feature is OFF (when flag is false we require unsubscribe)
|
|
711
692
|
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is true: do NOT validate for unsubscribe (aligned with FormBuilder).
|
|
712
693
|
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false: validate and require unsubscribe tag.
|
|
713
|
-
if (!
|
|
694
|
+
if (!isEmailUnsubscribeTagMandatory() && moduleType === OUTBOUND) {
|
|
714
695
|
// Check if content contains unsubscribe tag (either {{unsubscribe}} or {{unsubscribe(#...)})
|
|
715
696
|
const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
|
|
716
697
|
const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
|
|
@@ -719,13 +700,11 @@ const EmailHTMLEditor = (props) => {
|
|
|
719
700
|
// Show error notification
|
|
720
701
|
const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
|
|
721
702
|
const errorMessage = `${missingTagsMsg} unsubscribe`;
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
});
|
|
728
|
-
}, 0);
|
|
703
|
+
CapNotification.error({
|
|
704
|
+
message: 'ERROR ! ! !',
|
|
705
|
+
description: errorMessage,
|
|
706
|
+
duration: 5,
|
|
707
|
+
});
|
|
729
708
|
|
|
730
709
|
// Reset parent state so next click is detected as a change
|
|
731
710
|
if (onValidationFail) {
|
|
@@ -739,9 +718,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
739
718
|
// 3. Validate Content Tags
|
|
740
719
|
// For NON-liquid orgs: BLOCKING validation (matches CK/BEE behavior)
|
|
741
720
|
// For liquid orgs: Non-blocking (extractTags API will validate)
|
|
742
|
-
|
|
743
|
-
const shouldValidateTags = (tags.length > 0 || !isEmpty(injectedTags)) || (!isFullMode && !!htmlContent);
|
|
744
|
-
if (shouldValidateTags) {
|
|
721
|
+
if (tags.length > 0 || !isEmpty(injectedTags)) {
|
|
745
722
|
const validationResult = validateTags({
|
|
746
723
|
content: htmlContent,
|
|
747
724
|
tagsParam: tags,
|
|
@@ -749,12 +726,12 @@ const EmailHTMLEditor = (props) => {
|
|
|
749
726
|
location,
|
|
750
727
|
tagModule: getDefaultTags,
|
|
751
728
|
eventContextTags,
|
|
752
|
-
isFullMode,
|
|
753
729
|
});
|
|
754
730
|
|
|
755
731
|
const hasUnsupportedTags = validationResult?.unsupportedTags?.length > 0;
|
|
756
|
-
if (!validationResult?.valid ||
|
|
732
|
+
if (!validationResult?.valid || hasUnsupportedTags) {
|
|
757
733
|
setTagValidationError(validationResult);
|
|
734
|
+
|
|
758
735
|
// IMPORTANT: For non-liquid orgs, block save (like CK/BEE editor)
|
|
759
736
|
// For liquid orgs, continue (extractTags API will validate)
|
|
760
737
|
if (!isLiquidEnabled) {
|
|
@@ -1180,7 +1157,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
1180
1157
|
isFullMode={isFullMode}
|
|
1181
1158
|
onErrorAcknowledged={handleErrorAcknowledged}
|
|
1182
1159
|
onValidationChange={handleValidationChange}
|
|
1183
|
-
apiValidationErrors={
|
|
1160
|
+
apiValidationErrors={apiValidationErrors}
|
|
1184
1161
|
/>
|
|
1185
1162
|
</CapColumn>
|
|
1186
1163
|
</CapRow>
|
|
@@ -1016,6 +1016,36 @@ 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
|
+
|
|
1019
1049
|
it('allows save when unsubscribe validation is on and tag is present', () => {
|
|
1020
1050
|
isEmailUnsubscribeTagMandatory.mockReturnValue(false);
|
|
1021
1051
|
renderWithIntl({
|
|
@@ -1155,7 +1155,6 @@ export const InApp = (props) => {
|
|
|
1155
1155
|
location,
|
|
1156
1156
|
tagModule: getDefaultTags,
|
|
1157
1157
|
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1158
|
-
isFullMode,
|
|
1159
1158
|
}) || {};
|
|
1160
1159
|
|
|
1161
1160
|
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
@@ -1183,7 +1182,6 @@ export const InApp = (props) => {
|
|
|
1183
1182
|
location,
|
|
1184
1183
|
tagModule: getDefaultTags,
|
|
1185
1184
|
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1186
|
-
isFullMode,
|
|
1187
1185
|
}) || {};
|
|
1188
1186
|
|
|
1189
1187
|
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
@@ -878,7 +878,6 @@ export const InappAdvanced = (props) => {
|
|
|
878
878
|
location,
|
|
879
879
|
tagModule: getDefaultTags,
|
|
880
880
|
eventContextTags: metaEntities?.eventContextTags || [],
|
|
881
|
-
isFullMode,
|
|
882
881
|
}) || {};
|
|
883
882
|
|
|
884
883
|
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
@@ -906,7 +905,6 @@ export const InappAdvanced = (props) => {
|
|
|
906
905
|
location,
|
|
907
906
|
tagModule: getDefaultTags,
|
|
908
907
|
eventContextTags: metaEntities?.eventContextTags || [],
|
|
909
|
-
isFullMode,
|
|
910
908
|
}) || {};
|
|
911
909
|
|
|
912
910
|
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
@@ -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}
|