@capillarytech/creatives-library 8.0.130 → 8.0.132

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.
Files changed (77) hide show
  1. package/containers/App/constants.js +1 -0
  2. package/containers/Login/index.js +1 -2
  3. package/package.json +1 -1
  4. package/services/api.js +5 -0
  5. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
  6. package/tests/integration/TemplateCreation/api-response.js +5 -0
  7. package/tests/integration/TemplateCreation/msw-handler.js +42 -63
  8. package/utils/common.js +7 -0
  9. package/utils/commonUtils.js +2 -6
  10. package/utils/createMobilePushPayload.js +322 -0
  11. package/utils/tests/createMobilePushPayload.test.js +1054 -0
  12. package/v2Components/CapDeviceContent/index.js +1 -1
  13. package/v2Components/CapImageUpload/index.js +57 -44
  14. package/v2Components/CapInAppCTA/index.js +1 -0
  15. package/v2Components/CapMpushCTA/constants.js +25 -0
  16. package/v2Components/CapMpushCTA/index.js +403 -0
  17. package/v2Components/CapMpushCTA/index.scss +95 -0
  18. package/v2Components/CapMpushCTA/messages.js +101 -0
  19. package/v2Components/CapTagList/index.js +178 -121
  20. package/v2Components/CapVideoUpload/constants.js +3 -0
  21. package/v2Components/CapVideoUpload/index.js +182 -115
  22. package/v2Components/CapVideoUpload/messages.js +16 -0
  23. package/v2Components/Carousel/index.js +15 -13
  24. package/v2Components/ErrorInfoNote/style.scss +1 -0
  25. package/v2Components/MobilePushPreviewV2/index.js +57 -12
  26. package/v2Components/TemplatePreview/_templatePreview.scss +218 -74
  27. package/v2Components/TemplatePreview/assets/images/Android_With_date_and_time.svg +29 -0
  28. package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
  29. package/v2Components/TemplatePreview/assets/images/iOS_With_date_and_time.svg +26 -0
  30. package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
  31. package/v2Components/TemplatePreview/index.js +234 -107
  32. package/v2Components/TemplatePreview/messages.js +4 -0
  33. package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +10 -10
  34. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -62
  35. package/v2Containers/CreativesContainer/index.js +193 -136
  36. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -22
  37. package/v2Containers/InApp/constants.js +1 -0
  38. package/v2Containers/InApp/index.js +13 -13
  39. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +4748 -4658
  40. package/v2Containers/Login/index.js +1 -2
  41. package/v2Containers/MobilePush/Create/index.js +1 -0
  42. package/v2Containers/MobilePush/commonMethods.js +7 -14
  43. package/v2Containers/MobilePush/tests/commonMethods.test.js +401 -0
  44. package/v2Containers/MobilePushNew/actions.js +116 -0
  45. package/v2Containers/MobilePushNew/components/CtaButtons.js +183 -0
  46. package/v2Containers/MobilePushNew/components/MediaUploaders.js +835 -0
  47. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +346 -0
  48. package/v2Containers/MobilePushNew/components/index.js +5 -0
  49. package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +565 -0
  50. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +3180 -0
  51. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +654 -0
  52. package/v2Containers/MobilePushNew/constants.js +116 -0
  53. package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1462 -0
  54. package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1459 -0
  55. package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +366 -0
  56. package/v2Containers/MobilePushNew/hooks/useUpload.js +740 -0
  57. package/v2Containers/MobilePushNew/index.js +2158 -0
  58. package/v2Containers/MobilePushNew/index.scss +308 -0
  59. package/v2Containers/MobilePushNew/messages.js +272 -0
  60. package/v2Containers/MobilePushNew/reducer.js +160 -0
  61. package/v2Containers/MobilePushNew/sagas.js +193 -0
  62. package/v2Containers/MobilePushNew/selectors.js +55 -0
  63. package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
  64. package/v2Containers/MobilePushNew/tests/sagas.test.js +864 -0
  65. package/v2Containers/MobilePushNew/tests/selectors.test.js +665 -0
  66. package/v2Containers/MobilePushNew/tests/utils.test.js +421 -0
  67. package/v2Containers/MobilePushNew/utils.js +84 -0
  68. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1176 -976
  69. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +684 -424
  70. package/v2Containers/TagList/index.js +56 -10
  71. package/v2Containers/Templates/_templates.scss +100 -1
  72. package/v2Containers/Templates/index.js +170 -31
  73. package/v2Containers/Templates/messages.js +8 -0
  74. package/v2Containers/Templates/sagas.js +1 -0
  75. package/v2Containers/Whatsapp/constants.js +1 -0
  76. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +3992 -3677
  77. package/assets/loading_img.gif +0 -0
