@capillarytech/creatives-library 9.0.15-alpha.3 → 9.0.16-beta.0

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 (146) hide show
  1. package/AppRoot.js +80 -0
  2. package/app.js +5 -3
  3. package/constants/unified.js +0 -29
  4. package/entry.js +67 -1
  5. package/global-styles.js +1 -1
  6. package/mfe-exposed-components.js +2 -4
  7. package/package.json +2 -2
  8. package/services/api.js +1 -1
  9. package/services/tests/api.test.js +20 -35
  10. package/styles/containers/layout/_layoutPage.scss +8 -6
  11. package/utils/commonUtils.js +1 -19
  12. package/utils/getDataLayer.js +15 -0
  13. package/utils/gtmTrackers/gtmEvents/creativeDetails.js +2 -1
  14. package/utils/mfeDetect.js +1 -0
  15. package/v2Components/CapActionButton/constants.js +1 -8
  16. package/v2Components/CapActionButton/index.js +144 -202
  17. package/v2Components/CapActionButton/index.scss +14 -232
  18. package/v2Components/CapActionButton/messages.js +3 -19
  19. package/v2Components/CapActionButton/tests/index.test.js +18 -42
  20. package/v2Components/CapImageUpload/index.js +2 -2
  21. package/v2Components/CapTagList/index.js +0 -10
  22. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +48 -70
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  24. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -214
  25. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  26. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +9 -83
  27. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  28. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -60
  29. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  30. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +22 -202
  31. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +80 -381
  32. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +8 -155
  33. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  34. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  35. package/v2Components/CommonTestAndPreview/index.js +222 -761
  36. package/v2Components/CommonTestAndPreview/messages.js +3 -45
  37. package/v2Components/CommonTestAndPreview/sagas.js +6 -25
  38. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -1
  39. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +267 -284
  40. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  41. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  42. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  43. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  44. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  45. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  46. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  47. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -133
  48. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +24 -31
  49. package/v2Components/NavigationBar/index.js +9 -7
  50. package/v2Components/NavigationBar/mfeModuleHeader.config.js +16 -0
  51. package/v2Components/Pagination/_pagination.scss +2 -0
  52. package/v2Components/TemplatePreview/_templatePreview.scss +25 -45
  53. package/v2Components/TemplatePreview/index.js +32 -147
  54. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  55. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  56. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  57. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  58. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  59. package/v2Containers/Cap/constants.js +1 -0
  60. package/v2Containers/Cap/index.js +47 -21
  61. package/v2Containers/Cap/sagas.js +19 -0
  62. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +1 -4
  63. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +0 -4
  64. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  65. package/v2Containers/CreativesContainer/SlideBoxContent.js +7 -37
  66. package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -14
  67. package/v2Containers/CreativesContainer/SlideBoxHeader.js +5 -36
  68. package/v2Containers/CreativesContainer/constants.js +0 -11
  69. package/v2Containers/CreativesContainer/index.js +104 -323
  70. package/v2Containers/CreativesContainer/index.scss +1 -83
  71. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -79
  72. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  73. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  74. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +90 -333
  75. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  76. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  77. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  78. package/v2Containers/Rcs/constants.js +11 -131
  79. package/v2Containers/Rcs/index.js +829 -2682
  80. package/v2Containers/Rcs/index.scss +8 -443
  81. package/v2Containers/Rcs/messages.js +6 -45
  82. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +39409 -74176
  83. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +74 -10
  84. package/v2Containers/Rcs/tests/index.test.js +40 -151
  85. package/v2Containers/Rcs/tests/mockData.js +0 -38
  86. package/v2Containers/Rcs/tests/utils.test.js +30 -643
  87. package/v2Containers/Rcs/utils.js +12 -466
  88. package/v2Containers/Sms/Create/index.js +48 -115
  89. package/v2Containers/SmsTrai/Create/index.js +4 -9
  90. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  91. package/v2Containers/SmsTrai/Edit/index.js +169 -678
  92. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  93. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2854 -5455
  94. package/v2Containers/SmsWrapper/index.js +8 -37
  95. package/v2Containers/TagList/index.js +0 -6
  96. package/v2Containers/Templates/_templates.scss +11 -196
  97. package/v2Containers/Templates/actions.js +0 -11
  98. package/v2Containers/Templates/constants.js +0 -2
  99. package/v2Containers/Templates/index.js +60 -131
  100. package/v2Containers/Templates/sagas.js +13 -57
  101. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1015 -1060
  102. package/v2Containers/Templates/tests/sagas.test.js +16 -199
  103. package/v2Containers/TemplatesV2/TemplatesV2.style.js +10 -75
  104. package/v2Containers/TemplatesV2/index.js +43 -86
  105. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  106. package/v2Containers/Whatsapp/index.js +25 -4
  107. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  108. package/utils/rcsPayloadUtils.js +0 -92
  109. package/utils/templateVarUtils.js +0 -201
  110. package/utils/tests/rcsPayloadUtils.test.js +0 -226
  111. package/utils/tests/templateVarUtils.test.js +0 -204
  112. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  113. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  114. package/v2Components/CommonTestAndPreview/utils.js +0 -84
  115. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
  116. package/v2Components/SmsFallback/constants.js +0 -94
  117. package/v2Components/SmsFallback/index.js +0 -958
  118. package/v2Components/SmsFallback/index.scss +0 -266
  119. package/v2Components/SmsFallback/messages.js +0 -78
  120. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -120
  121. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  122. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  123. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  124. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -208
  125. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
  126. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  127. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  128. package/v2Components/TemplatePreview/constants.js +0 -2
  129. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  130. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  131. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  132. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -17
  133. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
  134. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  135. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  136. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  137. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -227
  138. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  139. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  140. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  141. package/v2Containers/SmsTrai/Edit/index.scss +0 -126
  142. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  143. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  144. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  145. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  146. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -9,72 +9,29 @@
