@capillarytech/creatives-library 9.0.35 → 9.0.36

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 (51) hide show
  1. package/app.js +175 -10
  2. package/entry.js +1 -67
  3. package/mfe-exposed-components.js +4 -2
  4. package/package.json +2 -2
  5. package/services/api.js +1 -9
  6. package/styles/containers/layout/_layoutPage.scss +6 -8
  7. package/utils/gtmTrackers/gtmEvents/creativeDetails.js +1 -2
  8. package/utils/rcsPayloadUtils.js +2 -5
  9. package/utils/tests/rcsPayloadUtils.test.js +2 -54
  10. package/v2Components/CapActionButton/constants.js +0 -1
  11. package/v2Components/CapActionButton/index.js +9 -77
  12. package/v2Components/CapActionButton/index.scss +0 -13
  13. package/v2Components/CapActionButton/messages.js +0 -13
  14. package/v2Components/CapActionButton/tests/index.test.js +1 -32
  15. package/v2Components/CommonTestAndPreview/index.js +15 -44
  16. package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -78
  17. package/v2Components/CommonTestAndPreview/utils.js +0 -34
  18. package/v2Components/NavigationBar/index.js +7 -9
  19. package/v2Components/NavigationBar/tests/index.test.js +19 -39
  20. package/v2Components/SmsFallback/index.js +0 -6
  21. package/v2Containers/Cap/constants.js +0 -1
  22. package/v2Containers/Cap/index.js +27 -57
  23. package/v2Containers/CreativesContainer/index.js +8 -11
  24. package/v2Containers/CreativesContainer/tests/index.test.js +0 -53
  25. package/v2Containers/Rcs/carouselUtils.js +73 -46
  26. package/v2Containers/Rcs/components/CarouselCard.js +9 -11
  27. package/v2Containers/Rcs/components/CarouselCardButtons.js +0 -20
  28. package/v2Containers/Rcs/constants.js +7 -2
  29. package/v2Containers/Rcs/index.js +205 -226
  30. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +47 -60
  31. package/v2Containers/Rcs/tests/CarouselCard.test.js +2 -1
  32. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -286
  33. package/v2Containers/Rcs/tests/carouselUtils.test.js +32 -28
  34. package/v2Containers/Rcs/tests/index.test.js +238 -151
  35. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -110
  36. package/v2Containers/Rcs/tests/utils.test.js +17 -36
  37. package/v2Containers/Rcs/utils.js +55 -28
  38. package/v2Containers/SmsTrai/Edit/index.js +24 -35
  39. package/v2Containers/Templates/_templates.scss +1 -1
  40. package/v2Containers/Templates/index.js +0 -1
  41. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +0 -5
  42. package/v2Containers/TemplatesV2/TemplatesV2.style.js +3 -8
  43. package/v2Containers/TemplatesV2/index.js +9 -22
  44. package/AppRoot.js +0 -124
  45. package/app-config.js +0 -61
  46. package/bootstrap.js +0 -185
  47. package/utils/getDataLayer.js +0 -15
  48. package/utils/mfeDetect.js +0 -1
  49. package/utils/mfeFirstPaintReady.js +0 -29
  50. package/utils/mfeHistory.js +0 -62
  51. package/v2Components/NavigationBar/mfeModuleHeader.config.js +0 -16
@@ -30,7 +30,6 @@ import {
30
30
  REQUEST,
31
31
  DEFAULT,
32
32
  ENABLE_PRODUCT_SUPPORT_VIDEOS,
33
- ENABLE_NEW_LEFT_NAVIGATION,
34
33
  CAMPAIGN_SETTINGS_URL,
35
34
  } from './constants';
36
35
  import './_cap.scss';
@@ -54,8 +53,7 @@ import { v2ViberSagas } from '../Viber/sagas';
54
53
  import { v2FacebookSagas } from '../Facebook/sagas';
55
54
  import createReducer from '../Line/Container/reducer';
56
55
  import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
