@capillarytech/creatives-library 9.0.29-alpha.1 → 9.0.29-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.29-alpha.1",
4
+ "version": "9.0.29-alpha.2",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -59,12 +59,9 @@ export const normalizeRcsMessageContentForApi = messageContentItem => {
59
59
  rcsCardPayloadOnly.cardContent.forEach(card => {
60
60
  if (Array.isArray(card?.suggestions)) {
61
61
  card.suggestions.forEach(suggestion => {
62
- // isSaved and urlType are UI-only editor state: campaigns' send-for-approval API
63
- // rejects them, and they aren't part of the persisted payload shape. The dynamic tag
64
- // (if any) already lives inline in `url` (e.g. "https://x.com{{first_name}}") — no
65
- // separate dynamicUrlPayload field is sent for RCS.
62
+ // isSaved is UI-only editor state: campaigns' send-for-approval API rejects it, and it
63
+ // isn't part of the persisted payload shape.
66
64
  delete suggestion?.isSaved;
67
- delete suggestion?.urlType;
68
65
  });
69
66
  }
70
67
  });
@@ -209,7 +209,7 @@ describe('normalizeRcsMessageContentForApi', () => {
209
209
  expect(item.rcsContent.cardContent).toEqual([{ title: 'Card' }]);
210
210
  });
211
211
 
212
- it('strips isSaved and urlType from each suggestion in cardContent', () => {
212
+ it('strips isSaved from each suggestion in cardContent', () => {
213
213
  const item = {
214
214
  rcsContent: {
215
215
  cardContent: [
@@ -217,9 +217,7 @@ describe('normalizeRcsMessageContentForApi', () => {
217
217
  title: 'Card 1',
218
218
  suggestions: [
219
219
  { type: 'QUICK_REPLY', text: 'Yes', isSaved: true },
220
- {
221
- type: 'CTA', text: 'Go', url: 'https://example.com', urlType: 'STATIC', isSaved: false,
222
- },
220
+ { type: 'CTA', text: 'Go', url: 'https://example.com', isSaved: false },
223
221
  ],
224
222
  },
225
223
  ],
@@ -232,56 +230,6 @@ describe('normalizeRcsMessageContentForApi', () => {
232
230
  ]);
233
231
  });
234
232
 
235
- it('keeps type "CTA" and the inline tag in url, but drops urlType, for a dynamic-URL suggestion', () => {
236
- const item = {
237
- rcsContent: {
238
- cardContent: [
239
- {
240
- title: 'Card 1',
241
- suggestions: [
242
- {
243
- type: 'CTA',
244
- text: 'Visit here',
245
- url: 'https://example.com/{{first_name}}',
246
- urlType: 'DYNAMIC',
247
- isSaved: true,
248
- },
249
- ],
250
- },
251
- ],
252
- },
253
- };
254
- normalizeRcsMessageContentForApi(item);
255
- expect(item.rcsContent.cardContent[0].suggestions).toEqual([
256
- {
257
- type: 'CTA',
258
- text: 'Visit here',
259
- url: 'https://example.com/{{first_name}}',
260
- },
261
- ]);
262
- });
263
-
264
- it('leaves phone number and quick reply suggestions untouched by the urlType stripping', () => {
265
- const item = {
266
- rcsContent: {
267
- cardContent: [
268
- {
269
- title: 'Card 1',
270
- suggestions: [
271
- {
272
- type: 'PHONE_NUMBER', text: 'Call', phoneNumber: '918987678', isSaved: true,
273
- },
274
- ],
275
- },
276
- ],
277
- },
278
- };
279
- normalizeRcsMessageContentForApi(item);
280
- expect(item.rcsContent.cardContent[0].suggestions).toEqual([
281
- { type: 'PHONE_NUMBER', text: 'Call', phoneNumber: '918987678' },
282
- ]);
283
- });
284
-
285
233
  it('handles cardContent with no suggestions gracefully', () => {
286
234
  const item = {
287
235
  rcsContent: {
@@ -43,7 +43,6 @@ export const RCS_CTA_URL_TYPE = {
43
43
  export const BTN_MAX_LENGTH = 20;
44
44
  export const PHONE_NUMBER_MAX_LENGTH = 15;
45
45
  export const URL_MAX_LENGTH = 2000;
46
- export const DYNAMIC_URL_SUFFIX = '{{1}}';
47
46
 
48
47
  export const CTA_TYPE_RADIO_OPTIONS = [
49
48
  { label: <FormattedMessage {...messages.ctaPhoneNo} />, value: RCS_BUTTON_TYPES.PHONE_NUMBER },
@@ -17,8 +17,7 @@ import CapButton from '@capillarytech/cap-ui-library/CapButton';
17
17
  import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
18
18
  import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
19
19
  import { CAP_SPACE_04 } from '@capillarytech/cap-ui-library/styled/variables';
20
- import globalMessages from '../../v2Containers/Cap/messages';
21
- import TagList from '../../v2Containers/TagList';
20
+ import globalMessages from '../../v2Containers/Cap/messages';import CapTagListWithInput from '../CapTagListWithInput';
22
21
 
23
22
  import { isUrl, isValidText } from '../../v2Containers/Line/Container/Wrapper/utils';
24
23
  import messages from './messages';
@@ -31,7 +30,6 @@ import {
31
30
  RCS_CTA_URL_TYPE,
32
31
  CTA_TYPE_RADIO_OPTIONS,
33
32
  CTA_URL_TYPE_SELECT_OPTIONS,
34
- DYNAMIC_URL_SUFFIX,
35
33
  } from './constants';
36
34
  import './index.scss';
37
35
  import { INITIAL_SUGGESTIONS, RCS_BUTTON_TYPES, HOST_ICS} from '../../v2Containers/Rcs/constants';
@@ -58,7 +56,6 @@ export const CapActionButton = (props) => {
58
56
  minSavedSuggestions = 0,
59
57
  hideDeleteSuggestionIndexes = [],
60
58
  } = props;
61
- const { formatMessage } = intl;
62
59
  const isHostIcs = host === HOST_ICS;
63
60
  const [urlError, setUrlError] = useState(false);
64
61
  const [buttonError, setButtonError] = useState(false);
@@ -125,8 +122,7 @@ export const CapActionButton = (props) => {
125
122
  return formatMessage(messages.ctaWebsiteUrlErrorMessage);
126
123
  }
127
124
  if (urlSubtype === RCS_CTA_URL_TYPE.DYNAMIC) {
128
- const baseUrl = trimmedUrl.replace(invalidVarRegex, '');
129
- return isUrl(baseUrl) ? false : formatMessage(messages.ctaWebsiteUrlErrorMessage);
125
+ return false;
130
126
  }
131
127
  if (!isUrl(trimmedUrl)) {
132
128
  return formatMessage(messages.ctaWebsiteUrlErrorMessage);
@@ -138,12 +134,9 @@ export const CapActionButton = (props) => {
138
134
  };
139
135
 
140
136
  const onUrlChange = ({ target }) => {
141
- const { id } = target;
137
+ const { value, id } = target;
142
138
  const row = suggestions[id] || {};
143
139
  const subtype = row.urlType || RCS_CTA_URL_TYPE.STATIC;
144
- const value = subtype === RCS_CTA_URL_TYPE.DYNAMIC
145
- ? target.value.replace(invalidVarRegex, '')
146
- : target.value;
147
140
  setUrlError(validateCtaUrlValue(value, subtype));
148
141
  updateHandler(HANDLERS.URL, value, id);
149
142
  };
@@ -156,14 +149,6 @@ export const CapActionButton = (props) => {
156
149
  updateButtonChange(cloned, index);
157
150
  };
158
151
 
159
- const onTagSelect = (data, index, url) => {
160
- updateHandler(HANDLERS.URL, (url || '').replace(DYNAMIC_URL_SUFFIX, `{{${data}}}`), index);
161
- };
162
-
163
- const revertTagSelect = (index, url) => {
164
- updateHandler(HANDLERS.URL, (url || '').replace(invalidVarRegex, DYNAMIC_URL_SUFFIX), index);
165
- };
166
-
167
152
  const onPhoneNoChange = (value, index) => {
168
153
  updateHandler(HANDLERS.PHONE_NUMBER, value, index);
169
154
  };
@@ -176,25 +161,12 @@ export const CapActionButton = (props) => {
176
161
  return true;
177
162
  } if (type === RCS_BUTTON_TYPES.CTA) {
178
163
  const subtype = urlType || RCS_CTA_URL_TYPE.STATIC;
179
- if (validateCtaUrlValue(url, subtype)) {
180
- return true;
181
- }
182
- // A dynamic URL still holding the unresolved {{1}} placeholder has no real
183
- // personalization tag assigned yet — block save until one is picked via "Add URL label".
184
- return subtype === RCS_CTA_URL_TYPE.DYNAMIC && (url || '').includes(DYNAMIC_URL_SUFFIX);
164
+ return !!validateCtaUrlValue(url, subtype);
185
165
  }
186
166
  return false;
187
167
  };
188
168
 
189
169
  const saveCta = (index) => {
190
- const cta = suggestions[index];
191
- if (
192
- cta?.type === RCS_BUTTON_TYPES.CTA
193
- && cta?.urlType === RCS_CTA_URL_TYPE.DYNAMIC
194
- && !(cta?.url || '').includes(DYNAMIC_URL_SUFFIX)
195
- ) {
196
- cta.url = `${cta.url || ''}${DYNAMIC_URL_SUFFIX}`;
197
- }
198
170
  updateHandler(HANDLERS.IS_SAVED, true, index);
199
171
  };
200
172
 
@@ -208,6 +180,8 @@ export const CapActionButton = (props) => {
208
180
  updateButtonChange(newSuggestion, suggestions?.length);
209
181
  };
210
182
 
183
+ const { formatMessage } = intl;
184
+
211
185
  const hideDeleteForSuggestionIndex = (idx) =>
212
186
  Array.isArray(hideDeleteSuggestionIndexes) && hideDeleteSuggestionIndexes.includes(idx);
213
187
 
@@ -341,32 +315,13 @@ export const CapActionButton = (props) => {
341
315
  </CapColumn>
342
316
  <CapColumn span={18} className="rcs-cta-url-value-col">
343
317
  <CapHeading type="h4" className="cta-label">
344
- {formatMessage(messages.ctaWebsiteUrl)}
345
- {urlSubtype === RCS_CTA_URL_TYPE.DYNAMIC && (
346
- <CapTooltipWithInfo
347
- infoIconProps={{
348
- style: { marginLeft: CAP_SPACE_04 },
349
- }}
350
- autoAdjustOverflow
351
- placement="right"
352
- title={formatMessage(messages.ctaDynamicUrlTooltip, { one: DYNAMIC_URL_SUFFIX })}
353
- />
354
- )}
318
+ {formatMessage(messages.ctaUrlField)}
355
319
  </CapHeading>
356
320
  <CapInput
357
321
  id={index}
358
- addonAfter={
359
- urlSubtype === RCS_CTA_URL_TYPE.DYNAMIC
360
- && !(url || '').includes(DYNAMIC_URL_SUFFIX)
361
- && DYNAMIC_URL_SUFFIX
362
- }
363
322
  className="rcs-cta-url"
364
323
  onChange={onUrlChange}
365
- placeholder={
366
- urlSubtype === RCS_CTA_URL_TYPE.DYNAMIC
367
- ? formatMessage(messages.ctaDynamicPlaceholder)
368
- : formatMessage(messages.ctaEnterUrlPlaceholder)
369
- }
324
+ placeholder={formatMessage(messages.ctaEnterUrlPlaceholder)}
370
325
  value={url || ''}
371
326
  size="large"
372
327
  maxLength={URL_MAX_LENGTH}
@@ -448,7 +403,7 @@ export const CapActionButton = (props) => {
448
403
  span={1}
449
404
  className={`${ctaIsPhone ? 'whatsapp-saved-cta-phone-icon' : ''}`}
450
405
  >
451
- <CapIcon size="s" type={ctaIsPhone ? 'call' : (ctaIsReply ? 'small-link' : 'open-in-new')} />
406
+ <CapIcon size="s" type={ctaIsPhone ? 'call' : (ctaIsReply ? 'small-link' : 'launch')} />
452
407
  </CapColumn>
453
408
  <CapColumn span={6}>
454
409
  <CapLabel
@@ -475,28 +430,6 @@ export const CapActionButton = (props) => {
475
430
  </CapTooltip>
476
431
  </>
477
432
  )}
478
- {isEditFlow && urlSubtype === RCS_CTA_URL_TYPE.DYNAMIC && (url || '').includes(DYNAMIC_URL_SUFFIX) && (
479
- <TagList
480
- className="rcs-cta-taglist"
481
- label={formatMessage(messages.ctaTagListLabel)}
482
- onTagSelect={(data) => onTagSelect(data, index, url)}
483
- location={location}
484
- tags={tags}
485
- injectedTags={injectedTags}
486
- selectedOfferDetails={selectedOfferDetails}
487
- onContextChange={onContextChange}
488
- />
489
- )}
490
- {isEditFlow && urlSubtype === RCS_CTA_URL_TYPE.DYNAMIC && !(url || '').includes(DYNAMIC_URL_SUFFIX) && (
491
- <CapTooltip title={formatMessage(messages.ctaTagListRevert)} placement="top">
492
- <CapIcon
493
- size="s"
494
- type="return"
495
- className="rcs-cta-tag-revert"
496
- onClick={() => revertTagSelect(index, url)}
497
- />
498
- </CapTooltip>
499
- )}
500
433
  {(isFullMode && !isEditFlow) && (
501
434
  <div className="rcs-saved-cta-action-icons">
502
435
  <CapIcon
@@ -84,19 +84,6 @@
84
84
  .rcs-saved-cta-delete-icon {
85
85
  cursor: pointer;
86
86
  }
87
-
88
- // Dynamic-URL tag controls: keep them from stretching the flex row's
89
- // height/width — same treatment as the edit/delete icons above.
90
- .rcs-cta-tag-revert {
91
- display: flex;
92
- align-items: center;
93
- flex-shrink: 0;
94
- cursor: pointer;
95
- }
96
-
97
- .rcs-cta-taglist {
98
- flex-shrink: 0;
99
- }
100
87
  }
101
88
 
102
89
  // Button text / URL: count via CapInput `suffix` (antd affix, right-aligned inside field).
@@ -199,17 +199,4 @@ export default defineMessages({
199
199
  id: `${prefix}.addLabels`,
200
200
  defaultMessage: 'Add Labels',
201
201
  },
202
- ctaDynamicUrlTooltip: {
203
- id: `${prefix}.ctaDynamicUrlTooltip`,
204
- defaultMessage:
205
- 'Only one variable can be added to a URL. No need to add {one} to the end of the URL',
206
- },
207
- ctaTagListLabel: {
208
- id: `${prefix}.ctaTagListLabel`,
209
- defaultMessage: 'Add URL label',
210
- },
211
- ctaTagListRevert: {
212
- id: `${prefix}.ctaTagListRevert`,
213
- defaultMessage: 'Reset website URL label to default value',
214
- },
215
202
  });
@@ -4,9 +4,7 @@ import '@testing-library/jest-dom';
4
4
  import { render, screen, fireEvent } from '../../../utils/test-utils';
5
5
  import { waitFor } from '@testing-library/react';
6
6
  import { CapActionButton } from '../index';
7
- import {
8
- BTN_MAX_LENGTH, PHONE_NUMBER_MAX_LENGTH, URL_MAX_LENGTH, RCS_CTA_URL_TYPE, DYNAMIC_URL_SUFFIX,
9
- } from '../constants';
7
+ import { BTN_MAX_LENGTH, PHONE_NUMBER_MAX_LENGTH, URL_MAX_LENGTH } from '../constants';
10
8
  import { RCS_BUTTON_TYPES, HOST_ICS } from '../../../v2Containers/Rcs/constants';
11
9
 
12
10
  const updateHandler = jest.fn();
@@ -29,7 +27,6 @@ const initializeComponent = (
29
27
  text: d.displayText ?? d.text ?? '',
30
28
  phoneNumber: d.phoneNumber ?? '',
31
29
  url: d.url ?? '',
32
- urlType: d.urlType,
33
30
  postback: d.postback ?? '',
34
31
  isSaved: d.isSaved ?? false,
35
32
  }));
@@ -554,34 +551,6 @@ describe('CapActionButton', () => {
554
551
  expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
555
552
  });
556
553
 
557
- it('should disable save for a dynamic-URL CTA still holding the unresolved {{1}} placeholder', () => {
558
- const initial = {
559
- index: 0,
560
- type: RCS_BUTTON_TYPES.CTA,
561
- text: 'Visit here',
562
- url: `https://example.com${DYNAMIC_URL_SUFFIX}`,
563
- urlType: RCS_CTA_URL_TYPE.DYNAMIC,
564
- postback: 'Visit here',
565
- isSaved: false,
566
- };
567
- initializeComponent([initial]);
568
- expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
569
- });
570
-
571
- it('should enable save for a dynamic-URL CTA once the placeholder has been replaced with a real tag', () => {
572
- const initial = {
573
- index: 0,
574
- type: RCS_BUTTON_TYPES.CTA,
575
- text: 'Visit here',
576
- url: 'https://example.com{{first_name}}',
577
- urlType: RCS_CTA_URL_TYPE.DYNAMIC,
578
- postback: 'Visit here',
579
- isSaved: false,
580
- };
581
- initializeComponent([initial]);
582
- expect(screen.getByRole('button', { name: /save/i })).not.toBeDisabled();
583
- });
584
-
585
554
  it('should render edit and delete icons in edit mode (renderedContent)', () => {
586
555
  const button = {
587
556
  index: 1,
@@ -19,6 +19,30 @@ import { sanitizeCardVarMappedValue } from './utils';
19
19
 
20
20
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
21
21
 
22
+ /**
23
+ * Assigns every variable token across the whole carousel a stable `cardVarMapped` key (a plain
24
+ * number, title then description, per card, in encounter order) — {cardIndex: {title: [key, …],
25
+ * description: [key, …]}}. Each occurrence is its own independent slot — even two tokens sharing
26
+ * the same literal tag name (e.g. "{{name}}" in both a card's title and description) get separate
27
+ * keys and are filled independently. A slot's key depends only on where its token sits in the
28
+ * template text, never on which tag currently fills it, so re-picking a different tag for the
29
+ * same slot never changes its key.
30
+ */
31
+ export const computeCarouselVarSlotKeys = (carouselData, rcsVarRegex) => {
32
+ let nextNumber = 1;
33
+ return (carouselData || []).map((card = {}) => {
34
+ const keysForField = (fieldStr) => (fieldStr || '').match(rcsVarRegex)?.map(() => {
35
+ const key = String(nextNumber);
36
+ nextNumber += 1;
37
+ return key;
38
+ }) ?? [];
39
+ return {
40
+ title: keysForField(card.title),
41
+ description: keysForField(card.description),
42
+ };
43
+ });
44
+ };
45
+
22
46
  export const getCarouselImageAssetIndex = (cardIndex) =>
23
47
  RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
24
48
  export const getCarouselVideoAssetIndex = (cardIndex) =>
@@ -142,22 +166,23 @@ export const getCarouselDescriptionCharacterCount = (carouselData, cardIndex) =>
142
166
  return descriptionText ? descriptionText.length : 0;
143
167
  };
144
168
 
145
- /** Preview (TestAndPreviewSlidebox / listing preview): resolve each card's title/desc through `resolveTemplateWithMap`. */
146
- export const buildCarouselCardsForPreview = (cards = [], { isFullMode, rcsVarRegex, resolveTemplateWithMap }) => {
147
- // Track cumulative variable slot index across all cards so resolveTemplateWithMap
148
- // uses the correct globalSlot slotKey for each token (e.g. card 1's {{3}} needs
149
- // slotOffset=2 so slotKey becomes "3", not "1").
150
- let slotOffset = 0;
151
- return (cards || []).map((card = {}) => {
169
+ /**
170
+ * Preview (TestAndPreviewSlidebox / listing preview): resolve each card's title/desc through
171
+ * `resolveCarouselTemplateWithMap(str, cardIndex, isDescField)` each variable slot's key is its
172
+ * global position across the whole carousel (see `getCarouselGlobalVarSlotKey`), so two cards can
173
+ * never collide even when they pick the same tag name for their own slot.
174
+ */
175
+ export const buildCarouselCardsForPreview = (cards = [], { isFullMode, resolveCarouselTemplateWithMap }) =>
176
+ (cards || []).map((card = {}, cardIndex) => {
152
177
  const rawTitle = card.title || '';
153
178
  const rawDesc = card.description || '';
154
179
  const titleVarCount = (rawTitle.match(rcsVarRegex) || []).length;
155
180
  const descVarCount = (rawDesc.match(rcsVarRegex) || []).length;
156
181
  const resolvedTitle = !isFullMode
157
- ? resolveTemplateWithMap(rawTitle, slotOffset)
182
+ ? resolveCarouselTemplateWithMap(rawTitle, cardIndex, false)
158
183
  : rawTitle;
159
184
  const resolvedDesc = !isFullMode
160
- ? resolveTemplateWithMap(rawDesc, slotOffset + titleVarCount)
185
+ ? resolveCarouselTemplateWithMap(rawDesc, cardIndex, true)
161
186
  : rawDesc;
162
187
  if (!isFullMode) {
163
188
  slotOffset += titleVarCount + descVarCount;
@@ -182,43 +207,50 @@ export const buildCarouselCardContentForPayload = (carouselData, {
182
207
  cardVarMapped,
183
208
  selectedCarouselHeight,
184
209
  rcsVarRegex,
185
- }) => (carouselData || []).map((card = {}) => {
186
- const cardMediaType = card.mediaType;
187
- const isCardVideo = cardMediaType === RCS_MEDIA_TYPES.VIDEO;
188
- const isCardImage = cardMediaType === RCS_MEDIA_TYPES.IMAGE;
189
- const mediaUrl = isCardVideo
190
- ? (card.videoAsset?.videoSrc || '')
191
- : (card.imageSrc || '');
192
- const thumbnailUrl = isCardVideo
193
- ? (card.thumbnailSrc || card.videoAsset?.videoThumbnail || '')
194
- : '';
195
- const cardVarTokens = [
196
- ...((card.title || '').match(rcsVarRegex) ?? []),
197
- ...((card.description || '').match(rcsVarRegex) ?? []),
198
- ];
199
- const cardVarMappedForCard = {};
200
- if (isSlotMappingMode && cardVarTokens.length > 0) {
201
- cardVarTokens.forEach((token) => {
210
+ }) => {
211
+ const slotKeysByCard = computeCarouselVarSlotKeys(carouselData, rcsVarRegex);
212
+ return (carouselData || []).map((card = {}, cardIndex) => {
213
+ const cardMediaType = card.mediaType;
214
+ const isCardVideo = cardMediaType === RCS_MEDIA_TYPES.VIDEO;
215
+ const isCardImage = cardMediaType === RCS_MEDIA_TYPES.IMAGE;
216
+ const mediaUrl = isCardVideo
217
+ ? (card.videoAsset?.videoSrc || '')
218
+ : (card.imageSrc || '');
219
+ const thumbnailUrl = isCardVideo
220
+ ? (card.thumbnailSrc || card.videoAsset?.videoThumbnail || '')
221
+ : '';
222
+ const cardVarTokens = [
223
+ ...((card.title || '').match(rcsVarRegex) ?? []),
224
+ ...((card.description || '').match(rcsVarRegex) ?? []),
225
+ ];
226
+ const cardSlotKeys = [
227
+ ...(slotKeysByCard[cardIndex]?.title ?? []),
228
+ ...(slotKeysByCard[cardIndex]?.description ?? []),
229
+ ];
230
+ const cardVarMappedForCard = {};
231
+ cardVarTokens.forEach((token, tokenIndex) => {
202
232
  const varName = getVarNameFromToken(token);
203
- if (!varName) return;
204
- cardVarMappedForCard[varName] = sanitizeCardVarMappedValue(cardVarMapped?.[varName] ?? '');
233
+ const slotKey = cardSlotKeys[tokenIndex];
234
+ if (!varName || !isSlotMappingMode) return;
235
+ const scopedValue = cardVarMapped?.[slotKey] ?? '';
236
+ cardVarMappedForCard[slotKey] = sanitizeCardVarMappedValue(scopedValue);
205
237
  });
206
- }
207
- return {
208
- title: card.title || '',
209
- description: card.description || '',
210
- mediaType: cardMediaType,
211
- ...((isCardImage || isCardVideo) && {
212
- media: {
213
- mediaUrl,
214
- thumbnailUrl,
215
- height: selectedCarouselHeight || MEDIUM,
216
- ...(isCardVideo && card.videoAsset?.videoName && { videoName: card.videoAsset.videoName }),
217
- },
218
- }),
219
- ...(Array.isArray(card.suggestions) && card.suggestions.length > 0 && {
220
- suggestions: card.suggestions,
221
- }),
222
- ...(Object.keys(cardVarMappedForCard).length > 0 && { cardVarMapped: cardVarMappedForCard }),
223
- };
224
- });
238
+ return {
239
+ title: card.title || '',
240
+ description: card.description || '',
241
+ mediaType: cardMediaType,
242
+ ...((isCardImage || isCardVideo) && {
243
+ media: {
244
+ mediaUrl,
245
+ thumbnailUrl,
246
+ height: selectedCarouselHeight || MEDIUM,
247
+ ...(isCardVideo && card.videoAsset?.videoName && { videoName: card.videoAsset.videoName }),
248
+ },
249
+ }),
250
+ ...(Array.isArray(card.suggestions) && card.suggestions.length > 0 && {
251
+ suggestions: card.suggestions,
252
+ }),
253
+ ...(Object.keys(cardVarMappedForCard).length > 0 && { cardVarMapped: cardVarMappedForCard }),
254
+ };
255
+ });
256
+ };
@@ -15,6 +15,7 @@ import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
15
15
  import TagList from '../../TagList';
16
16
  import globalMessages from '../../Cap/messages';
17
17
  import messages from '../messages';
18
+ import { RCS_TAG_AREA_FIELD_TITLE, RCS_TAG_AREA_FIELD_DESC } from '../constants';
18
19
  import CarouselCardMedia from './CarouselCardMedia';
19
20
  import CarouselCardButtons from './CarouselCardButtons';
20
21
  import CarouselCharacterCount from './CarouselCharacterCount';
@@ -50,7 +51,8 @@ const CarouselCard = ({
50
51
  onDescriptionChange,
51
52
  onTitleAddVar,
52
53
  onDescriptionAddVar,
53
- onTagSelect,
54
+ onTitleTagSelect,
55
+ onDescTagSelect,
54
56
  onContextChange,
55
57
  renderEditMessage,
56
58
  onFieldChange,
@@ -137,7 +139,7 @@ const CarouselCard = ({
137
139
  (isEditFlow || !isFullMode) ? (
138
140
  <TagList
139
141
  label={formatMessage(globalMessages.addLabels)}
140
- onTagSelect={onTagSelect}
142
+ onTagSelect={onTitleTagSelect}
141
143
  location={location}
142
144
  tags={tagsForPicker}
143
145
  onContextChange={onContextChange}
@@ -159,7 +161,7 @@ const CarouselCard = ({
159
161
  />
160
162
  <CapRow className="rcs_text_area_wrapper">
161
163
  {(isEditFlow || !isFullMode) ? (
162
- renderEditMessage(card.title || '')
164
+ renderEditMessage(card.title || '', cardIndex, RCS_TAG_AREA_FIELD_TITLE)
163
165
  ) : (
164
166
  <CapInput
165
167
  value={card.title || ''}
@@ -189,7 +191,7 @@ const CarouselCard = ({
189
191
  (isEditFlow || !isFullMode) ? (
190
192
  <TagList
191
193
  label={formatMessage(globalMessages.addLabels)}
192
- onTagSelect={onTagSelect}
194
+ onTagSelect={onDescTagSelect}
193
195
  location={location}
194
196
  tags={tagsForPicker}
195
197
  onContextChange={onContextChange}
@@ -211,7 +213,7 @@ const CarouselCard = ({
211
213
  />
212
214
  <CapRow className="rcs_text_area_wrapper">
213
215
  {(isEditFlow || !isFullMode) ? (
214
- renderEditMessage(card.description || '')
216
+ renderEditMessage(card.description || '', cardIndex, RCS_TAG_AREA_FIELD_DESC)
215
217
  ) : (
216
218
  <TextArea
217
219
  autosize={{ minRows: 3, maxRows: 5 }}
@@ -249,11 +251,6 @@ const CarouselCard = ({
249
251
  hostName={hostName}
250
252
  formatMessage={formatMessage}
251
253
  onSuggestionsChange={onSuggestionsChange}
252
- location={location}
253
- tags={tagsForPicker}
254
- injectedTags={injectedTags}
255
- selectedOfferDetails={selectedOfferDetails}
256
- onContextChange={onContextChange}
257
254
  />
258
255
  </CapCard>
259
256
  );
@@ -286,7 +283,8 @@ CarouselCard.propTypes = {
286
283
  onDescriptionChange: PropTypes.func.isRequired,
287
284
  onTitleAddVar: PropTypes.func.isRequired,
288
285
  onDescriptionAddVar: PropTypes.func.isRequired,
289
- onTagSelect: PropTypes.func.isRequired,
286
+ onTitleTagSelect: PropTypes.func.isRequired,
287
+ onDescTagSelect: PropTypes.func.isRequired,
290
288
  onContextChange: PropTypes.func,
291
289
  renderEditMessage: PropTypes.func.isRequired,
292
290
  onFieldChange: PropTypes.func.isRequired,
@@ -18,11 +18,6 @@ const CarouselCardButtons = ({
18
18
  hostName,
19
19
  formatMessage,
20
20
  onSuggestionsChange,
21
- location,
22
- tags,
23
- injectedTags,
24
- selectedOfferDetails,
25
- onContextChange,
26
21
  }) => {
27
22
  const suggestionsForCard = suggestions || [];
28
23
  return (
@@ -73,11 +68,6 @@ const CarouselCardButtons = ({
73
68
  host={hostName}
74
69
  minSavedSuggestions={cardIndex === 0 ? 1 : 0}
75
70
  hideDeleteSuggestionIndexes={cardIndex === 0 ? [0] : []}
76
- location={location}
77
- tags={tags}
78
- injectedTags={injectedTags}
79
- selectedOfferDetails={selectedOfferDetails}
80
- onContextChange={onContextChange}
81
71
  />
82
72
  </>
83
73
  );
@@ -91,11 +81,6 @@ CarouselCardButtons.propTypes = {
91
81
  hostName: PropTypes.string,
92
82
  formatMessage: PropTypes.func.isRequired,
93
83
  onSuggestionsChange: PropTypes.func.isRequired,
94
- location: PropTypes.object,
95
- tags: PropTypes.array,
96
- injectedTags: PropTypes.object,
97
- selectedOfferDetails: PropTypes.array,
98
- onContextChange: PropTypes.func,
99
84
  };
100
85
 
101
86
  CarouselCardButtons.defaultProps = {
@@ -103,11 +88,6 @@ CarouselCardButtons.defaultProps = {
103
88
  isEditFlow: false,
104
89
  isFullMode: false,
105
90
  hostName: '',
106
- location: {},
107
- tags: [],
108
- injectedTags: {},
109
- selectedOfferDetails: [],
110
- onContextChange: () => {},
111
91
  };
112
92
 
113
93
  export default React.memo(CarouselCardButtons);