9
9
 
10
10
  import get from 'lodash/get';
11
11
  import { CHANNELS } from '../../constants';
12
- import { RCS_CONTACT_INFO_GSM_TYPE_KEYS } from '../constants';
13
- import { typeMatches, unwrapEntity } from '../../utils';
14
12
 
15
- /**
16
- * Include row unless explicitly invalid — many APIs omit `valid`, which would
17
- * otherwise filter out every sender (RCS/SMS lists empty in UI).
18
- */
19
13
  const getSenderOptions = (contactInfo, key) =>
20
14
  (contactInfo || [])
21
- .filter((contactInfoItem) => typeMatches(contactInfoItem.type, key) && contactInfoItem.valid !== false)
15
+ .filter((contactInfoItem) => contactInfoItem.type === key && contactInfoItem.valid)
22
16
  .sort((contact) => (contact.default ? -1 : 0));
23
17
 
24
- /** RCS APIs may label senders under several `contactInfo.type` keys — see `RCS_CONTACT_INFO_GSM_TYPE_KEYS`. */
25
- const getRcsLikeGsmSenders = (contactInfo) => {
26
- const seen = new Set();
27
- const mergedSenderRows = [];
28
- RCS_CONTACT_INFO_GSM_TYPE_KEYS.forEach((key) => {
29
- getSenderOptions(contactInfo, key).forEach((row) => {
30
- const senderValue = row?.value;
31
- // Skip empty/whitespace sender values to avoid polluting the RCS sender dropdown.
32
- if (senderValue == null || String(senderValue).trim() === '' || seen.has(senderValue)) return;
33
- seen.add(senderValue);
34
- mergedSenderRows.push(row);
35
- });
36
- });
37
- return mergedSenderRows.sort((a, b) => (a.default ? -1 : 0) - (b.default ? -1 : 0));
38
- };
39
-
40
18
  /**
41
19
  * Parse raw API response for one channel into { domains: [...] }
42
- * @param {string} channel - SMS | EMAIL | WHATSAPP | RCS
20
+ * @param {string} channel - SMS | EMAIL | WHATSAPP
43
21
  * @param {Object} response - API response (e.g. { entity: { SMS: [...] } } or { entity: [...] })
44
22
  * @returns {{ domains: Array }} - domains array compatible with campaigns DeliverySettingsV2
45
23
  */