57
- import { getDataLayer } from '../../utils/getDataLayer';
58
- const gtm = getDataLayer();
56
+ const gtm = window.dataLayer || [];
59
57
  const {
60
58
  logNewTab,
61
59
  utilsGetOrgNameFromId,
@@ -74,19 +72,18 @@ const CapWrapper = styled.div`
74
72
  min-height: 100%;
75
73
  padding: 0;
76
74
  flex-direction: column;
77
- position: relative;
78
75
  `;
79
76
  GA.initialize({ accessKey: GTM_TRACKING_ID, trackUIError: true, trackLoadPerformance: 'creatives' });
80
77
 
81
78
  const MainWrapper = styled.div`
82
79
  position: relative;
83
- min-height: 100%;
84
- top: 0;
80
+ min-height: calc(100vh - 5.29rem);
81
+ top: 4.72rem;
85
82
  `;
86
83
 
87
84
  const ContentWrapper = styled.div`
88
85
  && {
89
- margin-left: 0;
86
+ margin-left: ${(props) => (props.leftNavbarExpanded ? '17rem' : '4.475rem')};
90
87
  }
91
88
  `;
92
89
 
@@ -121,16 +118,12 @@ export class Cap extends React.Component { // eslint-disable-line react/prefer-s
121
118
  const userGtmData = this.getUserGtmData();
122
119
  this.setDimensionData(userGtmData);
123
120
  gtm.push({ ...userGtmData, event: 'userAuthenticated' });
124
- // In MFE mode the host already initialized auth (window.capAuth is set with
125
- // the full superset); skip so we don't clobber it with the basic shape.
126
- if (!window.capAuth) {
127
- Auth.initialize({
128
- permissions: userData.accessiblePermissions,
129
- isCapUser: userData.isCapUser,
130
- roles: Object.keys(userData.accessRoles),
131
- accessibleFeatures,
132
- });
133
- }
121
+ Auth.initialize({
122
+ permissions: userData.accessiblePermissions,
123
+ isCapUser: userData.isCapUser,
124
+ roles: Object.keys(userData.accessRoles),
125
+ accessibleFeatures,
126
+ });
134
127
  });
135
128
  this.props.actions.getTopbarMenuData(parentModule);
