@capillarytech/creatives-library 8.0.126 → 8.0.127-alpha.1
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/containers/App/constants.js +1 -0
- package/index.html +3 -1
- package/package.json +1 -1
- package/services/api.js +4 -4
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
- package/tests/integration/TemplateCreation/api-response.js +5 -0
- package/tests/integration/TemplateCreation/msw-handler.js +42 -63
- package/utils/common.js +7 -0
- package/utils/commonUtils.js +2 -6
- package/utils/createPayload.js +272 -0
- package/utils/tests/createPayload.test.js +761 -0
- package/v2Components/CapImageUpload/index.js +59 -46
- package/v2Components/CapInAppCTA/index.js +1 -0
- package/v2Components/CapMpushCTA/constants.js +25 -0
- package/v2Components/CapMpushCTA/index.js +332 -0
- package/v2Components/CapMpushCTA/index.scss +95 -0
- package/v2Components/CapMpushCTA/messages.js +89 -0
- package/v2Components/CapTagList/index.js +177 -120
- package/v2Components/CapVideoUpload/constants.js +3 -0
- package/v2Components/CapVideoUpload/index.js +167 -110
- package/v2Components/CapVideoUpload/messages.js +16 -0
- package/v2Components/Carousel/index.js +15 -13
- package/v2Components/CustomerSearchSection/index.js +12 -7
- package/v2Components/ErrorInfoNote/style.scss +1 -0
- package/v2Components/MobilePushPreviewV2/index.js +37 -5
- package/v2Components/TemplatePreview/_templatePreview.scss +114 -72
- package/v2Components/TemplatePreview/assets/images/Android _ With date and time.svg +29 -0
- package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
- package/v2Components/TemplatePreview/assets/images/iOS _ With date and time.svg +26 -0
- package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
- package/v2Components/TemplatePreview/index.js +178 -50
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +169 -0
- package/v2Components/TestAndPreviewSlidebox/LeftPanelContent.js +95 -0
- package/v2Components/TestAndPreviewSlidebox/PreviewSection.js +69 -0
- package/v2Components/TestAndPreviewSlidebox/SendTestMessage.js +68 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +67 -246
- package/v2Components/TestAndPreviewSlidebox/tests/CustomValuesEditor.test.js +425 -0
- package/v2Components/TestAndPreviewSlidebox/tests/LeftPanelContent.test.js +400 -0
- package/v2Components/TestAndPreviewSlidebox/tests/SendTestMessage.test.js +448 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +9 -9
- package/v2Containers/CreativesContainer/index.js +191 -136
- package/v2Containers/Email/index.js +15 -2
- package/v2Containers/InApp/constants.js +1 -0
- package/v2Containers/InApp/index.js +13 -13
- package/v2Containers/MobilePush/Create/index.js +1 -0
- package/v2Containers/MobilePush/commonMethods.js +7 -14
- package/v2Containers/MobilePushNew/actions.js +116 -0
- package/v2Containers/MobilePushNew/components/CtaButtons.js +170 -0
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +754 -0
- package/v2Containers/MobilePushNew/components/PlatformContentFields.js +279 -0
- package/v2Containers/MobilePushNew/components/index.js +5 -0
- package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +779 -0
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +2114 -0
- package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +343 -0
- package/v2Containers/MobilePushNew/constants.js +115 -0
- package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1299 -0
- package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1223 -0
- package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +246 -0
- package/v2Containers/MobilePushNew/hooks/useUpload.js +726 -0
- package/v2Containers/MobilePushNew/index.js +2280 -0
- package/v2Containers/MobilePushNew/index.scss +308 -0
- package/v2Containers/MobilePushNew/messages.js +226 -0
- package/v2Containers/MobilePushNew/reducer.js +160 -0
- package/v2Containers/MobilePushNew/sagas.js +198 -0
- package/v2Containers/MobilePushNew/selectors.js +55 -0
- package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
- package/v2Containers/MobilePushNew/tests/sagas.test.js +863 -0
- package/v2Containers/MobilePushNew/tests/selectors.test.js +425 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +322 -0
- package/v2Containers/MobilePushNew/utils.js +33 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +5 -5
- package/v2Containers/TagList/index.js +56 -10
- package/v2Containers/Templates/_templates.scss +101 -1
- package/v2Containers/Templates/index.js +147 -35
- package/v2Containers/Templates/messages.js +8 -0
- package/v2Containers/Templates/sagas.js +2 -0
- package/v2Containers/Whatsapp/constants.js +1 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateLink,
|
|
3
|
+
validateExternalLink,
|
|
4
|
+
validateDeepLink,
|
|
5
|
+
} from "../utils";
|
|
6
|
+
import { isUrl } from "../../Line/Container/Wrapper/utils";
|
|
7
|
+
|
|
8
|
+
// Mock the isUrl utility function
|
|
9
|
+
jest.mock("../../Line/Container/Wrapper/utils", () => ({
|
|
10
|
+
isUrl: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe("utils.js", () => {
|
|
14
|
+
const mockFormatMessage = jest.fn();
|
|
15
|
+
const mockMessages = {
|
|
16
|
+
invalidUrl: {
|
|
17
|
+
id: "invalidUrl",
|
|
18
|
+
defaultMessage: "Invalid URL format",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
mockFormatMessage.mockImplementation((msg) => msg.defaultMessage);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("validateLink", () => {
|
|
28
|
+
describe("Valid scenarios", () => {
|
|
29
|
+
it("should return null for valid URL", () => {
|
|
30
|
+
isUrl.mockReturnValue(true);
|
|
31
|
+
|
|
32
|
+
const result = validateLink("https://example.com", mockFormatMessage, mockMessages);
|
|
33
|
+
|
|
34
|
+
expect(result).toBeNull();
|
|
35
|
+
expect(isUrl).toHaveBeenCalledWith("https://example.com");
|
|
36
|
+
expect(mockFormatMessage).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return null for valid URL with whitespace", () => {
|
|
40
|
+
isUrl.mockReturnValue(true);
|
|
41
|
+
|
|
42
|
+
const result = validateLink(" https://example.com ", mockFormatMessage, mockMessages);
|
|
43
|
+
|
|
44
|
+
expect(result).toBeNull();
|
|
45
|
+
expect(isUrl).toHaveBeenCalledWith("https://example.com");
|
|
46
|
+
expect(mockFormatMessage).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should return null for empty URL", () => {
|
|
50
|
+
const result = validateLink("", mockFormatMessage, mockMessages);
|
|
51
|
+
|
|
52
|
+
expect(result).toBeNull();
|
|
53
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
54
|
+
expect(mockFormatMessage).not.toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should return null for null URL", () => {
|
|
58
|
+
const result = validateLink(null, mockFormatMessage, mockMessages);
|
|
59
|
+
|
|
60
|
+
expect(result).toBeNull();
|
|
61
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
62
|
+
expect(mockFormatMessage).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should return null for undefined URL", () => {
|
|
66
|
+
const result = validateLink(undefined, mockFormatMessage, mockMessages);
|
|
67
|
+
|
|
68
|
+
expect(result).toBeNull();
|
|
69
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
70
|
+
expect(mockFormatMessage).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should return null for whitespace-only URL", () => {
|
|
74
|
+
const result = validateLink(" ", mockFormatMessage, mockMessages);
|
|
75
|
+
|
|
76
|
+
expect(result).toBeNull();
|
|
77
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
78
|
+
expect(mockFormatMessage).not.toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should return null for tab and newline whitespace", () => {
|
|
82
|
+
const result = validateLink("\t\n\r", mockFormatMessage, mockMessages);
|
|
83
|
+
|
|
84
|
+
expect(result).toBeNull();
|
|
85
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
86
|
+
expect(mockFormatMessage).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("Invalid scenarios", () => {
|
|
91
|
+
it("should return error message for invalid URL", () => {
|
|
92
|
+
isUrl.mockReturnValue(false);
|
|
93
|
+
|
|
94
|
+
const result = validateLink("invalid-url", mockFormatMessage, mockMessages);
|
|
95
|
+
|
|
96
|
+
expect(result).toBe("Invalid URL format");
|
|
97
|
+
expect(isUrl).toHaveBeenCalledWith("invalid-url");
|
|
98
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should return error message for invalid URL with whitespace", () => {
|
|
102
|
+
isUrl.mockReturnValue(false);
|
|
103
|
+
|
|
104
|
+
const result = validateLink(" invalid-url ", mockFormatMessage, mockMessages);
|
|
105
|
+
|
|
106
|
+
expect(result).toBe("Invalid URL format");
|
|
107
|
+
expect(isUrl).toHaveBeenCalledWith("invalid-url");
|
|
108
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should return error message for malformed URL", () => {
|
|
112
|
+
isUrl.mockReturnValue(false);
|
|
113
|
+
|
|
114
|
+
const result = validateLink("htp://malformed", mockFormatMessage, mockMessages);
|
|
115
|
+
|
|
116
|
+
expect(result).toBe("Invalid URL format");
|
|
117
|
+
expect(isUrl).toHaveBeenCalledWith("htp://malformed");
|
|
118
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should return error message for URL without protocol", () => {
|
|
122
|
+
isUrl.mockReturnValue(false);
|
|
123
|
+
|
|
124
|
+
const result = validateLink("example.com", mockFormatMessage, mockMessages);
|
|
125
|
+
|
|
126
|
+
expect(result).toBe("Invalid URL format");
|
|
127
|
+
expect(isUrl).toHaveBeenCalledWith("example.com");
|
|
128
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should handle special characters in invalid URL", () => {
|
|
132
|
+
isUrl.mockReturnValue(false);
|
|
133
|
+
|
|
134
|
+
const result = validateLink("http://ex@mple.com", mockFormatMessage, mockMessages);
|
|
135
|
+
|
|
136
|
+
expect(result).toBe("Invalid URL format");
|
|
137
|
+
expect(isUrl).toHaveBeenCalledWith("http://ex@mple.com");
|
|
138
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("Edge cases", () => {
|
|
143
|
+
it("should handle custom formatMessage function", () => {
|
|
144
|
+
const customFormatMessage = jest.fn().mockReturnValue("Custom error message");
|
|
145
|
+
isUrl.mockReturnValue(false);
|
|
146
|
+
|
|
147
|
+
const result = validateLink("invalid", customFormatMessage, mockMessages);
|
|
148
|
+
|
|
149
|
+
expect(result).toBe("Custom error message");
|
|
150
|
+
expect(customFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should handle different message object structure", () => {
|
|
154
|
+
const customMessages = {
|
|
155
|
+
invalidUrl: {
|
|
156
|
+
id: "custom.invalidUrl",
|
|
157
|
+
defaultMessage: "Custom invalid URL message",
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
isUrl.mockReturnValue(false);
|
|
161
|
+
|
|
162
|
+
const result = validateLink("invalid", mockFormatMessage, customMessages);
|
|
163
|
+
|
|
164
|
+
expect(result).toBe("Custom invalid URL message");
|
|
165
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(customMessages.invalidUrl);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should trim very long whitespace strings", () => {
|
|
169
|
+
const longWhitespace = " ".repeat(100);
|
|
170
|
+
|
|
171
|
+
const result = validateLink(longWhitespace, mockFormatMessage, mockMessages);
|
|
172
|
+
|
|
173
|
+
expect(result).toBeNull();
|
|
174
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("validateExternalLink", () => {
|
|
180
|
+
it("should call validateLink with correct parameters for valid URL", () => {
|
|
181
|
+
isUrl.mockReturnValue(true);
|
|
182
|
+
|
|
183
|
+
const result = validateExternalLink("https://external.com", mockFormatMessage, mockMessages);
|
|
184
|
+
|
|
185
|
+
expect(result).toBeNull();
|
|
186
|
+
expect(isUrl).toHaveBeenCalledWith("https://external.com");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("should call validateLink with correct parameters for invalid URL", () => {
|
|
190
|
+
isUrl.mockReturnValue(false);
|
|
191
|
+
|
|
192
|
+
const result = validateExternalLink("invalid-external", mockFormatMessage, mockMessages);
|
|
193
|
+
|
|
194
|
+
expect(result).toBe("Invalid URL format");
|
|
195
|
+
expect(isUrl).toHaveBeenCalledWith("invalid-external");
|
|
196
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should handle empty external link", () => {
|
|
200
|
+
const result = validateExternalLink("", mockFormatMessage, mockMessages);
|
|
201
|
+
|
|
202
|
+
expect(result).toBeNull();
|
|
203
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should handle null external link", () => {
|
|
207
|
+
const result = validateExternalLink(null, mockFormatMessage, mockMessages);
|
|
208
|
+
|
|
209
|
+
expect(result).toBeNull();
|
|
210
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should trim whitespace in external link", () => {
|
|
214
|
+
isUrl.mockReturnValue(true);
|
|
215
|
+
|
|
216
|
+
const result = validateExternalLink(" https://external.com ", mockFormatMessage, mockMessages);
|
|
217
|
+
|
|
218
|
+
expect(result).toBeNull();
|
|
219
|
+
expect(isUrl).toHaveBeenCalledWith("https://external.com");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("validateDeepLink", () => {
|
|
224
|
+
it("should call validateLink with correct parameters for valid deep link", () => {
|
|
225
|
+
isUrl.mockReturnValue(true);
|
|
226
|
+
|
|
227
|
+
const result = validateDeepLink("myapp://deeplink", mockFormatMessage, mockMessages);
|
|
228
|
+
|
|
229
|
+
expect(result).toBeNull();
|
|
230
|
+
expect(isUrl).toHaveBeenCalledWith("myapp://deeplink");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should call validateLink with correct parameters for invalid deep link", () => {
|
|
234
|
+
isUrl.mockReturnValue(false);
|
|
235
|
+
|
|
236
|
+
const result = validateDeepLink("invalid-deeplink", mockFormatMessage, mockMessages);
|
|
237
|
+
|
|
238
|
+
expect(result).toBe("Invalid URL format");
|
|
239
|
+
expect(isUrl).toHaveBeenCalledWith("invalid-deeplink");
|
|
240
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("should handle empty deep link", () => {
|
|
244
|
+
const result = validateDeepLink("", mockFormatMessage, mockMessages);
|
|
245
|
+
|
|
246
|
+
expect(result).toBeNull();
|
|
247
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should handle undefined deep link", () => {
|
|
251
|
+
const result = validateDeepLink(undefined, mockFormatMessage, mockMessages);
|
|
252
|
+
|
|
253
|
+
expect(result).toBeNull();
|
|
254
|
+
expect(isUrl).not.toHaveBeenCalled();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should trim whitespace in deep link", () => {
|
|
258
|
+
isUrl.mockReturnValue(true);
|
|
259
|
+
|
|
260
|
+
const result = validateDeepLink(" myapp://deeplink ", mockFormatMessage, mockMessages);
|
|
261
|
+
|
|
262
|
+
expect(result).toBeNull();
|
|
263
|
+
expect(isUrl).toHaveBeenCalledWith("myapp://deeplink");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should handle custom scheme deep links", () => {
|
|
267
|
+
isUrl.mockReturnValue(true);
|
|
268
|
+
|
|
269
|
+
const result = validateDeepLink("customscheme://action/path", mockFormatMessage, mockMessages);
|
|
270
|
+
|
|
271
|
+
expect(result).toBeNull();
|
|
272
|
+
expect(isUrl).toHaveBeenCalledWith("customscheme://action/path");
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("Function independence", () => {
|
|
277
|
+
it("should ensure validateExternalLink and validateDeepLink are independent", () => {
|
|
278
|
+
isUrl.mockReturnValue(true);
|
|
279
|
+
|
|
280
|
+
validateExternalLink("https://external.com", mockFormatMessage, mockMessages);
|
|
281
|
+
validateDeepLink("myapp://deeplink", mockFormatMessage, mockMessages);
|
|
282
|
+
|
|
283
|
+
expect(isUrl).toHaveBeenCalledTimes(2);
|
|
284
|
+
expect(isUrl).toHaveBeenNthCalledWith(1, "https://external.com");
|
|
285
|
+
expect(isUrl).toHaveBeenNthCalledWith(2, "myapp://deeplink");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should handle mixed valid and invalid calls", () => {
|
|
289
|
+
isUrl.mockReturnValueOnce(true).mockReturnValueOnce(false);
|
|
290
|
+
|
|
291
|
+
const result1 = validateExternalLink("https://valid.com", mockFormatMessage, mockMessages);
|
|
292
|
+
const result2 = validateDeepLink("invalid", mockFormatMessage, mockMessages);
|
|
293
|
+
|
|
294
|
+
expect(result1).toBeNull();
|
|
295
|
+
expect(result2).toBe("Invalid URL format");
|
|
296
|
+
expect(mockFormatMessage).toHaveBeenCalledTimes(1);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe("Error handling", () => {
|
|
301
|
+
it("should handle isUrl throwing an error", () => {
|
|
302
|
+
isUrl.mockImplementation(() => {
|
|
303
|
+
throw new Error("isUrl error");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(() => {
|
|
307
|
+
validateLink("https://example.com", mockFormatMessage, mockMessages);
|
|
308
|
+
}).toThrow("isUrl error");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("should handle formatMessage throwing an error", () => {
|
|
312
|
+
isUrl.mockReturnValue(false);
|
|
313
|
+
mockFormatMessage.mockImplementation(() => {
|
|
314
|
+
throw new Error("formatMessage error");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
expect(() => {
|
|
318
|
+
validateLink("invalid", mockFormatMessage, mockMessages);
|
|
319
|
+
}).toThrow("formatMessage error");
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isUrl } from "../Line/Container/Wrapper/utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Common link validation function using isUrl utility
|
|
5
|
+
* @param {string} url - The URL to validate
|
|
6
|
+
* @param {Function} formatMessage - Function to format error messages
|
|
7
|
+
* @param {Object} messages - Messages object containing invalidUrl message
|
|
8
|
+
* @returns {string|null} - Error message if invalid, null if valid
|
|
9
|
+
*/
|
|
10
|
+
export const validateLink = (url, formatMessage, messages) => {
|
|
11
|
+
if (!url || url.trim() === "") {
|
|
12
|
+
return null; // Empty URL is valid (optional field)
|
|
13
|
+
}
|
|
14
|
+
return isUrl(url.trim()) ? null : formatMessage(messages.invalidUrl);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validate external link specifically
|
|
19
|
+
* @param {string} url - The external URL to validate
|
|
20
|
+
* @param {Function} formatMessage - Function to format error messages
|
|
21
|
+
* @param {Object} messages - Messages object containing invalidUrl message
|
|
22
|
+
* @returns {string|null} - Error message if invalid, null if valid
|
|
23
|
+
*/
|
|
24
|
+
export const validateExternalLink = (url, formatMessage, messages) => validateLink(url, formatMessage, messages);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate deep link specifically
|
|
28
|
+
* @param {string} linkValue - The deep link to validate
|
|
29
|
+
* @param {Function} formatMessage - Function to format error messages
|
|
30
|
+
* @param {Object} messages - Messages object containing invalidUrl message
|
|
31
|
+
* @returns {string|null} - Error message if invalid, null if valid
|
|
32
|
+
*/
|
|
33
|
+
export const validateDeepLink = (linkValue, formatMessage, messages) => validateLink(linkValue, formatMessage, messages);
|
|
@@ -95639,7 +95639,7 @@ new message content.",
|
|
|
95639
95639
|
>
|
|
95640
95640
|
<input
|
|
95641
95641
|
accept="image/*"
|
|
95642
|
-
id="
|
|
95642
|
+
id="imageFileName"
|
|
95643
95643
|
key="imgFile"
|
|
95644
95644
|
onChange={[Function]}
|
|
95645
95645
|
style={
|
|
@@ -116561,7 +116561,7 @@ new message content.",
|
|
|
116561
116561
|
>
|
|
116562
116562
|
<input
|
|
116563
116563
|
accept="image/*"
|
|
116564
|
-
id="
|
|
116564
|
+
id="imageFileName"
|
|
116565
116565
|
key="imgFile"
|
|
116566
116566
|
onChange={[Function]}
|
|
116567
116567
|
style={
|
|
@@ -137377,7 +137377,7 @@ new message content.",
|
|
|
137377
137377
|
>
|
|
137378
137378
|
<input
|
|
137379
137379
|
accept="image/*"
|
|
137380
|
-
id="
|
|
137380
|
+
id="imageFileName"
|
|
137381
137381
|
key="imgFile"
|
|
137382
137382
|
onChange={[Function]}
|
|
137383
137383
|
style={
|
|
@@ -158299,7 +158299,7 @@ new message content.",
|
|
|
158299
158299
|
>
|
|
158300
158300
|
<input
|
|
158301
158301
|
accept="image/*"
|
|
158302
|
-
id="
|
|
158302
|
+
id="imageFileName"
|
|
158303
158303
|
key="imgFile"
|
|
158304
158304
|
onChange={[Function]}
|
|
158305
158305
|
style={
|
|
@@ -199210,7 +199210,7 @@ new message content.",
|
|
|
199210
199210
|
>
|
|
199211
199211
|
<input
|
|
199212
199212
|
accept="image/*"
|
|
199213
|
-
id="
|
|
199213
|
+
id="imageFileName"
|
|
199214
199214
|
key="imgFile"
|
|
199215
199215
|
onChange={[Function]}
|
|
199216
199216
|
style={
|
|
@@ -39,12 +39,14 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
39
39
|
loading: false,
|
|
40
40
|
tags: [],
|
|
41
41
|
tagsError: false,
|
|
42
|
+
currentContext: null, // Track current context to detect changes
|
|
42
43
|
};
|
|
43
44
|
this.renderTags = this.renderTags.bind(this);
|
|
44
45
|
this.populateTags = this.populateTags.bind(this);
|
|
45
46
|
this.populateTagForChildren = this.populateTagForChildren.bind(this);
|
|
46
47
|
this.transformInjectedTags = this.transformInjectedTags.bind(this);
|
|
47
48
|
this.transformCouponTags = this.transformCouponTags.bind(this);
|
|
49
|
+
this.loadingTimeout = null; // Add timeout reference
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
componentDidMount() {
|
|
@@ -52,33 +54,72 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
componentWillReceiveProps(nextProps) {
|
|
55
|
-
|
|
57
|
+
// Set loading to true in these scenarios:
|
|
58
|
+
// 1. Both injectedTags and tags are empty (initial load)
|
|
59
|
+
// 2. Context change is happening (detected by different tag arrays)
|
|
60
|
+
const { injectedTags: currentInjectedTags, tags: currentTags } = this.props;
|
|
61
|
+
const { injectedTags: nextInjectedTags, tags: nextTags, fetchingSchemaError } = nextProps;
|
|
62
|
+
|
|
63
|
+
const isInitialLoad = _.isEmpty(currentInjectedTags) && _.isEmpty(currentTags);
|
|
64
|
+
const isContextChange = !_.isEqual(nextTags, currentTags) && !_.isEmpty(currentTags);
|
|
65
|
+
|
|
66
|
+
if (isInitialLoad || isContextChange) {
|
|
56
67
|
this.setState({loading: true});
|
|
57
68
|
}
|
|
58
|
-
|
|
69
|
+
|
|
70
|
+
// Only reset loading for injectedTags changes if we're not currently loading due to context change
|
|
71
|
+
if (!_.isEqual(nextInjectedTags, currentInjectedTags) && !this.state.loading) {
|
|
59
72
|
this.setState({loading: false});
|
|
73
|
+
this.clearLoadingTimeout();
|
|
60
74
|
}
|
|
61
|
-
|
|
75
|
+
|
|
76
|
+
if (!_.isEqual(nextTags, currentTags)) {
|
|
62
77
|
this.setState({loading: false});
|
|
78
|
+
this.clearLoadingTimeout();
|
|
63
79
|
}
|
|
64
|
-
if (
|
|
65
|
-
this.setState({tagsError:
|
|
80
|
+
if (fetchingSchemaError) {
|
|
81
|
+
this.setState({tagsError: fetchingSchemaError, loading: false});
|
|
82
|
+
this.clearLoadingTimeout();
|
|
66
83
|
}
|
|
67
84
|
}
|
|
68
85
|
|
|
69
86
|
componentDidUpdate(prevProps) {
|
|
70
|
-
|
|
87
|
+
const { tags, injectedTags, selectedOfferDetails } = this.props;
|
|
88
|
+
const { tags: prevTags, injectedTags: prevInjectedTags, selectedOfferDetails: prevSelectedOfferDetails } = prevProps;
|
|
89
|
+
|
|
90
|
+
if (tags !== prevTags || injectedTags !== prevInjectedTags || selectedOfferDetails !== prevSelectedOfferDetails) {
|
|
71
91
|
this.generateTags(this.props);
|
|
72
92
|
}
|
|
73
93
|
}
|
|
74
94
|
|
|
95
|
+
componentWillUnmount() {
|
|
96
|
+
this.clearLoadingTimeout();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
clearLoadingTimeout = () => {
|
|
100
|
+
if (this.loadingTimeout) {
|
|
101
|
+
clearTimeout(this.loadingTimeout);
|
|
102
|
+
this.loadingTimeout = null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
75
106
|
onSelect = (selectedKeys) => {
|
|
76
|
-
this.props
|
|
107
|
+
const { onTagSelect } = this.props;
|
|
108
|
+
onTagSelect(selectedKeys[0]);
|
|
77
109
|
};
|
|
78
110
|
|
|
79
111
|
getTagsforContext = (data) => {
|
|
80
|
-
//
|
|
81
|
-
this.
|
|
112
|
+
// Set loading state when context change is requested
|
|
113
|
+
this.setState({loading: true, currentContext: data});
|
|
114
|
+
|
|
115
|
+
// Set a timeout to prevent infinite loading (fallback safety)
|
|
116
|
+
this.clearLoadingTimeout();
|
|
117
|
+
this.loadingTimeout = setTimeout(() => {
|
|
118
|
+
this.setState({loading: false});
|
|
119
|
+
}, 5000); // Reduced timeout to 5 seconds for better UX
|
|
120
|
+
|
|
121
|
+
const { onContextChange } = this.props;
|
|
122
|
+
onContextChange(data);
|
|
82
123
|
}
|
|
83
124
|
|
|
84
125
|
generateTags = (props) => {
|
|
@@ -159,7 +200,8 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
159
200
|
//Form tags object with tag headers
|
|
160
201
|
_.forEach(tagsList, (temp) => {
|
|
161
202
|
const tag = temp.definition;
|
|
162
|
-
const {
|
|
203
|
+
const { intl } = this.props;
|
|
204
|
+
const { locale: userLocale } = intl || {};
|
|
163
205
|
|
|
164
206
|
// Check if the tag.value should be skipped based on feature control
|
|
165
207
|
if (_.includes(excludedTags, tag.value)) {
|
|
@@ -360,6 +402,10 @@ TagList.propTypes = {
|
|
|
360
402
|
disabled: PropTypes.bool,
|
|
361
403
|
fetchingSchemaError: PropTypes.bool,
|
|
362
404
|
eventContextTags: PropTypes.array,
|
|
405
|
+
intl: PropTypes.shape({
|
|
406
|
+
formatMessage: PropTypes.func.isRequired,
|
|
407
|
+
locale: PropTypes.string,
|
|
408
|
+
}).isRequired,
|
|
363
409
|
};
|
|
364
410
|
|
|
365
411
|
const mapStateToProps = createStructuredSelector({
|
|
@@ -219,6 +219,106 @@
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
+
|
|
223
|
+
.MOBILEPUSH {
|
|
224
|
+
.ant-card-body {
|
|
225
|
+
padding: 0;
|
|
226
|
+
background-color: $CAP_WHITE;
|
|
227
|
+
border-top: 1px solid $CAP_COLOR_16;
|
|
228
|
+
.ant-card-meta {
|
|
229
|
+
background-color: $CAP_G09;
|
|
230
|
+
padding: 0;
|
|
231
|
+
.ant-card-meta-description {
|
|
232
|
+
.mobilepush-container {
|
|
233
|
+
background-color: $CAP_WHITE;
|
|
234
|
+
padding: $CAP_SPACE_12;
|
|
235
|
+
.app-header {
|
|
236
|
+
color: #5D5D5D;
|
|
237
|
+
font-weight: 600;
|
|
238
|
+
padding: 0.285rem 0.571rem 0.285rem 0.571rem;
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
justify-content: space-between;
|
|
242
|
+
.app-header-left{
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
div {
|
|
246
|
+
word-break: break-word;
|
|
247
|
+
}
|
|
248
|
+
.app-icon {
|
|
249
|
+
width: 0.857rem;
|
|
250
|
+
height: 0.857rem;
|
|
251
|
+
border-radius: 50%;
|
|
252
|
+
background-color: #737070;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
.mobilepush-message {
|
|
257
|
+
padding-left: 1.65rem;
|
|
258
|
+
}
|
|
259
|
+
.mobilepush-image {
|
|
260
|
+
width: 100%;
|
|
261
|
+
height: 120px;
|
|
262
|
+
margin-top: 10px;
|
|
263
|
+
}
|
|
264
|
+
.mobilepush-image-padding {
|
|
265
|
+
padding-left: 1.65rem;
|
|
266
|
+
}
|
|
267
|
+
.scroll-container {
|
|
268
|
+
overflow-x: auto;
|
|
269
|
+
display: flex;
|
|
270
|
+
padding-top: $CAP_SPACE_06;
|
|
271
|
+
padding-right: $CAP_SPACE_06;
|
|
272
|
+
white-space: nowrap;
|
|
273
|
+
scrollbar-width: none; // Hide scrollbar in Firefox
|
|
274
|
+
&::-webkit-scrollbar {
|
|
275
|
+
display: none; // Hide scrollbar in Chrome/Safari/Opera
|
|
276
|
+
}
|
|
277
|
+
overflow: hidden;
|
|
278
|
+
height: 100%;
|
|
279
|
+
width: 100%;
|
|
280
|
+
margin-left: 1.65rem;
|
|
281
|
+
.whatsapp-carousel-container {
|
|
282
|
+
padding: $CAP_SPACE_04 0px $CAP_SPACE_08;
|
|
283
|
+
border-radius: $CAP_SPACE_06;
|
|
284
|
+
background-color: $CAP_WHITE;
|
|
285
|
+
width: 80%;
|
|
286
|
+
flex-shrink: 0;
|
|
287
|
+
white-space: pre-wrap;
|
|
288
|
+
word-break: break-word;
|
|
289
|
+
overflow: auto;
|
|
290
|
+
text-align: left;
|
|
291
|
+
margin: 0;
|
|
292
|
+
.whatsapp-carousel-card {
|
|
293
|
+
margin: $CAP_SPACE_02 $CAP_SPACE_02 $CAP_SPACE_01 $CAP_SPACE_02;
|
|
294
|
+
.whatsapp-carousel-body {
|
|
295
|
+
margin-bottom: $CAP_SPACE_08;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
.actions{
|
|
301
|
+
background-color: #ffffff;
|
|
302
|
+
height: auto;
|
|
303
|
+
padding: $CAP_SPACE_08;
|
|
304
|
+
text-align: center;
|
|
305
|
+
display: flex;
|
|
306
|
+
flex-direction: column;
|
|
307
|
+
align-items: center;
|
|
308
|
+
justify-content: center;
|
|
309
|
+
gap: $CAP_SPACE_08;
|
|
310
|
+
.action{
|
|
311
|
+
font-size: $FONT_SIZE_S;
|
|
312
|
+
font-weight: 600;
|
|
313
|
+
color: #1970DA;
|
|
314
|
+
height: 1.25rem;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
222
322
|
}
|
|
223
323
|
|
|
224
324
|
.create-new-link{
|
|
@@ -502,7 +602,7 @@
|
|
|
502
602
|
}
|
|
503
603
|
}
|
|
504
604
|
.iphone-push-message-Container{
|
|
505
|
-
top:
|
|
605
|
+
top: 10%;
|
|
506
606
|
.message-pop{
|
|
507
607
|
border-radius: 4px;
|
|
508
608
|
word-wrap: break-word;
|