46
24
  export function parseSenderDetailsResponse(channel, response) {
47
- const entity = unwrapEntity(response);
48
- if (entity == null || entity === '') {
25
+ const entity = get(response, 'entity', response);
26
+ if (!entity) {
49
27
  return { domains: [] };
50
28
  }
51
29
 
52
- // Normalize once: entity keys may be any casing (SMS/sms/rcs) but downstream branches use CHANNELS.* (uppercase).
53
- const normalizedChannel =
54
- channel == null || String(channel).trim() === ''
55
- ? ''
56
- : String(channel).trim().toUpperCase();
57
-
58
- // Single-channel API: entity may be { [channel]: [...] } or direct array.
59
- // Some responses use different casing (e.g. rcs vs RCS) or a single domain object instead of an array.
60
- let channelSenderDetails =
61
- get(entity, channel, null)
62
- ?? (normalizedChannel ? get(entity, normalizedChannel, null) : null)
63
- ?? (normalizedChannel ? get(entity, normalizedChannel.toLowerCase(), null) : null);
64
-
30
+ // Single-channel API: entity may be { [channel]: [...] } or direct array
31
+ let channelSenderDetails = get(entity, channel, null);
65
32
  if (channelSenderDetails == null && Array.isArray(entity)) {
66
33
  channelSenderDetails = entity;
67
34
  }
68
-
69
- if (channelSenderDetails != null && !Array.isArray(channelSenderDetails)) {
70
- const single = channelSenderDetails;
71
- if (single && typeof single === 'object' && single.domainProperties) {
72
- channelSenderDetails = [single];
73
- } else {
74
- return { domains: [] };
75
- }
76
- }
77
-
78
35
  if (!Array.isArray(channelSenderDetails)) {
79
36
  return { domains: [] };
80
37
  }
@@ -85,7 +42,6 @@ export function parseSenderDetailsResponse(channel, response) {
85
42
  const { id: dgmId, priority, domainProperties = {} } = element;
86
43
  const {
87
44
  domainName,
88
- hostName,
89
45
  id = -1,
90
46
  contactInfo,
91
47
  connectionProperties: {
@@ -95,25 +51,21 @@ export function parseSenderDetailsResponse(channel, response) {
95
51
  } = {},
96
52
  } = domainProperties;
97
53
 
98
- const resolvedDomainName = domainName || hostName;
99
-
100
54
  const domain = {
101
55
  dgmId,
102
- domainName: resolvedDomainName,
56
+ domainName,
103
57
  domainId: id,
104
58
  priority: priority != null ? priority : 0,
105
59
  };
106
60
 
107
- if ([CHANNELS.SMS, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(normalizedChannel)) {
61
+ if ([CHANNELS.SMS, CHANNELS.WHATSAPP].includes(channel)) {
108
62
  domain.cdmaSenders = getSenderOptions(contactInfo, 'cdma_sender_id');
109
- domain.gsmSenders = normalizedChannel === CHANNELS.RCS
110
- ? getRcsLikeGsmSenders(contactInfo)
111
- : getSenderOptions(contactInfo, 'gsm_sender_id');
63
+ domain.gsmSenders = getSenderOptions(contactInfo, 'gsm_sender_id');
112
64
  domain.sourceAccountIdentifier =
113
65
  sourceAccountIdentifier || wabaId || userid || '';
114
66
  }
115
67
 
116
- if (normalizedChannel === CHANNELS.EMAIL) {
68
+ if (channel === CHANNELS.EMAIL) {
117
69
  domain.emailSenders = getSenderOptions(contactInfo, 'sender_id');
118
70
  domain.emailRepliers = getSenderOptions(contactInfo, 'reply_to_id');
119
71
  }
@@ -124,8 +76,7 @@ export function parseSenderDetailsResponse(channel, response) {
124
76
  // Sort by priority and dedupe by domainName (match campaigns behaviour)
125
77
  domains.sort((a, b) => (a.priority || 0) - (b.priority || 0));
126
78
  const deduped = domains.reduce((acc, domain) => {
127
- const key = domain.domainName ?? domain.domainId;
128
- if (!acc.find((existing) => (existing.domainName ?? existing.domainId) === key)) {
79
+ if (!acc.find((d) => d.domainName === domain.domainName)) {
129
80
  acc.push(domain);
130
81
  }
131
82
  return acc;
@@ -6,13 +6,12 @@ import CapButton from '@capillarytech/cap-ui-library/CapButton';
6
6
  import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
7
7
  import CapStepsAccordian from '@capillarytech/cap-ui-library/CapStepsAccordian';
8
8
  import CapTreeSelect from '@capillarytech/cap-ui-library/CapTreeSelect';
9
- import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
10
9
  import isEmpty from 'lodash/isEmpty';
11
10
  import messages from './messages';
12
11
  import DeliverySettings from './DeliverySettings';
13
12
  import { CHANNELS } from './constants';
14
13
 
15
- const CHANNELS_WITH_DELIVERY_SETTINGS = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
14
+ const CHANNELS_WITH_DELIVERY_SETTINGS = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
16
15
 
17
16
  const SendTestMessage = ({
18
17
  isFetchingTestCustomers,
@@ -29,13 +28,12 @@ const SendTestMessage = ({
29
28
  searchValue,
30
29
  setSearchValue,
31
30
  deliverySettings,
32
- senderDetailsByChannel = {},
31
+ senderDetailsOptions,
33
32
  wecrmAccounts,
34
33
  onSaveDeliverySettings,
35
34
  isLoadingSenderDetails,
36
35
  smsTraiDltEnabled,
37
36
  registeredSenderIds,
38
- isChannelSmsFallbackPreviewEnabled,
39
37
  }) => {
40
38
  const addCustomerContent = renderAddTestCustomerButton ? renderAddTestCustomerButton() : null;
41
39
  return (
@@ -79,14 +77,13 @@ const SendTestMessage = ({
79
77
  <DeliverySettings
80
78
  channel={channel}
81
79
  deliverySettings={deliverySettings || {}}
82
- senderDetailsByChannel={senderDetailsByChannel}
80
+ senderDetailsOptions={senderDetailsOptions}
83
81
  wecrmAccounts={wecrmAccounts || []}
84
82
  onSaveDeliverySettings={onSaveDeliverySettings}
85
83
  isLoadingSenderDetails={isLoadingSenderDetails}
86
84
  formatMessage={formatMessage}
87
85
  smsTraiDltEnabled={smsTraiDltEnabled}
88
86
  registeredSenderIds={registeredSenderIds}
89
- isChannelSmsFallbackPreviewEnabled={isChannelSmsFallbackPreviewEnabled}
90
87
  whatsappAccountFromForm={
91
88
  channel === CHANNELS.WHATSAPP && formData?.accountName
92
89
  ? { accountName: formData.accountName }
@@ -124,13 +121,12 @@ SendTestMessage.propTypes = {
124
121
  searchValue: PropTypes.string,
125
122
  setSearchValue: PropTypes.func,
126
123
  deliverySettings: PropTypes.object,
127
- senderDetailsByChannel: PropTypes.object,
124
+ senderDetailsOptions: PropTypes.array,
128
125
  wecrmAccounts: PropTypes.array,
129
126
  onSaveDeliverySettings: PropTypes.func,
130
127
  isLoadingSenderDetails: PropTypes.bool,
131
128
  smsTraiDltEnabled: PropTypes.bool,
132
129
  registeredSenderIds: PropTypes.array,
133
- isChannelSmsFallbackPreviewEnabled: PropTypes.bool,
134
130
  };
135
131
 
136
132
  SendTestMessage.defaultProps = {
@@ -140,13 +136,12 @@ SendTestMessage.defaultProps = {
140
136
  searchValue: '',
141
137
  setSearchValue: () => {}, // no-op when not provided; required by TreeSelect when showSearch is true
142
138
  deliverySettings: {},
143
- senderDetailsByChannel: {},
139
+ senderDetailsOptions: [],
144
140
  wecrmAccounts: [],
145
141
  onSaveDeliverySettings: undefined,
146
142
  isLoadingSenderDetails: false,
147
143
  smsTraiDltEnabled: false,
148
144
  registeredSenderIds: [],
149
- isChannelSmsFallbackPreviewEnabled: false,
150
145
  };
151
146
 
152
147
  export default SendTestMessage;
@@ -25,7 +25,6 @@ import {
25
25
  TIME_FORMAT_MINUTE_PADDING_THRESHOLD,
26
26
  TIME_FORMAT_AM,
27
27
  TIME_FORMAT_PM,
28
- MEDIA_TYPE_VIDEO,
29
28
  } from '../constants';
30
29
  import messages from '../messages';
31
30
  import { RCS_BUTTON_TYPES } from '../../../v2Containers/Rcs/constants';
@@ -51,56 +50,39 @@ const RcsPreviewContent = ({
51
50
  const deviceName = device === ANDROID ? ANDROID_DEVICE_NAME : IOS_DEVICE_NAME;
52
51
 
53
52
  // Render RCS suggestions (Quick Reply, CTA, Phone Number)
54
- // options.isCarousel: stacked bars below card body with gap (Figma), no dividers between CTAs
55
- const renderRcsSuggestionsPreview = (suggestionsArg, options = {}) => {
56
- const { isCarousel = false } = options;
53
+ const renderRcsSuggestionsPreview = () => {
57
54
  const renderArray = [];
58
- const rawSuggestions = Array.isArray(suggestionsArg)
59
- ? suggestionsArg
60
- : (content?.suggestions || []);
61
- // Skip unsaved/empty draft suggestions (e.g. the pre-seeded default phone-number slot
62
- // on carousel card 1, or a suggestion mid-edit via the pencil icon — both explicitly set
63
- // `isSaved: false`) — but suggestions hydrated from an already-persisted template never
64
- // carry `isSaved` at all (it's a client-only "saved this session" flag), so `undefined`
65
- // must still render; only an explicit `false` means "hide this".
66
- const suggestions = rawSuggestions.filter(
67
- (suggestion) => suggestion?.isSaved !== false && String(suggestion?.text || '').trim(),
68
- );
55
+ const { suggestions = [] } = content || {};
69
56
 
70
57
  if (suggestions.length === 0) {
71
58
  return null;
72
59
  }
73
60
 
74
- const ctaClass = isCarousel ? 'rcs-cta-preview rcs-cta-preview--carousel-bar' : 'rcs-cta-preview';
75
-
76
61
  suggestions.forEach((suggestion) => {
77
62
  const { type, text } = suggestion || {};
78
63
  const suggestionKey = `${type}-${text || Math.random()}`;
79
64
 
80
- if (renderArray.length > 0 && !isCarousel) {
65
+ if (renderArray.length > 0) {
81
66
  renderArray.push(<CapDivider key={`divider-${suggestionKey}`} className="whatsapp-divider" />);
82
67
  }
83
68
 
84
69
  if (type === RCS_BUTTON_TYPES.QUICK_REPLY) {
85
70
  renderArray.push(
86
- <CapLabel key={suggestionKey} type="label21" className={ctaClass}>
71
+ <CapLabel key={suggestionKey} type="label21" className="rcs-cta-preview">
87
72
  <CapIcon type="small-link" size="xs" />
88
73
  {text}
89
74
  </CapLabel>
90
75
  );
91
76
  } else if (type === RCS_BUTTON_TYPES.CTA) {
92
77
  renderArray.push(
93
- <CapLabel key={suggestionKey} type="label21" className={ctaClass}>
94
- {/* "launch" icon's SVG has hardcoded <defs>/<mask> ids, which collide
95
- and render as a solid square when 2+ CTA buttons appear together —
96
- "open-in-new" is a single flat path with no ids, so it can't collide. */}
97
- <CapIcon type="open-in-new" size="xs" />
78
+ <CapLabel key={suggestionKey} type="label21" className="rcs-cta-preview">
79
+ <CapIcon type="launch" size="xs" />
98
80
  {text}
99
81
  </CapLabel>
100
82
  );
101
83
  } else if (type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
102
84
  renderArray.push(
103
- <CapLabel key={suggestionKey} type="label21" className={ctaClass}>
85
+ <CapLabel key={suggestionKey} type="label21" className="rcs-cta-preview">
104
86
  <CapIcon type="call" size="xs" />
105
87
  {text}
106
88
  </CapLabel>
@@ -111,109 +93,6 @@ const RcsPreviewContent = ({
111
93
  return renderArray?.length > 0 ? renderArray : null;
112
94
  };
113
95
 
114
- // Carousel media box: aspect matches RCS editor card size (SHORT/MEDIUM × SMALL/MEDIUM).
115
- const getCarouselMediaAspectStyle = (isVideo) => {
116
- const d = content?.carouselPreviewDimensions;
117
- if (!d) return undefined;
118
- if (isVideo) {
119
- const { videoThumbWidth, videoThumbHeight } = d;
120
- if (!videoThumbWidth || !videoThumbHeight) return undefined;
121
- return { aspectRatio: `${videoThumbWidth} / ${videoThumbHeight}` };
122
- }
123
- const { imageWidth, imageHeight } = d;
124
- if (!imageWidth || !imageHeight) return undefined;
125
- return { aspectRatio: `${imageWidth} / ${imageHeight}` };
126
- };
127
-
128
- // Carousel preview (WhatsApp-style static horizontal scroll).
129
- // Caller passes a non-empty array only (see hasCarousel); avoids duplicate guards that Sonar
130
- // flagged as partially covered because inner checks were unreachable when hasCarousel was true.
131
- const renderCarouselPreviewContent = (carouselCards) => (
132
- <CapRow className="msg-container-carousel">
133
- <CapRow className="scroll-container" type="flex" noWrap>
134
- {carouselCards.map((card, idx) => {
135
- const key = `rcs-carousel-${idx}-${card?.bodyText || card?.imageSrc || card?.videoPreviewImg || ''}`;
136
- const isVideo =
137
- (card?.mediaType || '').toLowerCase() === String(MEDIA_TYPE_VIDEO).toLowerCase();
138
- const cardSuggestions = Array.isArray(card?.suggestions) ? card.suggestions : [];
139
- const suggestionsNode = cardSuggestions.length > 0
140
- ? renderRcsSuggestionsPreview(cardSuggestions, { isCarousel: true })
141
- : null;
142
- const titleTrimmed = String(card?.title ?? '').trim();
143
- const bodyTrimmed = String(card?.bodyText ?? '').trim();
144
- const titleShown = titleTrimmed || formatMessage(messages.rcsCarouselPreviewTitlePlaceholder);
145
- const bodyShown = bodyTrimmed || formatMessage(messages.rcsCarouselPreviewBodyPlaceholder);
146
- return (
147
- <CapRow key={key} className="rcs-carousel-item">
148
- <CapRow className="message-pop align-left message-pop-carousel">
149
- <CapRow className="whatsapp-content">
150
- {!isVideo && (
151
- <CapRow
152
- className="whatsapp-image rcs-carousel-media-wrap"
153
- style={getCarouselMediaAspectStyle(false)}
154
- >
155
- <CapImage
156
- src={card?.imageSrc ? card.imageSrc : rcsImageEmptyPreview}
157
- className="rcs-carousel-img"
158
- alt={formatMessage(messages.previewGenerated)}
159
- />
160
- </CapRow>
161
- )}
162
- {isVideo && (
163
- <CapTooltip
164
- title={formatMessage(messages.videoPreviewTooltip)}
165
- wrapperClassName="rcs-carousel-video-tooltip-wrapper"
166
- triggerClassName="rcs-carousel-video-tooltip-trigger"
167
- >
168
- <CapRow className="video-preview">
169
- <CapRow
170
- className="whatsapp-image rcs-carousel-media-wrap"
171
- style={getCarouselMediaAspectStyle(true)}
172
- >
173
- <CapImage
174
- src={card?.videoPreviewImg ? card.videoPreviewImg : rcsVideoEmptyPreview}
175
- className="rcs-carousel-img"
176
- alt={formatMessage(messages.previewGenerated)}
177
- />
178
- </CapRow>
179
- <CapRow className="icon-position">
180
- <CapImage className="video-icon" src={videoPlay} alt="Play" />
181
- </CapRow>
182
- </CapRow>
183
- </CapTooltip>
184
- )}
185
-
186
- <CapRow className="carousel-content">
187
- <CapLabel
188
- type={card.titleLabelType || 'label1'}
189
- className={`carousel-title${titleTrimmed ? '' : ' rcs-carousel-field-placeholder'}`}
190
- >
191
- {titleShown}
192
- </CapLabel>
193
- <CapLabel
194
- type={card.bodyLabelType || 'label2'}
195
- className={`carousel-message${bodyTrimmed ? '' : ' rcs-carousel-field-placeholder'}`}
196
- >
197
- {bodyShown}
198
- </CapLabel>
199
- </CapRow>
200
- </CapRow>
201
- </CapRow>
202
-
203
- {/* Deliberately OUTSIDE .message-pop-carousel — the CTA bar is its own
204
- floating element below the card, not clipped/rounded as part of it. */}
205
- {!!suggestionsNode && (
206
- <CapRow className="rcs-carousel-cta-stack" gutter={0}>
207
- {suggestionsNode}
208
- </CapRow>
209
- )}
210
- </CapRow>
211
- );
212
- })}
213
- </CapRow>
214
- </CapRow>
215
- );
216
-
217
96
  // Render text preview content
218
97
  const renderTextPreviewContent = () => {
219
98
  const {
@@ -347,39 +226,11 @@ const RcsPreviewContent = ({
347
226
  const timestamp = `${displayHours}:${displayMinutes} ${ampm}`;
348
227
 
349
228
  // Render normal RCS preview (same structure as SMS up to sms-message-container)
350
- const carouselCardsNormalized = Array.isArray(content?.carouselData) ? content?.carouselData : [];
351
- const hasCarousel = carouselCardsNormalized?.length > 0;
352
229
  const hasMedia = !!(content?.rcsImageSrc || content?.rcsVideoSrc);
353
- let contentToRender;
354
- if (hasCarousel) {
355
- contentToRender = renderCarouselPreviewContent(carouselCardsNormalized);
356
- } else if (hasMedia) {
357
- contentToRender = renderMediaPreviewContent();
358
- } else {
359
- contentToRender = renderTextPreviewContent();
360
- }
361
-
362
- const carouselPanelStyle = hasCarousel ? {
363
- position: 'absolute',
364
- left: '50%',
365
- transform: 'translateX(-50%)',
366
- width: '100%',
367
- maxWidth: '26.786rem',
368
- boxSizing: 'border-box',
369
- top: device === ANDROID ? '3.1rem' : '4.143rem',
370
- height: device === ANDROID ? 'calc(100% - 3.8rem)' : device === IOS ? 'calc(100% - 5rem)' : 'calc(100% - 4.5rem)',
371
- display: 'flex',
372
- flexDirection: 'column',
373
- paddingTop: '5.857rem',
374
- paddingBottom: '1.142rem',
375
- } : undefined;
230
+ const contentToRender = hasMedia ? renderMediaPreviewContent() : renderTextPreviewContent();
376
231
 
377
232
  return (
378
- <CapRow
379
- type='flex' align='middle' justify='center'
380
- className={`sms-device-container rcs-device-container ${hasCarousel ? 'rcs-device-container-carousel' : ''}`}
381
- style={{ position: 'relative' }}
382
- >
233
+ <CapRow type='flex' align='middle' justify='center' className="sms-device-container">
383
234
  {/* Device Background Image */}
384
235
  <CapImage
385
236
  className="sms-device-image"
@@ -387,10 +238,8 @@ const RcsPreviewContent = ({
387
238
  alt={`${deviceName} Device`}
388
239
  />
389
240
 
390
- {/* Content Overlay - nav bar + date separator always; message content only for non-carousel */}
391
- <CapRow
392
- className={`sms-content-overlay sms-content-overlay-${device} sms-preview sms-${device} rcs-content-overlay`}
393
- >
241
+ {/* Content Overlay */}
242
+ <CapRow className={`sms-content-overlay sms-content-overlay-${device} sms-preview sms-${device}`}>
394
243
  {/* Navigation Bar - Same as SMS */}
395
244
  <CapRow type='flex' className={`sms-navigation-bar ${!showHeader ? 'sms-navigation-bar-no-header' : ''}`}>
396
245
  <CapIcon type="chevron-left" className="sms-back-arrow" />
@@ -405,41 +254,24 @@ const RcsPreviewContent = ({
405
254
  <FormattedMessage {...messages.timeAgo} defaultMessage="Today" />
406
255
  </CapRow>
407
256
 
408
- {/* Message Container - non-carousel only; carousel is rendered outside this overflow-x:hidden ancestor */}
409
- {!hasCarousel && (
410
- <CapRow className="sms-message-container">
411
- <CapRow useLegacy className="sms-message-bubble">
412
- <CapRow useLegacy className="sms-message-text">
413
- <CapRow useLegacy className="message-pop sms">
414
- {contentToRender}
415
- </CapRow>
257
+ {/* Message Container - Same structure as SMS, only content inside is RCS-specific */}
258
+ <CapRow className="sms-message-container">
259
+ <CapRow useLegacy className="sms-message-bubble">
260
+ <CapRow useLegacy className="sms-message-text">
261
+ {/* RCS-specific content rendering */}
262
+ <CapRow useLegacy className="message-pop sms">
263
+ {contentToRender}
416
264
  </CapRow>
417
265
  </CapRow>
418
- <CapRow useLegacy className="sms-timestamp">
419
- {timestamp}
420
- </CapRow>
421
266
  </CapRow>
422
- )}
423
- </CapRow>
424
-
425
- {/* Carousel panel: sibling of overlay, no overflow-x:hidden ancestor → scroll-container
426
- can receive horizontal scroll events. Positioned absolutely over the device image. */}
427
- {hasCarousel && (
428
- <CapRow
429
- className={`rcs-carousel-panel rcs-carousel-panel-${device}`}
430
- // style={carouselPanelStyle}
431
- >
432
- {renderCarouselPreviewContent(carouselCardsNormalized)}
433
- <CapRow useLegacy className="sms-timestamp rcs-carousel-timestamp">
267
+ <CapRow useLegacy className="sms-timestamp">
434
268
  {timestamp}
435
269
  </CapRow>
436
270
  </CapRow>
437
- )}
271
+ </CapRow>
438
272
  </CapRow>
439
- )
440
- }
441
-
442
-
273
+ );
274
+ };
443
275
 
444
276
  RcsPreviewContent.propTypes = {
445
277
  content: PropTypes.shape({
@@ -450,18 +282,6 @@ RcsPreviewContent.propTypes = {
450
282
  rcsImageSrc: PropTypes.string,
451
283
  rcsVideoSrc: PropTypes.string,
452
284
  rcsThumbnailSrc: PropTypes.string,
453
- carouselData: PropTypes.arrayOf(
454
- PropTypes.shape({
455
- titleLabelType: PropTypes.string,
456
- bodyLabelType: PropTypes.string,
457
- })
458
- ),
459
- carouselPreviewDimensions: PropTypes.shape({
460
- imageWidth: PropTypes.number,
461
- imageHeight: PropTypes.number,
462
- videoThumbWidth: PropTypes.number,
463
- videoThumbHeight: PropTypes.number,
464
- }),
465
285
  suggestions: PropTypes.arrayOf(
466
286
  PropTypes.shape({
467
287
  type: PropTypes.string,