136
129
  if (this.props.Global.orgID !== undefined) {
@@ -507,8 +500,6 @@ export class Cap extends React.Component { // eslint-disable-line react/prefer-s
507
500
  const type = this.props.location.query.type;
508
501
  const toastMessages = this.props.Global.messages;
509
502
  const { isCreativesAccessible, showOrgChangeModal, showRefreshModal } = this.state;
510
- const { accessibleFeatures = [] } = currentOrgDetails;
511
- const isLatestLeftNavigationEnabled = accessibleFeatures.includes(ENABLE_NEW_LEFT_NAVIGATION);
512
503
  return (
513
504
  <CapWrapper>
514
505
  { this.props.loader.localeLoading || this.props.Global.fetching_userdata ?
@@ -546,47 +537,26 @@ export class Cap extends React.Component { // eslint-disable-line react/prefer-s
546
537
  campaignOrgV2Status={currentOrgDetails.org_campaign_v2_status}
547
538
  handleLeftNavBarExpanded={this.handleLeftNavBarExpanded}
548
539
  leftNavbarExpandedProp={this.state.leftNavbarExpanded}
540
+ />) : ''}
541
+ <MainWrapper className="main">
549
542
 
543
+ <ContentWrapper
544
+ className={`main-content ${this.state.leftNavbarExpanded ? "contet-width-collapse" : "content-width-expanded"}`}
545
+ leftNavbarExpanded={this.state.leftNavbarExpanded}
550
546
  >
551
- <MainWrapper isLatestLeftNavigationEnabled={isLatestLeftNavigationEnabled} className="main">
552
- <ContentWrapper
553
- className={`main-content ${this.state.leftNavbarExpanded ? "contet-width-collapse" : "content-width-expanded"}`}
554
- isLatestLeftNavigationEnabled={isLatestLeftNavigationEnabled}
555
- leftNavbarExpanded={this.state.leftNavbarExpanded}
556
- >
557
- <Switch>
558
- {componentRoutes.map(routeProps => {
559
- return (
560
- <RenderRoute
561
- {...routeProps}
562
- key={routeProps.path}
563
- isLoggedIn={isLoggedIn}
564
- />
565
- )}
566
- )}
567
- </Switch>
568
- </ContentWrapper>
569
- </MainWrapper>
570
- </NavigationBar>) : (
571
- <MainWrapper isLatestLeftNavigationEnabled={isLatestLeftNavigationEnabled} className="main">
572
- <ContentWrapper
573
- className={`main-content ${this.state.leftNavbarExpanded ? "contet-width-collapse" : "content-width-expanded"}`}
574
- isLatestLeftNavigationEnabled={isLatestLeftNavigationEnabled}
575
- leftNavbarExpanded={this.state.leftNavbarExpanded}
576
- >
577
- <Switch>
578
- {componentRoutes.map(routeProps => {
579
- return (
580
- <RenderRoute
581
- {...routeProps}
582
- key={routeProps.path}
583
- isLoggedIn={isLoggedIn}
584
- />
585
- )}
547
+ <Switch>
548
+ {componentRoutes.map(routeProps => {
549
+ return (
550
+ <RenderRoute
551
+ {...routeProps}
552
+ key={routeProps.path}
553
+ isLoggedIn={isLoggedIn}
554
+ />
586
555
  )}
587
- </Switch>
588
- </ContentWrapper>
589
- </MainWrapper>)}
556
+ )}
557
+ </Switch>
558
+ </ContentWrapper>
559
+ </MainWrapper>
590
560
  </div>
591
561
  {(toastMessages && toastMessages.length > 0) &&
592
562
  toastMessages.map((message) => {
@@ -90,8 +90,8 @@ import {
90
90
  import {EXTERNAL_URL, SITE_URL, WEBPUSH_MEDIA_TYPES} from '../WebPush/constants';
91
91
  import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
92
92
  import { RCS_STATUSES, contentType as RCS_CONTENT_TYPE, EMBEDDED } from '../Rcs/constants';
93
- import { pickRcsCardVarMappedEntries } from '../Rcs/rcsLibraryHydrationUtils';
94
93
  import { mapRcsCardContentForConsumerWithResolvedTags } from '../Rcs/utils';
94
+ import { pickRcsCardVarMappedEntries } from '../Rcs/rcsLibraryHydrationUtils';
95
95
  import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
96
96
  import { CREATIVE } from '../Facebook/constants';
97
97
  import { LOYALTY } from '../App/constants';
@@ -1442,21 +1442,13 @@ export class Creatives extends React.Component {
1442
1442
  cardSettings = {},
1443
1443
  accountId = "",
1444
1444
  } = get(versions, 'base.content.RCS.rcsContent', {});
1445
+ const rootRcsCardVarMappedFromSubmit = get(template, 'value.rcsCardVarMapped');
1445
1446
  const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
1446
1447
  ? cardContentFromSubmit[0]
1447
1448
  : null;
1448
- const cardVarMappedFromFirstRcsCard =
1449
- firstCardFromSubmit?.cardVarMapped != null
1450
- && typeof firstCardFromSubmit.cardVarMapped === 'object'
1451
- ? pickRcsCardVarMappedEntries(firstCardFromSubmit.cardVarMapped)
1452
- : null;
1453
- // Campaigns/consumers should see the literal tag (e.g. `{{first_name}}`) in title/description,
1454
- // not the internal numeric slot placeholder (`{{1}}`) — that slot scheme is only meaningful
1455
- // to this editor's own reopen/hydration (see getFormData, which persists raw cardContentFromSubmit
1456
- // separately for that round-trip and is unaffected by this).
1457
1449
  const cardContent = mapRcsCardContentForConsumerWithResolvedTags(
1458
1450
  cardContentFromSubmit,
1459
- cardVarMappedFromFirstRcsCard,
1451
+ rootRcsCardVarMappedFromSubmit,
1460
1452
  isFullModeForRcsConsumerPayload,
1461
1453
  );
1462
1454
  const rcsContent = {
@@ -1465,6 +1457,11 @@ export class Creatives extends React.Component {
1465
1457
  cardSettings,
1466
1458
  cardContent,
1467
1459
  };
1460
+ const cardVarMappedFromFirstRcsCard =
1461
+ firstCardFromSubmit?.cardVarMapped != null
1462
+ && typeof firstCardFromSubmit.cardVarMapped === 'object'
1463
+ ? pickRcsCardVarMappedEntries(firstCardFromSubmit.cardVarMapped)
1464
+ : null;
1468
1465
  const includeRootRcsCardVarMappedOnConsumerPayload =
1469
1466
  cardVarMappedFromFirstRcsCard
1470
1467
  && Object.keys(cardVarMappedFromFirstRcsCard).length > 0
@@ -267,59 +267,6 @@ describe('Test SlideBoxContent container', () => {
267
267
  expect(instance.state.templateData.rcsCardVarMapped).toEqual(cardVarMapped);
268
268
  });
269
269
 
270
- it('RCS getCreativesData sends the literal tag (not the numeric slot) in title/description to consumers', async () => {
271
- renderedComponent = shallowWithIntl(
272
- <Creatives
273
- loyaltyMetaData={loyaltyMetaData}
274
- Templates={rcsTemplates}
275
- channel="RCS"
276
- slidBoxContent="editTemplate"
277
- templateData={rcsEditTemplateData}
278
- handleCloseCreatives={handleCloseCreatives}
279
- getCreativesData={getCreativesData}
280
- isFullMode={false}
281
- templateActions={{
282
- getCdnTransformationConfig,
283
- }}
284
- />,
285
- );
286
- renderedComponent.instance().onEditTemplate();
287
-
288
- const mockValue = {
289
- validity: true,
290
- value: {
291
- name: 'rcs_creative_name',
292
- type: 'RCS',
293
- versions: {
294
- base: {
295
- content: {
296
- RCS: {
297
- rcsContent: {
298
- contentType: 'RICHCARD',
299
- cardType: 'STANDALONE',
300
- cardSettings: {},
301
- cardContent: [{
302
- title: 'Hi {{1}}',
303
- description: 'Visit {{2}}',
304
- cardVarMapped: { 1: '{{first_name}}', 2: '{{loyalty_points}}' },
305
- }],
306
- },
307
- smsFallBackContent: {},
308
- },
309
- },
310
- },
311
- },
312
- },
313
- };
314
-
315
- renderedComponent.instance().getFormData(mockValue);
316
- await tick();
317
-
318
- const sentCardContent = getCreativesData.mock.calls[0][0].rcsContent.cardContent[0];
319
- expect(sentCardContent.title).toBe('Hi {{first_name}}');
320
- expect(sentCardContent.description).toBe('Visit {{loyalty_points}}');
321
- });
322
-
323
270
  it('Text getCreatives data for rcs, data from creatives done to campaigns', async () => {
324
271
  renderFunction('SMS', 'editTemplate', smsTemplates, smsEditTemplateData );
325
272
  renderedComponent.instance().onEditTemplate();
@@ -19,8 +19,29 @@ import { sanitizeCardVarMappedValue } from './utils';
19
19
 
20
20
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
21
21
 
22
- export const getCarouselVarMapKey = (cardIndex, varName) =>
23
- (cardIndex === 0 ? varName : `c${cardIndex}::${varName}`);
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
+ };
24
45
 
25
46
  export const getCarouselImageAssetIndex = (cardIndex) =>
26
47
  RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
@@ -147,19 +168,19 @@ export const getCarouselDescriptionCharacterCount = (carouselData, cardIndex) =>
147
168
 
148
169
  /**
149
170
  * Preview (TestAndPreviewSlidebox / listing preview): resolve each card's title/desc through
150
- * `resolveCarouselTemplateWithMap(str, cardIndex)` — a per-card-scoped resolver (see
151
- * `getCarouselVarMapKey`). Each card's variables are independent, so no cross-card slot offset is
152
- * needed (unlike the old flat/global-slot scheme this replaced).
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.
153
174
  */
154
175
  export const buildCarouselCardsForPreview = (cards = [], { isFullMode, resolveCarouselTemplateWithMap }) =>
155
176
  (cards || []).map((card = {}, cardIndex) => {
156
177
  const rawTitle = card.title || '';
157
178
  const rawDesc = card.description || '';
158
179
  const resolvedTitle = !isFullMode
159
- ? resolveCarouselTemplateWithMap(rawTitle, cardIndex)
180
+ ? resolveCarouselTemplateWithMap(rawTitle, cardIndex, false)
160
181
  : rawTitle;
161
182
  const resolvedDesc = !isFullMode
162
- ? resolveCarouselTemplateWithMap(rawDesc, cardIndex)
183
+ ? resolveCarouselTemplateWithMap(rawDesc, cardIndex, true)
163
184
  : rawDesc;
164
185
  const videoThumb = card.thumbnailSrc || card.videoAsset?.videoThumbnail || '';
165
186
  const videoSrc = card.videoAsset?.videoSrc || '';
@@ -180,44 +201,50 @@ export const buildCarouselCardContentForPayload = (carouselData, {
180
201
  cardVarMapped,
181
202
  selectedCarouselHeight,
182
203
  rcsVarRegex,
183
- }) => (carouselData || []).map((card = {}, cardIndex) => {
184
- const cardMediaType = card.mediaType;
185
- const isCardVideo = cardMediaType === RCS_MEDIA_TYPES.VIDEO;
186
- const isCardImage = cardMediaType === RCS_MEDIA_TYPES.IMAGE;
187
- const mediaUrl = isCardVideo
188
- ? (card.videoAsset?.videoSrc || '')
189
- : (card.imageSrc || '');
190
- const thumbnailUrl = isCardVideo
191
- ? (card.thumbnailSrc || card.videoAsset?.videoThumbnail || '')
192
- : '';
193
- const cardVarTokens = [
194
- ...((card.title || '').match(rcsVarRegex) ?? []),
195
- ...((card.description || '').match(rcsVarRegex) ?? []),
196
- ];
197
- const cardVarMappedForCard = {};
198
- if (isSlotMappingMode && cardVarTokens.length > 0) {
199
- cardVarTokens.forEach((token) => {
204
+ }) => {
205
+ const slotKeysByCard = computeCarouselVarSlotKeys(carouselData, rcsVarRegex);
206
+ return (carouselData || []).map((card = {}, cardIndex) => {
207
+ const cardMediaType = card.mediaType;
208
+ const isCardVideo = cardMediaType === RCS_MEDIA_TYPES.VIDEO;
209
+ const isCardImage = cardMediaType === RCS_MEDIA_TYPES.IMAGE;
210
+ const mediaUrl = isCardVideo
211
+ ? (card.videoAsset?.videoSrc || '')
212
+ : (card.imageSrc || '');
213
+ const thumbnailUrl = isCardVideo
214
+ ? (card.thumbnailSrc || card.videoAsset?.videoThumbnail || '')
215
+ : '';
216
+ const cardVarTokens = [
217
+ ...((card.title || '').match(rcsVarRegex) ?? []),
218
+ ...((card.description || '').match(rcsVarRegex) ?? []),
219
+ ];
220
+ const cardSlotKeys = [
221
+ ...(slotKeysByCard[cardIndex]?.title ?? []),
222
+ ...(slotKeysByCard[cardIndex]?.description ?? []),
223
+ ];
224
+ const cardVarMappedForCard = {};
225
+ cardVarTokens.forEach((token, tokenIndex) => {
200
226
  const varName = getVarNameFromToken(token);
201
- if (!varName) return;
202
- const scopedValue = cardVarMapped?.[getCarouselVarMapKey(cardIndex, varName)] ?? '';
203
- cardVarMappedForCard[varName] = sanitizeCardVarMappedValue(scopedValue);
227
+ const slotKey = cardSlotKeys[tokenIndex];
228
+ if (!varName || !isSlotMappingMode) return;
229
+ const scopedValue = cardVarMapped?.[slotKey] ?? '';
230
+ cardVarMappedForCard[slotKey] = sanitizeCardVarMappedValue(scopedValue);
204
231
  });
205
- }
206
- return {
207
- title: card.title || '',
208
- description: card.description || '',
209
- mediaType: cardMediaType,
210
- ...((isCardImage || isCardVideo) && {
211
- media: {
212
- mediaUrl,
213
- thumbnailUrl,
214
- height: selectedCarouselHeight || MEDIUM,
215
- ...(isCardVideo && card.videoAsset?.videoName && { videoName: card.videoAsset.videoName }),
216
- },
217
- }),
218
- ...(Array.isArray(card.suggestions) && card.suggestions.length > 0 && {
219
- suggestions: card.suggestions,
220
- }),
221
- ...(Object.keys(cardVarMappedForCard).length > 0 && { cardVarMapped: cardVarMappedForCard }),
222
- };
223
- });
232
+ return {
233
+ title: card.title || '',
234
+ description: card.description || '',
235
+ mediaType: cardMediaType,
236
+ ...((isCardImage || isCardVideo) && {
237
+ media: {
238
+ mediaUrl,
239
+ thumbnailUrl,
240
+ height: selectedCarouselHeight || MEDIUM,
241
+ ...(isCardVideo && card.videoAsset?.videoName && { videoName: card.videoAsset.videoName }),
242
+ },
243
+ }),
244
+ ...(Array.isArray(card.suggestions) && card.suggestions.length > 0 && {
245
+ suggestions: card.suggestions,
246
+ }),
247
+ ...(Object.keys(cardVarMappedForCard).length > 0 && { cardVarMapped: cardVarMappedForCard }),
248
+ };
249
+ });
250
+ };
@@ -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 || '', cardIndex)
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 || '', cardIndex)
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);
@@ -78,8 +78,13 @@ export const rcsVarTestRegex = /^\{\{[^}]+\}\}$/;
78
78
  export const RCS_NUMERIC_VAR_TOKEN_REGEX = /\{\{(\d+)\}\}/g;
79
79
  /** `cardVarMapped` slot keys that are numeric only (legacy ordering). */
80
80
  export const RCS_NUMERIC_VAR_NAME_REGEX = /^\d+$/;
81
- /** Semantic Liquid-style keys on RCS `cardVarMapped` (same class as `{{…}}` inner names in the editor). */
82
- export const RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX = /^[\w.()]+$/;
81
+ /**
82
+ * Semantic Liquid-style keys on RCS `cardVarMapped` (same class as `{{…}}` inner names in the
83
+ * editor). Carousel slots are now keyed by global position (see `computeCarouselVarSlotKeys`), a
84
+ * plain number, so this only needs to match bare semantic names — the optional `cN::` prefix is
85
+ * kept solely so `pickRcsCardVarMappedEntries` doesn't drop legacy scoped keys from older payloads.
86
+ */
87
+ export const RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX = /^(?:c\d+::)?[\w.()]+$/;
83
88
  /** Escape all special RegExp characters in an arbitrary string before using it in `new RegExp(...)`. */
84
89
  export const REGEX_SPECIAL_CHARS_ESCAPE_PATTERN = /[-/\\^$*+?.()|[\]{}]/g;
85
90
  /** Entire string is wrapped in `{{…}}` mustache delimiters (allows any content including `}`). */