@capillarytech/creatives-library 8.0.309 → 8.0.310
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/constants/unified.js +1 -5
- package/initialState.js +2 -0
- package/package.json +1 -1
- package/services/api.js +0 -17
- package/services/tests/api.test.js +0 -85
- package/utils/common.js +8 -5
- package/utils/commonUtils.js +93 -46
- package/utils/tagValidations.js +223 -83
- package/utils/tests/commonUtil.test.js +124 -316
- package/utils/tests/tagValidations.test.js +358 -441
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +49 -78
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -134
- package/v2Components/CommonTestAndPreview/actions.js +0 -10
- package/v2Components/CommonTestAndPreview/constants.js +1 -15
- package/v2Components/CommonTestAndPreview/index.js +19 -80
- package/v2Components/CommonTestAndPreview/messages.js +0 -94
- package/v2Components/CommonTestAndPreview/reducer.js +0 -10
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +0 -53
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
- package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -36
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +0 -377
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
- package/v2Components/ErrorInfoNote/index.js +5 -2
- package/v2Components/FormBuilder/index.js +203 -137
- package/v2Components/FormBuilder/messages.js +8 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
- package/v2Containers/Cap/mockData.js +14 -0
- package/v2Containers/Cap/reducer.js +55 -3
- package/v2Containers/Cap/tests/reducer.test.js +102 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +1 -5
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -13
- package/v2Containers/CreativesContainer/constants.js +0 -6
- package/v2Containers/CreativesContainer/index.js +7 -47
- package/v2Containers/Email/index.js +5 -1
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +70 -23
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +120 -20
- package/v2Containers/FTP/index.js +51 -2
- package/v2Containers/FTP/messages.js +4 -0
- package/v2Containers/InApp/index.js +107 -35
- package/v2Containers/InApp/tests/index.test.js +6 -17
- package/v2Containers/InappAdvance/index.js +112 -4
- package/v2Containers/InappAdvance/tests/index.test.js +0 -2
- package/v2Containers/Line/Container/Text/index.js +1 -0
- package/v2Containers/MobilePush/Create/index.js +19 -59
- package/v2Containers/MobilePush/Edit/index.js +20 -48
- package/v2Containers/MobilePushNew/index.js +32 -12
- package/v2Containers/MobilepushWrapper/index.js +1 -3
- package/v2Containers/Rcs/index.js +37 -12
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1276 -1408
- package/v2Containers/Sms/Create/index.js +3 -39
- package/v2Containers/Sms/Create/messages.js +0 -4
- package/v2Containers/Sms/Edit/index.js +3 -35
- package/v2Containers/Sms/commonMethods.js +6 -3
- package/v2Containers/SmsTrai/Edit/index.js +47 -11
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +294 -327
- package/v2Containers/SmsWrapper/index.js +0 -2
- package/v2Containers/TemplatesV2/index.js +13 -28
- package/v2Containers/Viber/index.js +1 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +8 -17
- package/v2Containers/WebPush/Create/utils/validation.test.js +24 -44
- package/v2Containers/Whatsapp/index.js +17 -9
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5246
- package/v2Containers/Zalo/index.js +11 -3
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -284
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -72
- package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -657
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +0 -172
- package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -466
- package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
- package/v2Containers/Sms/tests/commonMethods.test.js +0 -122
package/constants/unified.js
CHANGED
|
@@ -43,6 +43,7 @@ export const HOSPITALITY_BASED_SCOPE = 'HOSPITALITY_BASED_SCOPE';
|
|
|
43
43
|
export const REGISTRATION_CUSTOM_FIELD = 'Registration custom fields';
|
|
44
44
|
export const GIFT_CARDS = 'GIFT_CARDS';
|
|
45
45
|
export const PROMO_ENGINE = 'PROMO_ENGINE';
|
|
46
|
+
export const LIQUID_SUPPORT = 'ENABLE_LIQUID_SUPPORT';
|
|
46
47
|
export const ENABLE_NEW_MPUSH = 'ENABLE_NEW_MPUSH';
|
|
47
48
|
export const ENABLE_NEW_EDITOR_FLOW_INAPP = 'ENABLE_NEW_EDITOR_FLOW_INAPP';
|
|
48
49
|
export const SUPPORT_CK_EDITOR = 'SUPPORT_CK_EDITOR';
|
|
@@ -150,11 +151,6 @@ export const BADGES_ENROLL = 'BADGES_ENROLL';
|
|
|
150
151
|
export const BADGES_ISSUE = 'BADGES_ISSUE';
|
|
151
152
|
export const CUSTOMER_BARCODE_TAG = 'customer_barcode';
|
|
152
153
|
export const COPY_OF = 'Copy of';
|
|
153
|
-
export const UNSUBSCRIBE_TAG = 'unsubscribe';
|
|
154
|
-
/** Whitespace-tolerant check for {{ unsubscribe }}-style tag in content. */
|
|
155
|
-
export const UNSUBSCRIBE_TAG_REGEX = new RegExp(`\\{\\{\\s*${UNSUBSCRIBE_TAG}\\s*\\}\\}`);
|
|
156
|
-
/** Matches {{ ... }} and captures the inner tag content (for scanning content for tags). */
|
|
157
|
-
export const TAG_CONTENT_REGEX = /{{([^}]+)}}/g;
|
|
158
154
|
export const ENTRY_TRIGGER_TAG_REGEX = /\bentryTrigger\.\w+(?:\.\w+)?(?:\(\w+\))?/g;
|
|
159
155
|
export const SKIP_TAGS_REGEX_GROUPS = ["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]*\\)", "referral_unique_(code|url).*userid"];
|
|
160
156
|
|
package/initialState.js
CHANGED
package/package.json
CHANGED
package/services/api.js
CHANGED
|
@@ -735,21 +735,4 @@ export const getBeePopupBuilderToken = () => {
|
|
|
735
735
|
return request(url, getAPICallObject('GET'));
|
|
736
736
|
};
|
|
737
737
|
|
|
738
|
-
/**
|
|
739
|
-
* Look up member by identifier (email or mobile) for add test customer flow.
|
|
740
|
-
* Uses standard API auth headers (no x-cap-ct) to avoid CORS preflight issues with node layer.
|
|
741
|
-
* @param {string} identifierType - 'email' or 'mobile'
|
|
742
|
-
* @param {string} identifierValue - email address or mobile number
|
|
743
|
-
* @returns {Promise} Promise resolving to { success, response: { exists, customerDetails } }
|
|
744
|
-
*/
|
|
745
|
-
export const getMembersLookup = (identifierType, identifierValue) => {
|
|
746
|
-
const url = `${API_ENDPOINT}/members?identifierType=${encodeURIComponent(identifierType)}&identifierValue=${encodeURIComponent(identifierValue)}`;
|
|
747
|
-
return request(url, getAPICallObject('GET'));
|
|
748
|
-
};
|
|
749
|
-
|
|
750
|
-
export const createTestCustomer = (payload) => {
|
|
751
|
-
const url = `${API_ENDPOINT}/testCustomers`;
|
|
752
|
-
return request(url, getAPICallObject('POST', payload));
|
|
753
|
-
};
|
|
754
|
-
|
|
755
738
|
export {request, getAPICallObject};
|
|
@@ -28,8 +28,6 @@ import {
|
|
|
28
28
|
getAssetStatus,
|
|
29
29
|
getBeePopupBuilderToken,
|
|
30
30
|
getCmsAccounts,
|
|
31
|
-
getMembersLookup,
|
|
32
|
-
createTestCustomer,
|
|
33
31
|
} from '../api';
|
|
34
32
|
import { mockData } from './mockData';
|
|
35
33
|
import getSchema from '../getSchema';
|
|
@@ -1009,86 +1007,3 @@ describe('getCmsAccounts', () => {
|
|
|
1009
1007
|
expect(result).toBeInstanceOf(Promise);
|
|
1010
1008
|
});
|
|
1011
1009
|
});
|
|
1012
|
-
|
|
1013
|
-
describe('getMembersLookup', () => {
|
|
1014
|
-
beforeEach(() => {
|
|
1015
|
-
global.fetch = jest.fn();
|
|
1016
|
-
global.fetch.mockReturnValue(Promise.resolve({
|
|
1017
|
-
status: 200,
|
|
1018
|
-
json: () => Promise.resolve({ success: true, response: { exists: false, customerDetails: [] } }),
|
|
1019
|
-
}));
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
it('should return a Promise', () => {
|
|
1023
|
-
const result = getMembersLookup('email', 'user@example.com');
|
|
1024
|
-
expect(result).toBeInstanceOf(Promise);
|
|
1025
|
-
});
|
|
1026
|
-
|
|
1027
|
-
it('should be callable with identifierType and identifierValue', () => {
|
|
1028
|
-
expect(typeof getMembersLookup).toBe('function');
|
|
1029
|
-
const result = getMembersLookup('mobile', '9123456789');
|
|
1030
|
-
expect(result).toBeInstanceOf(Promise);
|
|
1031
|
-
});
|
|
1032
|
-
|
|
1033
|
-
it('should call fetch with correct URL encoding and GET method', () => {
|
|
1034
|
-
global.fetch.mockClear();
|
|
1035
|
-
getMembersLookup('email', 'user+test@example.com');
|
|
1036
|
-
expect(global.fetch).toHaveBeenCalled();
|
|
1037
|
-
const calls = global.fetch.mock.calls;
|
|
1038
|
-
const withEncoding = calls.find(
|
|
1039
|
-
(c) =>
|
|
1040
|
-
c[0] &&
|
|
1041
|
-
String(c[0]).includes('members') &&
|
|
1042
|
-
String(c[0]).includes('identifierType=email') &&
|
|
1043
|
-
String(c[0]).includes('user%2Btest%40example.com')
|
|
1044
|
-
);
|
|
1045
|
-
if (withEncoding) {
|
|
1046
|
-
const [url, options] = withEncoding;
|
|
1047
|
-
expect(url).toContain('identifierType=email');
|
|
1048
|
-
expect(url).toContain('identifierValue=user%2Btest%40example.com');
|
|
1049
|
-
expect(options?.method || 'GET').toBe('GET');
|
|
1050
|
-
}
|
|
1051
|
-
const anyMembersCall = calls.find((c) => c[0] && String(c[0]).includes('members'));
|
|
1052
|
-
expect(anyMembersCall).toBeDefined();
|
|
1053
|
-
expect(anyMembersCall[0]).toContain('identifierType=');
|
|
1054
|
-
expect(anyMembersCall[0]).toContain('identifierValue=');
|
|
1055
|
-
expect(anyMembersCall[1]?.method || 'GET').toBe('GET');
|
|
1056
|
-
});
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
|
-
describe('createTestCustomer', () => {
|
|
1060
|
-
beforeEach(() => {
|
|
1061
|
-
global.fetch = jest.fn();
|
|
1062
|
-
global.fetch.mockReturnValue(Promise.resolve({
|
|
1063
|
-
status: 200,
|
|
1064
|
-
json: () => Promise.resolve({ success: true }),
|
|
1065
|
-
}));
|
|
1066
|
-
});
|
|
1067
|
-
|
|
1068
|
-
it('should return a Promise', () => {
|
|
1069
|
-
const payload = { customer: { email: 'test@example.com', firstName: 'Test' } };
|
|
1070
|
-
const result = createTestCustomer(payload);
|
|
1071
|
-
expect(result).toBeInstanceOf(Promise);
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
it('should accept customerId payload for existing customer', () => {
|
|
1075
|
-
const payload = { customerId: 'cust-123' };
|
|
1076
|
-
const result = createTestCustomer(payload);
|
|
1077
|
-
expect(result).toBeInstanceOf(Promise);
|
|
1078
|
-
expect(global.fetch).toHaveBeenCalled();
|
|
1079
|
-
const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
|
|
1080
|
-
expect(lastCall[0]).toContain('testCustomers');
|
|
1081
|
-
expect(lastCall[1].method).toBe('POST');
|
|
1082
|
-
expect(lastCall[1].body).toBe(JSON.stringify(payload));
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
|
-
it('should call fetch with /testCustomers URL and POST method', () => {
|
|
1086
|
-
const payload = { customer: { email: 'n@b.co', firstName: 'N' } };
|
|
1087
|
-
createTestCustomer(payload);
|
|
1088
|
-
expect(global.fetch).toHaveBeenCalled();
|
|
1089
|
-
const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
|
|
1090
|
-
expect(lastCall[0]).toContain('testCustomers');
|
|
1091
|
-
expect(lastCall[1].method).toBe('POST');
|
|
1092
|
-
expect(lastCall[1].body).toBe(JSON.stringify(payload));
|
|
1093
|
-
});
|
|
1094
|
-
});
|
package/utils/common.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
BADGES_ISSUE,
|
|
23
23
|
ENABLE_WECHAT,
|
|
24
24
|
ENABLE_WEBPUSH,
|
|
25
|
+
LIQUID_SUPPORT,
|
|
25
26
|
SUPPORT_CK_EDITOR,
|
|
26
27
|
ENABLE_NEW_MPUSH,
|
|
27
28
|
ENABLE_NEW_EDITOR_FLOW_INAPP
|
|
@@ -91,6 +92,12 @@ export const hasPromoFeature = Auth.hasFeatureAccess.bind(
|
|
|
91
92
|
null,
|
|
92
93
|
PROMO_ENGINE,
|
|
93
94
|
);
|
|
95
|
+
|
|
96
|
+
export const hasLiquidSupportFeature = Auth.hasFeatureAccess.bind(
|
|
97
|
+
null,
|
|
98
|
+
LIQUID_SUPPORT,
|
|
99
|
+
);
|
|
100
|
+
|
|
94
101
|
export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
|
|
95
102
|
null,
|
|
96
103
|
SUPPORT_CK_EDITOR,
|
|
@@ -126,11 +133,7 @@ export const hasCustomerBarcodeFeatureEnabled = Auth.hasFeatureAccess.bind(
|
|
|
126
133
|
ENABLE_CUSTOMER_BARCODE_TAG,
|
|
127
134
|
);
|
|
128
135
|
|
|
129
|
-
|
|
130
|
-
// When this flag is enabled for an org, the Unsubscribe tag is NOT mandatory in the email flow.
|
|
131
|
-
// This is as per the requirement in the tech doc:
|
|
132
|
-
// https://capillarytech.atlassian.net/wiki/spaces/CAM/pages/3941662838/Remove+mandate+for+Unsubscribe+tag+in+email+flow
|
|
133
|
-
export const isEmailUnsubscribeTagOptional = Auth.hasFeatureAccess.bind(
|
|
136
|
+
export const isEmailUnsubscribeTagMandatory = Auth.hasFeatureAccess.bind(
|
|
134
137
|
null,
|
|
135
138
|
EMAIL_UNSUBSCRIBE_TAG_MANDATORY,
|
|
136
139
|
);
|
package/utils/commonUtils.js
CHANGED
|
@@ -16,9 +16,8 @@ import {
|
|
|
16
16
|
IOS,
|
|
17
17
|
} from "../v2Containers/CreativesContainer/constants";
|
|
18
18
|
import { GLOBAL_CONVERT_OPTIONS } from "../v2Components/FormBuilder/constants";
|
|
19
|
+
import { checkSupport, extractNames, skipTags as defaultSkipTags, isInsideLiquidBlock } from "./tagValidations";
|
|
19
20
|
import { SMS_TRAI_VAR } from '../v2Containers/SmsTrai/Edit/constants';
|
|
20
|
-
import { EMAIL_REGEX, PHONE_REGEX } from '../v2Components/CommonTestAndPreview/constants';
|
|
21
|
-
|
|
22
21
|
export const apiMessageFormatHandler = (id, fallback) => (
|
|
23
22
|
<FormattedMessage id={id} defaultMessage={fallback} />
|
|
24
23
|
);
|
|
@@ -142,7 +141,12 @@ export const validateLiquidTemplateContent = async (
|
|
|
142
141
|
messages,
|
|
143
142
|
onError = () => {},
|
|
144
143
|
onSuccess = () => {},
|
|
144
|
+
tagLookupMap,
|
|
145
|
+
eventContextTags,
|
|
146
|
+
isLiquidFlow,
|
|
147
|
+
forwardedTags = {},
|
|
145
148
|
tabType,
|
|
149
|
+
skipTags = defaultSkipTags,
|
|
146
150
|
} = options;
|
|
147
151
|
const emptyBodyError = formatMessage(messages?.emailBodyEmptyError);
|
|
148
152
|
const somethingWrongMsg = formatMessage(messages?.somethingWentWrong);
|
|
@@ -200,6 +204,81 @@ export const validateLiquidTemplateContent = async (
|
|
|
200
204
|
});
|
|
201
205
|
return false;
|
|
202
206
|
}
|
|
207
|
+
// Extract and validate tags
|
|
208
|
+
const extractedLiquidTags = extractNames(result?.data || []);
|
|
209
|
+
// Get supported tags
|
|
210
|
+
const supportedLiquidTags = checkSupport(
|
|
211
|
+
result,
|
|
212
|
+
tagLookupMap,
|
|
213
|
+
eventContextTags,
|
|
214
|
+
isLiquidFlow,
|
|
215
|
+
forwardedTags
|
|
216
|
+
);
|
|
217
|
+
// Helper function to check if a tag appears only inside {% %} blocks
|
|
218
|
+
const isTagOnlyInsideLiquidBlocks = (tagName) => {
|
|
219
|
+
// Escape special regex characters in tag name, including dots
|
|
220
|
+
// Dots need to be escaped to match literally (item.name should match item.name, not item or name)
|
|
221
|
+
const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
222
|
+
|
|
223
|
+
// First, check if tag appears in {% %} syntax itself (like "order.items" in "{% for item in order.items %}")
|
|
224
|
+
// This is a tag used in Liquid logic, not output, so it should always be skipped
|
|
225
|
+
// Match the tag name as a whole (with dots escaped), optionally surrounded by word boundaries or non-word chars
|
|
226
|
+
// For tags with dots, we match them directly; for simple tags, we use word boundaries
|
|
227
|
+
const hasDot = tagName.includes('.');
|
|
228
|
+
const liquidSyntaxPattern = hasDot
|
|
229
|
+
? `{%[^%]*${escapedTagName}[^%]*%}`
|
|
230
|
+
: `{%[^%]*\\b${escapedTagName}\\b[^%]*%}`;
|
|
231
|
+
const liquidSyntaxRegex = new RegExp(liquidSyntaxPattern, 'g');
|
|
232
|
+
const liquidSyntaxMatches = Array.from(content.matchAll(liquidSyntaxRegex));
|
|
233
|
+
|
|
234
|
+
// Find all occurrences of {{tagName}} in the content (output tags)
|
|
235
|
+
// Match patterns like: {{tagName}}, {{ tagName }}, {{tagName }}, {{ tagName}}
|
|
236
|
+
// Use non-word-boundary approach for tags with dots (item.name should match item.name, not item or name separately)
|
|
237
|
+
const outputTagRegex = new RegExp(`\\{\\{\\s*${escapedTagName}\\s*\\}\\}`, 'g');
|
|
238
|
+
const outputTagMatches = Array.from(content.matchAll(outputTagRegex));
|
|
239
|
+
const outputTagPositions = outputTagMatches.map((match) => match.index);
|
|
240
|
+
|
|
241
|
+
// If tag appears in {% %} syntax, skip validation
|
|
242
|
+
if (liquidSyntaxMatches.length > 0) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// If no output tag matches found, don't skip validation
|
|
247
|
+
// The tag was extracted by the API, so it exists somewhere and should be validated
|
|
248
|
+
if (outputTagPositions.length === 0) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check if all output tag occurrences are inside {% %} blocks
|
|
253
|
+
// We check the position of {{ to see if it's inside a block
|
|
254
|
+
// Only skip validation if ALL occurrences are inside blocks
|
|
255
|
+
return outputTagPositions.every((tagIndex) => {
|
|
256
|
+
if (tagIndex === undefined || tagIndex === null) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
return isInsideLiquidBlock(content, tagIndex);
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Find unsupported tags, excluding those that are only inside {% %} blocks
|
|
264
|
+
const unsupportedLiquidTags = extractedLiquidTags?.filter(
|
|
265
|
+
(tag) => !supportedLiquidTags?.includes(tag)
|
|
266
|
+
&& !skipTags(tag)
|
|
267
|
+
&& !isTagOnlyInsideLiquidBlocks(tag)
|
|
268
|
+
);
|
|
269
|
+
// Handle unsupported tags
|
|
270
|
+
if (unsupportedLiquidTags?.length > 0) {
|
|
271
|
+
const errorMsg = formatMessage(messages.unsupportedTagsValidationError, {
|
|
272
|
+
unsupportedTags: unsupportedLiquidTags.join(", "),
|
|
273
|
+
});
|
|
274
|
+
onError({
|
|
275
|
+
standardErrors: [],
|
|
276
|
+
liquidErrors: [errorMsg],
|
|
277
|
+
tabType,
|
|
278
|
+
});
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
// All validations passed
|
|
203
282
|
onSuccess(content, tabType);
|
|
204
283
|
return true;
|
|
205
284
|
};
|
|
@@ -217,8 +296,8 @@ export const _validatePlatformSpecificContent = async (
|
|
|
217
296
|
...commonVltcOptions // Other options like getLiquidTags, formatMessage, messages, currentTab, etc.
|
|
218
297
|
} = options;
|
|
219
298
|
|
|
220
|
-
let isAndroidValid =
|
|
221
|
-
let isIosValid =
|
|
299
|
+
let isAndroidValid = false;
|
|
300
|
+
let isIosValid = false;
|
|
222
301
|
|
|
223
302
|
// This aggregator is passed to validateLiquidTemplateContent.
|
|
224
303
|
// It accumulates errors in the new structure and calls the parentOnError
|
|
@@ -286,8 +365,8 @@ export const _validatePlatformSpecificContent = async (
|
|
|
286
365
|
/**
|
|
287
366
|
* Validate Mobile Push content for both Android and iOS tabs
|
|
288
367
|
* @param {object} formData - Form data containing Android and iOS content
|
|
289
|
-
* @param {object} options - Options for validation
|
|
290
|
-
* @returns {Promise
|
|
368
|
+
* @param {object} options - Options for validation
|
|
369
|
+
* @returns {Promise} - Promise that resolves when validation completes
|
|
291
370
|
*/
|
|
292
371
|
export const validateMobilePushContent = async (formData, options) => {
|
|
293
372
|
const {
|
|
@@ -299,46 +378,22 @@ export const validateMobilePushContent = async (formData, options) => {
|
|
|
299
378
|
// Clear previous errors by calling the passed onError
|
|
300
379
|
onError({ standardErrors: [], liquidErrors: [] });
|
|
301
380
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
// New Mobile Push UI: both tabs use { title, message, buttons/ctas } — use extractContent().
|
|
305
|
-
const rawAndroid = formData?.[0];
|
|
306
|
-
const rawIos = formData?.[1];
|
|
307
|
-
const isObject = (v) => v != null && typeof v === 'object' && !Array.isArray(v);
|
|
308
|
-
const androidTab = isObject(rawAndroid) ? rawAndroid : {};
|
|
309
|
-
const iosTab = isObject(rawIos) ? rawIos : {};
|
|
310
|
-
const isOldUiShape = 'message-title' in androidTab || 'message-editor' in androidTab;
|
|
311
|
-
let androidContentForValidation;
|
|
312
|
-
let iosContentForValidation;
|
|
313
|
-
if (isOldUiShape) {
|
|
314
|
-
androidContentForValidation = [androidTab['message-title'], androidTab['message-editor']]
|
|
315
|
-
.filter(Boolean)
|
|
316
|
-
.join(' ');
|
|
317
|
-
iosContentForValidation = [iosTab['message-title2'], iosTab['message-editor2']]
|
|
318
|
-
.filter(Boolean)
|
|
319
|
-
.join(' ');
|
|
320
|
-
} else {
|
|
321
|
-
androidContentForValidation = extractContent(androidTab);
|
|
322
|
-
iosContentForValidation = extractContent(iosTab);
|
|
323
|
-
}
|
|
381
|
+
const androidContent = JSON.stringify(formData?.[0]);
|
|
382
|
+
const iosContent = JSON.stringify(formData?.[1]);
|
|
324
383
|
|
|
325
384
|
// Pass the original 'onError' from this function's arguments,
|
|
326
385
|
// 'currentTab', and other relevant options to the helper.
|
|
327
386
|
const overallSuccess = await _validatePlatformSpecificContent(
|
|
328
|
-
|
|
329
|
-
|
|
387
|
+
androidContent,
|
|
388
|
+
iosContent,
|
|
330
389
|
MOBILE_PUSH,
|
|
331
390
|
{ ...restOptions, currentTab, onError }
|
|
332
391
|
);
|
|
333
|
-
|
|
334
|
-
// Submit payload remains full form data (JSON string) for save.
|
|
335
|
-
const androidContentToSubmit = JSON.stringify(formData?.[0]);
|
|
336
|
-
const iosContentToSubmit = JSON.stringify(formData?.[1]);
|
|
337
392
|
const getContentToSubmit = () => {
|
|
338
|
-
if (currentTab === 1 &&
|
|
339
|
-
if (currentTab === 2 &&
|
|
340
|
-
if (
|
|
341
|
-
if (
|
|
393
|
+
if (currentTab === 1 && androidContent) return [androidContent, ANDROID?.toLowerCase()];
|
|
394
|
+
if (currentTab === 2 && iosContent) return [iosContent, IOS?.toLowerCase()];
|
|
395
|
+
if (androidContent) return [androidContent, ANDROID?.toLowerCase()];
|
|
396
|
+
if (iosContent) return [iosContent, IOS?.toLowerCase()];
|
|
342
397
|
return ["", ""];
|
|
343
398
|
};
|
|
344
399
|
if (overallSuccess) {
|
|
@@ -532,11 +587,3 @@ export const checkForPersonalizationTokens = (formData) => {
|
|
|
532
587
|
}
|
|
533
588
|
return false;
|
|
534
589
|
};
|
|
535
|
-
|
|
536
|
-
export const isValidEmail = (email) => EMAIL_REGEX.test(email);
|
|
537
|
-
export const isValidMobile = (mobile) => PHONE_REGEX.test(mobile);
|
|
538
|
-
|
|
539
|
-
export const formatPhoneNumber = (phone) => {
|
|
540
|
-
if (!phone) return '';
|
|
541
|
-
return String(phone).replace(/[^\d]/g, '');
|
|
542
|
-
};
|