@capillarytech/creatives-library 8.0.123-alpha.0 → 8.0.124

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 (65) hide show
  1. package/containers/App/constants.js +1 -0
  2. package/package.json +2 -2
  3. package/services/api.js +1 -1
  4. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
  5. package/tests/integration/TemplateCreation/api-response.js +5 -0
  6. package/tests/integration/TemplateCreation/msw-handler.js +42 -63
  7. package/utils/common.js +7 -0
  8. package/utils/commonUtils.js +2 -6
  9. package/utils/createPayload.js +240 -0
  10. package/utils/tests/createPayload.test.js +761 -0
  11. package/v2Components/CapImageUpload/index.js +51 -45
  12. package/v2Components/CapInAppCTA/index.js +1 -0
  13. package/v2Components/CapMpushCTA/constants.js +25 -0
  14. package/v2Components/CapMpushCTA/index.js +332 -0
  15. package/v2Components/CapMpushCTA/index.scss +95 -0
  16. package/v2Components/CapMpushCTA/messages.js +89 -0
  17. package/v2Components/CapTagList/index.js +177 -120
  18. package/v2Components/CapVideoUpload/constants.js +3 -0
  19. package/v2Components/CapVideoUpload/index.js +167 -110
  20. package/v2Components/CapVideoUpload/messages.js +16 -0
  21. package/v2Components/ErrorInfoNote/style.scss +1 -0
  22. package/v2Components/MobilePushPreviewV2/index.js +37 -5
  23. package/v2Components/TemplatePreview/_templatePreview.scss +114 -72
  24. package/v2Components/TemplatePreview/assets/images/Android _ With date and time.svg +29 -0
  25. package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
  26. package/v2Components/TemplatePreview/assets/images/iOS _ With date and time.svg +26 -0
  27. package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
  28. package/v2Components/TemplatePreview/index.js +178 -50
  29. package/v2Components/TemplatePreview/messages.js +4 -0
  30. package/v2Containers/CreativesContainer/SlideBoxContent.js +7 -8
  31. package/v2Containers/CreativesContainer/index.js +194 -138
  32. package/v2Containers/InApp/constants.js +1 -0
  33. package/v2Containers/InApp/index.js +13 -13
  34. package/v2Containers/MobilePush/Create/index.js +1 -0
  35. package/v2Containers/MobilePushNew/actions.js +116 -0
  36. package/v2Containers/MobilePushNew/components/CtaButtons.js +170 -0
  37. package/v2Containers/MobilePushNew/components/MediaUploaders.js +686 -0
  38. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +279 -0
  39. package/v2Containers/MobilePushNew/components/index.js +5 -0
  40. package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +779 -0
  41. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +2114 -0
  42. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +343 -0
  43. package/v2Containers/MobilePushNew/constants.js +115 -0
  44. package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1299 -0
  45. package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1223 -0
  46. package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +246 -0
  47. package/v2Containers/MobilePushNew/hooks/useUpload.js +709 -0
  48. package/v2Containers/MobilePushNew/index.js +1960 -0
  49. package/v2Containers/MobilePushNew/index.scss +308 -0
  50. package/v2Containers/MobilePushNew/messages.js +226 -0
  51. package/v2Containers/MobilePushNew/reducer.js +160 -0
  52. package/v2Containers/MobilePushNew/sagas.js +198 -0
  53. package/v2Containers/MobilePushNew/selectors.js +55 -0
  54. package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
  55. package/v2Containers/MobilePushNew/tests/sagas.test.js +863 -0
  56. package/v2Containers/MobilePushNew/tests/selectors.test.js +425 -0
  57. package/v2Containers/MobilePushNew/tests/utils.test.js +322 -0
  58. package/v2Containers/MobilePushNew/utils.js +33 -0
  59. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +5 -5
  60. package/v2Containers/TagList/index.js +56 -10
  61. package/v2Containers/Templates/_templates.scss +101 -1
  62. package/v2Containers/Templates/index.js +147 -35
  63. package/v2Containers/Templates/messages.js +8 -0
  64. package/v2Containers/Templates/sagas.js +2 -0
  65. 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);
@@ -95634,7 +95634,7 @@ new message content.",
95634
95634
  >
95635
95635
  <input
95636
95636
  accept="image/*"
95637
- id="fileName"
95637
+ id="imageFileName"
95638
95638
  key="imgFile"
95639
95639
  onChange={[Function]}
95640
95640
  style={
@@ -116555,7 +116555,7 @@ new message content.",
116555
116555
  >
116556
116556
  <input
116557
116557
  accept="image/*"
116558
- id="fileName"
116558
+ id="imageFileName"
116559
116559
  key="imgFile"
116560
116560
  onChange={[Function]}
116561
116561
  style={
@@ -137370,7 +137370,7 @@ new message content.",
137370
137370
  >
137371
137371
  <input
137372
137372
  accept="image/*"
137373
- id="fileName"
137373
+ id="imageFileName"
137374
137374
  key="imgFile"
137375
137375
  onChange={[Function]}
137376
137376
  style={
@@ -158291,7 +158291,7 @@ new message content.",
158291
158291
  >
158292
158292
  <input
158293
158293
  accept="image/*"
158294
- id="fileName"
158294
+ id="imageFileName"
158295
158295
  key="imgFile"
158296
158296
  onChange={[Function]}
158297
158297
  style={
@@ -199200,7 +199200,7 @@ new message content.",
199200
199200
  >
199201
199201
  <input
199202
199202
  accept="image/*"
199203
- id="fileName"
199203
+ id="imageFileName"
199204
199204
  key="imgFile"
199205
199205
  onChange={[Function]}
199206
199206
  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
- if (_.isEmpty(this.props.injectedTags) && _.isEmpty(this.props.tags)) {
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
- if (!_.isEqual(nextProps.injectedTags, this.props.injectedTags)) {
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
- if (!_.isEqual(nextProps.tags, this.props.tags)) {
75
+
76
+ if (!_.isEqual(nextTags, currentTags)) {
62
77
  this.setState({loading: false});
78
+ this.clearLoadingTimeout();
63
79
  }
64
- if (nextProps?.fetchingSchemaError) {
65
- this.setState({tagsError: nextProps?.fetchingSchemaError});
80
+ if (fetchingSchemaError) {
81
+ this.setState({tagsError: fetchingSchemaError, loading: false});
82
+ this.clearLoadingTimeout();
66
83
  }
67
84
  }
68
85
 
69
86
  componentDidUpdate(prevProps) {
70
- if (this.props.tags !== prevProps.tags || this.props.injectedTags !== prevProps.injectedTags || this.props.selectedOfferDetails !== prevProps.selectedOfferDetails) {
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.onTagSelect(selectedKeys[0]);
107
+ const { onTagSelect } = this.props;
108
+ onTagSelect(selectedKeys[0]);
77
109
  };
78
110
 
79
111
  getTagsforContext = (data) => {
80
- //this.setState({loading: true});
81
- this.props.onContextChange(data);
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 { locale: userLocale } = this.props?.intl || {};
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: 8%;
605
+ top: 10%;
506
606
  .message-pop{
507
607
  border-radius: 4px;
508
608
  word-wrap: break-word;