@@ -0,0 +1,421 @@
1
+ import {
2
+ validateLink,
3
+ validateExternalLink,
4
+ validateDeepLink,
5
+ isDeepLink,
6
+ } from "../utils";
7
+ import { isUrl } from "../../Line/Container/Wrapper/utils";
8
+
9
+ // Mock the isUrl utility function
10
+ jest.mock("../../Line/Container/Wrapper/utils", () => ({
11
+ isUrl: jest.fn(),
12
+ }));
13
+
14
+ describe("utils.js", () => {
15
+ const mockFormatMessage = jest.fn();
16
+ const mockMessages = {
17
+ invalidUrl: {
18
+ id: "invalidUrl",
19
+ defaultMessage: "Invalid URL format",
20
+ },
21
+ };
22
+
23
+ beforeEach(() => {
24
+ jest.clearAllMocks();
25
+ mockFormatMessage.mockImplementation((msg) => msg.defaultMessage);
26
+ });
27
+
28
+ describe("validateLink", () => {
29
+ describe("Valid scenarios", () => {
30
+ it("should return null for valid URL", () => {
31
+ isUrl.mockReturnValue(true);
32
+
33
+ const result = validateLink("https://example.com", mockFormatMessage, mockMessages);
34
+
35
+ expect(result).toBeNull();
36
+ expect(isUrl).toHaveBeenCalledWith("https://example.com");
37
+ expect(mockFormatMessage).not.toHaveBeenCalled();
38
+ });
39
+
40
+ it("should return null for valid URL with whitespace", () => {
41
+ isUrl.mockReturnValue(true);
42
+
43
+ const result = validateLink(" https://example.com ", mockFormatMessage, mockMessages);
44
+
45
+ expect(result).toBeNull();
46
+ expect(isUrl).toHaveBeenCalledWith("https://example.com");
47
+ expect(mockFormatMessage).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it("should return null for empty URL", () => {
51
+ const result = validateLink("", mockFormatMessage, mockMessages);
52
+
53
+ expect(result).toBeNull();
54
+ expect(isUrl).not.toHaveBeenCalled();
55
+ expect(mockFormatMessage).not.toHaveBeenCalled();
56
+ });
57
+
58
+ it("should return null for null URL", () => {
59
+ const result = validateLink(null, mockFormatMessage, mockMessages);
60
+
61
+ expect(result).toBeNull();
62
+ expect(isUrl).not.toHaveBeenCalled();
63
+ expect(mockFormatMessage).not.toHaveBeenCalled();
64
+ });
65
+
66
+ it("should return null for undefined URL", () => {
67
+ const result = validateLink(undefined, mockFormatMessage, mockMessages);
68
+
69
+ expect(result).toBeNull();
70
+ expect(isUrl).not.toHaveBeenCalled();
71
+ expect(mockFormatMessage).not.toHaveBeenCalled();
72
+ });
73
+
74
+ it("should return null for whitespace-only URL", () => {
75
+ const result = validateLink(" ", mockFormatMessage, mockMessages);
76
+
77
+ expect(result).toBeNull();
78
+ expect(isUrl).not.toHaveBeenCalled();
79
+ expect(mockFormatMessage).not.toHaveBeenCalled();
80
+ });
81
+
82
+ it("should return null for tab and newline whitespace", () => {
83
+ const result = validateLink("\t\n\r", mockFormatMessage, mockMessages);
84
+
85
+ expect(result).toBeNull();
86
+ expect(isUrl).not.toHaveBeenCalled();
87
+ expect(mockFormatMessage).not.toHaveBeenCalled();
88
+ });
89
+ });
90
+
91
+ describe("Invalid scenarios", () => {
92
+ it("should return error message for invalid URL", () => {
93
+ isUrl.mockReturnValue(false);
94
+
95
+ const result = validateLink("invalid-url", mockFormatMessage, mockMessages);
96
+
97
+ expect(result).toBe("Invalid URL format");
98
+ expect(isUrl).toHaveBeenCalledWith("invalid-url");
99
+ expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
100
+ });
101
+
102
+ it("should return error message for invalid URL with whitespace", () => {
103
+ isUrl.mockReturnValue(false);
104
+
105
+ const result = validateLink(" invalid-url ", mockFormatMessage, mockMessages);
106
+
107
+ expect(result).toBe("Invalid URL format");
108
+ expect(isUrl).toHaveBeenCalledWith("invalid-url");
109
+ expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
110
+ });
111
+
112
+ it("should return error message for malformed URL", () => {
113
+ isUrl.mockReturnValue(false);
114
+
115
+ const result = validateLink("htp://malformed", mockFormatMessage, mockMessages);
116
+
117
+ expect(result).toBe("Invalid URL format");
118
+ expect(isUrl).toHaveBeenCalledWith("htp://malformed");
119
+ expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
120
+ });
121
+
122
+ it("should return error message for URL without protocol", () => {
123
+ isUrl.mockReturnValue(false);
124
+
125
+ const result = validateLink("example.com", mockFormatMessage, mockMessages);
126
+
127
+ expect(result).toBe("Invalid URL format");
128
+ expect(isUrl).toHaveBeenCalledWith("example.com");
129
+ expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
130
+ });
131
+
132
+ it("should handle special characters in invalid URL", () => {
133
+ isUrl.mockReturnValue(false);
134
+
135
+ const result = validateLink("http://ex@mple.com", mockFormatMessage, mockMessages);
136
+
137
+ expect(result).toBe("Invalid URL format");
138
+ expect(isUrl).toHaveBeenCalledWith("http://ex@mple.com");
139
+ expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
140
+ });
141
+ });
142
+
143
+ describe("Edge cases", () => {
144
+ it("should handle custom formatMessage function", () => {
145
+ const customFormatMessage = jest.fn().mockReturnValue("Custom error message");
146
+ isUrl.mockReturnValue(false);
147
+
148
+ const result = validateLink("invalid", customFormatMessage, mockMessages);
149
+
150
+ expect(result).toBe("Custom error message");
151
+ expect(customFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
152
+ });
153
+
154
+ it("should handle different message object structure", () => {
155
+ const customMessages = {
156
+ invalidUrl: {
157
+ id: "custom.invalidUrl",
158
+ defaultMessage: "Custom invalid URL message",
159
+ },
160
+ };
161
+ isUrl.mockReturnValue(false);
162
+
163
+ const result = validateLink("invalid", mockFormatMessage, customMessages);
164
+
165
+ expect(result).toBe("Custom invalid URL message");
166
+ expect(mockFormatMessage).toHaveBeenCalledWith(customMessages.invalidUrl);
167
+ });
168
+
169
+ it("should trim very long whitespace strings", () => {
170
+ const longWhitespace = " ".repeat(100);
171
+
172
+ const result = validateLink(longWhitespace, mockFormatMessage, mockMessages);
173
+
174
+ expect(result).toBeNull();
175
+ expect(isUrl).not.toHaveBeenCalled();
176
+ });
177
+ });
178
+ });
179
+
180
+ describe("validateExternalLink", () => {
181
+ it("should call validateLink with correct parameters for valid URL", () => {
182
+ isUrl.mockReturnValue(true);
183
+
184
+ const result = validateExternalLink("https://external.com", mockFormatMessage, mockMessages);
185
+
186
+ expect(result).toBeNull();
187
+ expect(isUrl).toHaveBeenCalledWith("https://external.com");
188
+ });
189
+
190
+ it("should call validateLink with correct parameters for invalid URL", () => {
191
+ isUrl.mockReturnValue(false);
192
+
193
+ const result = validateExternalLink("invalid-external", mockFormatMessage, mockMessages);
194
+
195
+ expect(result).toBe("Invalid URL format");
196
+ expect(isUrl).toHaveBeenCalledWith("invalid-external");
197
+ expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
198
+ });
199
+
200
+ it("should handle empty external link", () => {
201
+ const result = validateExternalLink("", mockFormatMessage, mockMessages);
202
+
203
+ expect(result).toBeNull();
204
+ expect(isUrl).not.toHaveBeenCalled();
205
+ });
206
+
207
+ it("should handle null external link", () => {
208
+ const result = validateExternalLink(null, mockFormatMessage, mockMessages);
209
+
210
+ expect(result).toBeNull();
211
+ expect(isUrl).not.toHaveBeenCalled();
212
+ });
213
+
214
+ it("should trim whitespace in external link", () => {
215
+ isUrl.mockReturnValue(true);
216
+
217
+ const result = validateExternalLink(" https://external.com ", mockFormatMessage, mockMessages);
218
+
219
+ expect(result).toBeNull();
220
+ expect(isUrl).toHaveBeenCalledWith("https://external.com");
221
+ });
222
+ });
223
+
224
+ describe("validateDeepLink", () => {
225
+ it("should return null for valid custom scheme deep link", () => {
226
+ const result = validateDeepLink("myapp://deeplink", mockFormatMessage, mockMessages);
227
+
228
+ expect(result).toBeNull();
229
+ expect(mockFormatMessage).not.toHaveBeenCalled();
230
+ });
231
+
232
+ it("should return null for valid HTTP deep link", () => {
233
+ const result = validateDeepLink("https://example.com/path", mockFormatMessage, mockMessages);
234
+
235
+ expect(result).toBeNull();
236
+ expect(mockFormatMessage).not.toHaveBeenCalled();
237
+ });
238
+
239
+ it("should return null for valid relative path deep link", () => {
240
+ const result = validateDeepLink("/path/to/screen", mockFormatMessage, mockMessages);
241
+
242
+ expect(result).toBeNull();
243
+ expect(mockFormatMessage).not.toHaveBeenCalled();
244
+ });
245
+
246
+ it("should return error message for invalid deep link", () => {
247
+ const result = validateDeepLink("invalid-deeplink", mockFormatMessage, mockMessages);
248
+
249
+ expect(result).toBe("Invalid URL format");
250
+ expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.invalidUrl);
251
+ });
252
+
253
+ it("should handle empty deep link", () => {
254
+ const result = validateDeepLink("", mockFormatMessage, mockMessages);
255
+
256
+ expect(result).toBeNull();
257
+ expect(mockFormatMessage).not.toHaveBeenCalled();
258
+ });
259
+
260
+ it("should handle undefined deep link", () => {
261
+ const result = validateDeepLink(undefined, mockFormatMessage, mockMessages);
262
+
263
+ expect(result).toBeNull();
264
+ expect(mockFormatMessage).not.toHaveBeenCalled();
265
+ });
266
+
267
+ it("should trim whitespace in deep link", () => {
268
+ const result = validateDeepLink(" myapp://deeplink ", mockFormatMessage, mockMessages);
269
+
270
+ expect(result).toBeNull();
271
+ expect(mockFormatMessage).not.toHaveBeenCalled();
272
+ });
273
+
274
+ it("should handle custom scheme deep links", () => {
275
+ const result = validateDeepLink("customscheme://action/path", mockFormatMessage, mockMessages);
276
+
277
+ expect(result).toBeNull();
278
+ expect(mockFormatMessage).not.toHaveBeenCalled();
279
+ });
280
+
281
+ it("should handle app-specific deep links without path", () => {
282
+ const result = validateDeepLink("myapp://", mockFormatMessage, mockMessages);
283
+
284
+ expect(result).toBeNull();
285
+ expect(mockFormatMessage).not.toHaveBeenCalled();
286
+ });
287
+ });
288
+
289
+ describe("isDeepLink", () => {
290
+ describe("Valid deep link formats", () => {
291
+ it("should validate custom scheme URLs", () => {
292
+ expect(isDeepLink("myapp://path")).toBe(true);
293
+ expect(isDeepLink("com.example.app://action")).toBe(true);
294
+ expect(isDeepLink("customscheme://path/to/screen")).toBe(true);
295
+ });
296
+
297
+ it("should validate HTTP/HTTPS URLs", () => {
298
+ expect(isDeepLink("https://example.com/path")).toBe(true);
299
+ expect(isDeepLink("http://example.com/path")).toBe(true);
300
+ expect(isDeepLink("https://app.example.com/deep/link")).toBe(true);
301
+ });
302
+
303
+ it("should validate relative paths", () => {
304
+ expect(isDeepLink("/path")).toBe(true);
305
+ expect(isDeepLink("path")).toBe(true);
306
+ expect(isDeepLink("/path/to/screen")).toBe(true);
307
+ expect(isDeepLink("home/screen")).toBe(true);
308
+ });
309
+
310
+ it("should validate app-specific deep links without path", () => {
311
+ expect(isDeepLink("myapp://")).toBe(true);
312
+ expect(isDeepLink("com.example.app://")).toBe(true);
313
+ });
314
+
315
+ it("should handle URLs with query parameters", () => {
316
+ expect(isDeepLink("myapp://path?param=value")).toBe(true);
317
+ expect(isDeepLink("https://example.com/path?param=value")).toBe(true);
318
+ });
319
+
320
+ it("should handle URLs with special characters", () => {
321
+ expect(isDeepLink("myapp://path-with-dashes")).toBe(true);
322
+ expect(isDeepLink("myapp://path_with_underscores")).toBe(true);
323
+ expect(isDeepLink("myapp://path.with.dots")).toBe(true);
324
+ });
325
+ });
326
+
327
+ describe("Invalid deep link formats", () => {
328
+ it("should reject invalid URLs", () => {
329
+ expect(isDeepLink("invalid-url")).toBe(false);
330
+ expect(isDeepLink("not-a-url")).toBe(false);
331
+ expect(isDeepLink("random text")).toBe(false);
332
+ });
333
+
334
+ it("should reject URLs with invalid schemes", () => {
335
+ expect(isDeepLink("://path")).toBe(false);
336
+ expect(isDeepLink("123://path")).toBe(false);
337
+ expect(isDeepLink("-invalid://path")).toBe(false);
338
+ });
339
+
340
+ it("should reject empty or whitespace-only strings", () => {
341
+ expect(isDeepLink("")).toBe(false);
342
+ expect(isDeepLink(" ")).toBe(false);
343
+ expect(isDeepLink("\t\n")).toBe(false);
344
+ });
345
+
346
+ it("should reject null and undefined", () => {
347
+ expect(isDeepLink(null)).toBe(false);
348
+ expect(isDeepLink(undefined)).toBe(false);
349
+ });
350
+
351
+ it("should reject non-string inputs", () => {
352
+ expect(isDeepLink(123)).toBe(false);
353
+ expect(isDeepLink({})).toBe(false);
354
+ expect(isDeepLink([])).toBe(false);
355
+ });
356
+ });
357
+
358
+ describe("Edge cases", () => {
359
+ it("should trim whitespace", () => {
360
+ expect(isDeepLink(" myapp://path ")).toBe(true);
361
+ expect(isDeepLink("\tmyapp://path\n")).toBe(true);
362
+ });
363
+
364
+ it("should handle very long URLs", () => {
365
+ const longPath = "/" + "a".repeat(1000);
366
+ expect(isDeepLink(longPath)).toBe(true);
367
+ });
368
+
369
+ it("should handle URLs with multiple slashes", () => {
370
+ expect(isDeepLink("myapp://path//to//screen")).toBe(true);
371
+ expect(isDeepLink("///path")).toBe(true);
372
+ });
373
+ });
374
+ });
375
+
376
+ describe("Function independence", () => {
377
+ it("should ensure validateExternalLink and validateDeepLink are independent", () => {
378
+ isUrl.mockReturnValue(true);
379
+
380
+ validateExternalLink("https://external.com", mockFormatMessage, mockMessages);
381
+ validateDeepLink("myapp://deeplink", mockFormatMessage, mockMessages);
382
+
383
+ expect(isUrl).toHaveBeenCalledTimes(1);
384
+ expect(isUrl).toHaveBeenCalledWith("https://external.com");
385
+ });
386
+
387
+ it("should handle mixed valid and invalid calls", () => {
388
+ isUrl.mockReturnValueOnce(true);
389
+
390
+ const result1 = validateExternalLink("https://valid.com", mockFormatMessage, mockMessages);
391
+ const result2 = validateDeepLink("invalid", mockFormatMessage, mockMessages);
392
+
393
+ expect(result1).toBeNull();
394
+ expect(result2).toBe("Invalid URL format");
395
+ expect(mockFormatMessage).toHaveBeenCalledTimes(1);
396
+ });
397
+ });
398
+
399
+ describe("Error handling", () => {
400
+ it("should handle isUrl throwing an error", () => {
401
+ isUrl.mockImplementation(() => {
402
+ throw new Error("isUrl error");
403
+ });
404
+
405
+ expect(() => {
406
+ validateLink("https://example.com", mockFormatMessage, mockMessages);
407
+ }).toThrow("isUrl error");
408
+ });
409
+
410
+ it("should handle formatMessage throwing an error", () => {
411
+ isUrl.mockReturnValue(false);
412
+ mockFormatMessage.mockImplementation(() => {
413
+ throw new Error("formatMessage error");
414
+ });
415
+
416
+ expect(() => {
417
+ validateLink("invalid", mockFormatMessage, mockMessages);
418
+ }).toThrow("formatMessage error");
419
+ });
420
+ });
421
+ });
@@ -0,0 +1,84 @@
1
+ import { isUrl } from "../Line/Container/Wrapper/utils";
2
+
3
+ /**
4
+ * Validate deep link format - supports custom schemes, HTTP/HTTPS, and relative paths
5
+ * @param {string} url - The deep link to validate
6
+ * @returns {boolean} - True if valid deep link format, false otherwise
7
+ */
8
+ export const isDeepLink = (url) => {
9
+ if (!url || typeof url !== 'string') {
10
+ return false;
11
+ }
12
+
13
+ const trimmedUrl = url.trim();
14
+ if (trimmedUrl === '') {
15
+ return false;
16
+ }
17
+
18
+ // Pattern 1: Custom scheme URLs (e.g., myapp://path, com.example.app://action)
19
+ const customSchemePattern = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s]*$/;
20
+
21
+ // Pattern 2: HTTP/HTTPS URLs (standard web URLs)
22
+ const httpPattern = /^https?:\/\/[^\s]*$/;
23
+
24
+ // Pattern 3: Relative paths (e.g., /path, path, home/screen) - must be valid path format
25
+ // Accept simple paths, paths with slashes, or paths starting with /
26
+ // But reject URLs that look like invalid web URLs (containing common URL patterns)
27
+ const relativePathPattern = /^(\/[a-zA-Z0-9\/\-_\.]+|[a-zA-Z0-9\/\-_\.]+(\/[a-zA-Z0-9\/\-_\.]*)*)$/;
28
+
29
+ // Additional check: reject patterns that look like invalid URLs
30
+ const invalidUrlPattern = /^(invalid|not|random|http|https|www|\.com|\.org|\.net)/i;
31
+
32
+ // Pattern 4: App-specific deep links without scheme (e.g., myapp://)
33
+ const appSpecificPattern = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/$/;
34
+
35
+ // Check if it matches any valid pattern
36
+ const isValidPattern = customSchemePattern.test(trimmedUrl) ||
37
+ httpPattern.test(trimmedUrl) ||
38
+ relativePathPattern.test(trimmedUrl) ||
39
+ appSpecificPattern.test(trimmedUrl);
40
+
41
+ // If it matches a valid pattern, also check that it's not an invalid URL pattern
42
+ if (isValidPattern && relativePathPattern.test(trimmedUrl)) {
43
+ return !invalidUrlPattern.test(trimmedUrl);
44
+ }
45
+
46
+ return isValidPattern;
47
+ };
48
+
49
+ /**
50
+ * Common link validation function using isUrl utility
51
+ * @param {string} url - The URL to validate
52
+ * @param {Function} formatMessage - Function to format error messages
53
+ * @param {Object} messages - Messages object containing invalidUrl message
54
+ * @returns {string|null} - Error message if invalid, null if valid
55
+ */
56
+ export const validateLink = (url, formatMessage, messages) => {
57
+ if (!url || url.trim() === "") {
58
+ return null; // Empty URL is valid (optional field)
59
+ }
60
+ return isUrl(url.trim()) ? null : formatMessage(messages.invalidUrl);
61
+ };
62
+
63
+ /**
64
+ * Validate external link specifically
65
+ * @param {string} url - The external URL to validate
66
+ * @param {Function} formatMessage - Function to format error messages
67
+ * @param {Object} messages - Messages object containing invalidUrl message
68
+ * @returns {string|null} - Error message if invalid, null if valid
69
+ */
70
+ export const validateExternalLink = (url, formatMessage, messages) => validateLink(url, formatMessage, messages);
71
+
72
+ /**
73
+ * Validate deep link specifically using deep link validation
74
+ * @param {string} linkValue - The deep link to validate
75
+ * @param {Function} formatMessage - Function to format error messages
76
+ * @param {Object} messages - Messages object containing invalidUrl message
77
+ * @returns {string|null} - Error message if invalid, null if valid
78
+ */
79
+ export const validateDeepLink = (linkValue, formatMessage, messages) => {
80
+ if (!linkValue || linkValue.trim() === "") {
81
+ return null; // Empty deep link is valid (optional field)
82
+ }
83
+ return isDeepLink(linkValue.trim()) ? null : formatMessage(messages.invalidUrl);
84
+ };