@capillarytech/creatives-library 9.0.28 → 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.
Files changed (149) hide show
  1. package/constants/unified.js +29 -0
  2. package/global-styles.js +1 -1
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +35 -20
  5. package/utils/commonUtils.js +22 -1
  6. package/utils/rcsPayloadUtils.js +99 -0
  7. package/utils/templateVarUtils.js +198 -0
  8. package/utils/tests/rcsPayloadUtils.test.js +243 -0
  9. package/utils/tests/templateVarUtils.test.js +204 -0
  10. package/v2Components/CapActionButton/constants.js +20 -1
  11. package/v2Components/CapActionButton/index.js +193 -144
  12. package/v2Components/CapActionButton/index.scss +232 -14
  13. package/v2Components/CapActionButton/messages.js +19 -3
  14. package/v2Components/CapActionButton/tests/index.test.js +42 -18
  15. package/v2Components/CapImageUpload/index.js +2 -2
  16. package/v2Components/CapTagList/index.js +9 -0
  17. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +58 -11
  24. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +186 -22
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +381 -80
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
  28. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  29. package/v2Components/CommonTestAndPreview/constants.js +35 -2
  30. package/v2Components/CommonTestAndPreview/index.js +770 -231
  31. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  32. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  33. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  34. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
  35. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +412 -257
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +337 -63
  37. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  38. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  39. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  40. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +108 -15
  41. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  42. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  43. package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  46. package/v2Components/CommonTestAndPreview/tests/utils.test.js +151 -0
  47. package/v2Components/CommonTestAndPreview/utils.js +84 -0
  48. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  49. package/v2Components/SmsFallback/constants.js +94 -0
  50. package/v2Components/SmsFallback/index.js +958 -0
  51. package/v2Components/SmsFallback/index.scss +266 -0
  52. package/v2Components/SmsFallback/messages.js +78 -0
  53. package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
  54. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  55. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
  58. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  59. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +471 -0
  60. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  61. package/v2Components/TemplatePreview/_templatePreview.scss +45 -25
  62. package/v2Components/TemplatePreview/constants.js +2 -0
  63. package/v2Components/TemplatePreview/index.js +147 -32
  64. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  65. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  66. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  67. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  68. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  69. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  70. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  71. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  72. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
  73. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
  74. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  75. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +27 -0
  76. package/v2Containers/CreativesContainer/SlideBoxContent.js +37 -4
  77. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  78. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  79. package/v2Containers/CreativesContainer/constants.js +11 -0
  80. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +77 -0
  81. package/v2Containers/CreativesContainer/index.js +328 -106
  82. package/v2Containers/CreativesContainer/index.scss +102 -1
  83. package/v2Containers/CreativesContainer/tests/CreativesSlideBoxWrapper.test.js +58 -0
  84. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  85. package/v2Containers/CreativesContainer/tests/SlideBoxContent.smsDltPreview.test.js +73 -0
  86. package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +37 -0
  87. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +103 -34
  88. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +193 -15
  89. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +88 -0
  90. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +549 -57
  91. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  92. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  93. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  94. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  95. package/v2Containers/Email/index.js +3 -63
  96. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +4 -9
  97. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  98. package/v2Containers/Rcs/carouselUtils.js +256 -0
  99. package/v2Containers/Rcs/components/CarouselCard.js +315 -0
  100. package/v2Containers/Rcs/components/CarouselCardButtons.js +93 -0
  101. package/v2Containers/Rcs/components/CarouselCardMedia.js +136 -0
  102. package/v2Containers/Rcs/components/CarouselCharacterCount.js +31 -0
  103. package/v2Containers/Rcs/components/CarouselDimensionSelection.js +80 -0
  104. package/v2Containers/Rcs/constants.js +137 -16
  105. package/v2Containers/Rcs/index.js +1912 -958
  106. package/v2Containers/Rcs/index.scss +443 -8
  107. package/v2Containers/Rcs/messages.js +45 -22
  108. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +522 -0
  109. package/v2Containers/Rcs/tests/CarouselCard.test.js +465 -0
  110. package/v2Containers/Rcs/tests/CarouselCardButtons.test.js +211 -0
  111. package/v2Containers/Rcs/tests/CarouselCardMedia.test.js +160 -0
  112. package/v2Containers/Rcs/tests/CarouselCharacterCount.test.js +50 -0
  113. package/v2Containers/Rcs/tests/CarouselDimensionSelection.test.js +119 -0
  114. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74176 -39409
  115. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
  116. package/v2Containers/Rcs/tests/carouselUtils.test.js +916 -0
  117. package/v2Containers/Rcs/tests/index.test.js +400 -40
  118. package/v2Containers/Rcs/tests/mockData.js +38 -0
  119. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +873 -0
  120. package/v2Containers/Rcs/tests/utils.test.js +762 -30
  121. package/v2Containers/Rcs/utils.js +571 -12
  122. package/v2Containers/Sms/Create/index.js +115 -48
  123. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  124. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  125. package/v2Containers/SmsTrai/Create/index.js +9 -4
  126. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  127. package/v2Containers/SmsTrai/Edit/index.js +678 -169
  128. package/v2Containers/SmsTrai/Edit/index.scss +126 -0
  129. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  130. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
  131. package/v2Containers/SmsWrapper/index.js +37 -8
  132. package/v2Containers/TagList/index.js +5 -0
  133. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  134. package/v2Containers/Templates/_templates.scss +196 -12
  135. package/v2Containers/Templates/actions.js +11 -0
  136. package/v2Containers/Templates/constants.js +2 -0
  137. package/v2Containers/Templates/index.js +131 -59
  138. package/v2Containers/Templates/sagas.js +57 -13
  139. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +129 -0
  140. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
  141. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  142. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  143. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  144. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  145. package/v2Containers/TemplatesV2/index.js +88 -25
  146. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  147. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  148. package/v2Containers/Whatsapp/index.js +3 -20
  149. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -0,0 +1,315 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import CapCard from '@capillarytech/cap-ui-library/CapCard';
4
+ import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
5
+ import CapButton from '@capillarytech/cap-ui-library/CapButton';
6
+ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
7
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
8
+ import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
9
+ import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
10
+ import CapRadioGroup from '@capillarytech/cap-ui-library/CapRadioGroup';
11
+ import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
12
+ import CapInput from '@capillarytech/cap-ui-library/CapInput';
13
+ import CapError from '@capillarytech/cap-ui-library/CapError';
14
+ import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
15
+ import TagList from '../../TagList';
16
+ import globalMessages from '../../Cap/messages';
17
+ import messages from '../messages';
18
+ import { RCS_TAG_AREA_FIELD_TITLE, RCS_TAG_AREA_FIELD_DESC } from '../constants';
19
+ import CarouselCardMedia from './CarouselCardMedia';
20
+ import CarouselCardButtons from './CarouselCardButtons';
21
+ import CarouselCharacterCount from './CarouselCharacterCount';
22
+
23
+ const { TextArea } = CapInput;
24
+
25
+ /** One carousel card's editor: media, title, description, buttons — used as a tab pane in the carousel editor. */
26
+ const CarouselCard = ({
27
+ cardIndex,
28
+ card,
29
+ carouselLength,
30
+ isEditFlow,
31
+ isFullMode,
32
+ mediaRadioOptions,
33
+ cardError,
34
+ location,
35
+ tagsForPicker,
36
+ injectedTags,
37
+ selectedOfferDetails,
38
+ formatMessage,
39
+ titleMaxLength,
40
+ descriptionMaxLength,
41
+ titleCharCount,
42
+ descriptionCharCount,
43
+ dimKey,
44
+ rcsData,
45
+ uploadRcsImage,
46
+ uploadRcsVideo,
47
+ hostName,
48
+ onDelete,
49
+ onMediaTypeChange,
50
+ onTitleChange,
51
+ onDescriptionChange,
52
+ onTitleAddVar,
53
+ onDescriptionAddVar,
54
+ onTitleTagSelect,
55
+ onDescTagSelect,
56
+ onContextChange,
57
+ renderEditMessage,
58
+ onFieldChange,
59
+ onSuggestionsChange,
60
+ }) => (
61
+ <CapCard
62
+ title={`Card ${cardIndex + 1}`}
63
+ extra={
64
+ !isEditFlow &&
65
+ (carouselLength === 1 ? (
66
+ <CapTooltip title={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}>
67
+ <span className="button-disabled-tooltip-wrapper rcs-carousel-delete-tooltip-wrap">
68
+ <CapButton
69
+ className="rcs-carousel-card-delete"
70
+ type="flat"
71
+ onClick={onDelete}
72
+ disabled
73
+ aria-label={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}
74
+ >
75
+ <CapIcon type="delete" />
76
+ </CapButton>
77
+ </span>
78
+ </CapTooltip>
79
+ ) : (
80
+ <CapButton
81
+ className="rcs-carousel-card-delete"
82
+ type="flat"
83
+ onClick={onDelete}
84
+ aria-label={formatMessage(globalMessages.delete)}
85
+ >
86
+ <CapIcon type="delete" />
87
+ </CapButton>
88
+ ))
89
+ }
90
+ className="rcs-carousel-card"
91
+ >
92
+ {/* Media selection should be at top of card */}
93
+ {isEditFlow ? (
94
+ <div className="rcs-creative-name-readonly">
95
+ <CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
96
+ <CapHeading type="h5" className="rcs-creative-name-value">
97
+ {mediaRadioOptions.find((option) => option.value === card.mediaType)?.label || '-'}
98
+ </CapHeading>
99
+ </div>
100
+ ) : (
101
+ <CapRow className="rcs-carousel-media-selection">
102
+ <CapColumn className="rcs-carousel-media-selection-heading">
103
+ <CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
104
+ </CapColumn>
105
+ <CapColumn>
106
+ <CapRadioGroup
107
+ id={`rcs-carousel-media-radio-${cardIndex}`}
108
+ options={mediaRadioOptions}
109
+ value={card.mediaType}
110
+ onChange={({ target: { value } }) => onMediaTypeChange(value)}
111
+ disabled={!isFullMode}
112
+ className="rcs-radio"
113
+ />
114
+ </CapColumn>
115
+ </CapRow>
116
+ )}
117
+
118
+ <CapRow className={`rcs-carousel-media-upload ${isEditFlow ? 'rcs-carousel-media-upload--readonly-above' : ''}`}>
119
+ <CarouselCardMedia
120
+ cardIndex={cardIndex}
121
+ card={card}
122
+ dimKey={dimKey}
123
+ isFullMode={isFullMode}
124
+ isEditFlow={isEditFlow}
125
+ rcsData={rcsData}
126
+ uploadRcsImage={uploadRcsImage}
127
+ uploadRcsVideo={uploadRcsVideo}
128
+ formatMessage={formatMessage}
129
+ onFieldChange={onFieldChange}
130
+ />
131
+ </CapRow>
132
+
133
+ {/* Title after media */}
134
+ <CapRow className="rcs-carousel-card-row">
135
+ <CapHeader
136
+ className="rcs-template-title-label"
137
+ title={<CapHeading type="h4">Card title</CapHeading>}
138
+ suffix={
139
+ (isEditFlow || !isFullMode) ? (
140
+ <TagList
141
+ label={formatMessage(globalMessages.addLabels)}
142
+ onTagSelect={onTitleTagSelect}
143
+ location={location}
144
+ tags={tagsForPicker}
145
+ onContextChange={onContextChange}
146
+ injectedTags={injectedTags || {}}
147
+ selectedOfferDetails={selectedOfferDetails}
148
+ />
149
+ ) : (
150
+ <CapButton
151
+ data-testid={`rcs-carousel-title-add-var-${cardIndex}`}
152
+ type="flat"
153
+ isAddBtn
154
+ onClick={onTitleAddVar}
155
+ disabled={!!cardError?.title}
156
+ >
157
+ {formatMessage(messages.addVar)}
158
+ </CapButton>
159
+ )
160
+ }
161
+ />
162
+ <CapRow className="rcs_text_area_wrapper">
163
+ {(isEditFlow || !isFullMode) ? (
164
+ renderEditMessage(card.title || '', cardIndex, RCS_TAG_AREA_FIELD_TITLE)
165
+ ) : (
166
+ <CapInput
167
+ value={card.title || ''}
168
+ placeholder={formatMessage(messages.templateTitlePlaceholder)}
169
+ onChange={onTitleChange}
170
+ disabled={isEditFlow || !isFullMode}
171
+ errorMessage={cardError?.title}
172
+ />
173
+ )}
174
+ </CapRow>
175
+ </CapRow>
176
+ {!isEditFlow && (
177
+ <CapRow className="rcs-carousel-character-count-row">
178
+ <CarouselCharacterCount
179
+ formatMessage={formatMessage}
180
+ currentLength={titleCharCount}
181
+ maxLength={titleMaxLength}
182
+ />
183
+ </CapRow>
184
+ )}
185
+
186
+ {/* Description after title */}
187
+ <CapRow className="rcs-carousel-card-row">
188
+ <CapHeader
189
+ title={<CapHeading type="h4">Card body text</CapHeading>}
190
+ suffix={
191
+ (isEditFlow || !isFullMode) ? (
192
+ <TagList
193
+ label={formatMessage(globalMessages.addLabels)}
194
+ onTagSelect={onDescTagSelect}
195
+ location={location}
196
+ tags={tagsForPicker}
197
+ onContextChange={onContextChange}
198
+ injectedTags={injectedTags || {}}
199
+ selectedOfferDetails={selectedOfferDetails}
200
+ />
201
+ ) : (
202
+ <CapButton
203
+ data-testid={`rcs-carousel-desc-add-var-${cardIndex}`}
204
+ type="flat"
205
+ isAddBtn
206
+ onClick={onDescriptionAddVar}
207
+ disabled={!!cardError?.description}
208
+ >
209
+ {formatMessage(messages.addVar)}
210
+ </CapButton>
211
+ )
212
+ }
213
+ />
214
+ <CapRow className="rcs_text_area_wrapper">
215
+ {(isEditFlow || !isFullMode) ? (
216
+ renderEditMessage(card.description || '', cardIndex, RCS_TAG_AREA_FIELD_DESC)
217
+ ) : (
218
+ <TextArea
219
+ autosize={{ minRows: 3, maxRows: 5 }}
220
+ value={card.description || ''}
221
+ placeholder={formatMessage(messages.templateDescPlaceholder)}
222
+ onChange={onDescriptionChange}
223
+ disabled={isEditFlow || !isFullMode}
224
+ errorMessage={
225
+ cardError?.description && (
226
+ <CapError className="rcs-template-message-error">
227
+ {cardError.description}
228
+ </CapError>
229
+ )
230
+ }
231
+ />
232
+ )}
233
+ </CapRow>
234
+ </CapRow>
235
+ {!isEditFlow && (
236
+ <CapRow className="rcs-carousel-character-count-row">
237
+ <CarouselCharacterCount
238
+ formatMessage={formatMessage}
239
+ currentLength={descriptionCharCount}
240
+ maxLength={descriptionMaxLength}
241
+ />
242
+ </CapRow>
243
+ )}
244
+
245
+ <CapDivider className="rcs-carousel-card-divider" />
246
+ <CarouselCardButtons
247
+ cardIndex={cardIndex}
248
+ suggestions={card.suggestions || []}
249
+ isEditFlow={isEditFlow}
250
+ isFullMode={isFullMode}
251
+ hostName={hostName}
252
+ formatMessage={formatMessage}
253
+ onSuggestionsChange={onSuggestionsChange}
254
+ />
255
+ </CapCard>
256
+ );
257
+
258
+ CarouselCard.propTypes = {
259
+ cardIndex: PropTypes.number.isRequired,
260
+ card: PropTypes.object,
261
+ carouselLength: PropTypes.number,
262
+ isEditFlow: PropTypes.bool,
263
+ isFullMode: PropTypes.bool,
264
+ mediaRadioOptions: PropTypes.array,
265
+ cardError: PropTypes.object,
266
+ location: PropTypes.object,
267
+ tagsForPicker: PropTypes.array,
268
+ injectedTags: PropTypes.object,
269
+ selectedOfferDetails: PropTypes.array,
270
+ formatMessage: PropTypes.func.isRequired,
271
+ titleMaxLength: PropTypes.number,
272
+ descriptionMaxLength: PropTypes.number,
273
+ titleCharCount: PropTypes.number,
274
+ descriptionCharCount: PropTypes.number,
275
+ dimKey: PropTypes.string,
276
+ rcsData: PropTypes.object,
277
+ uploadRcsImage: PropTypes.func.isRequired,
278
+ uploadRcsVideo: PropTypes.func.isRequired,
279
+ hostName: PropTypes.string,
280
+ onDelete: PropTypes.func.isRequired,
281
+ onMediaTypeChange: PropTypes.func.isRequired,
282
+ onTitleChange: PropTypes.func.isRequired,
283
+ onDescriptionChange: PropTypes.func.isRequired,
284
+ onTitleAddVar: PropTypes.func.isRequired,
285
+ onDescriptionAddVar: PropTypes.func.isRequired,
286
+ onTitleTagSelect: PropTypes.func.isRequired,
287
+ onDescTagSelect: PropTypes.func.isRequired,
288
+ onContextChange: PropTypes.func,
289
+ renderEditMessage: PropTypes.func.isRequired,
290
+ onFieldChange: PropTypes.func.isRequired,
291
+ onSuggestionsChange: PropTypes.func.isRequired,
292
+ };
293
+
294
+ CarouselCard.defaultProps = {
295
+ card: {},
296
+ carouselLength: 1,
297
+ isEditFlow: false,
298
+ isFullMode: false,
299
+ mediaRadioOptions: [],
300
+ cardError: {},
301
+ location: {},
302
+ tagsForPicker: [],
303
+ injectedTags: {},
304
+ selectedOfferDetails: [],
305
+ titleMaxLength: 0,
306
+ descriptionMaxLength: 0,
307
+ titleCharCount: 0,
308
+ descriptionCharCount: 0,
309
+ dimKey: '',
310
+ rcsData: {},
311
+ hostName: '',
312
+ onContextChange: () => {},
313
+ };
314
+
315
+ export default React.memo(CarouselCard);
@@ -0,0 +1,93 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import cloneDeep from 'lodash/cloneDeep';
4
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
5
+ import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
6
+ import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
7
+ import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
8
+ import CapActionButton from '../../../v2Components/CapActionButton';
9
+ import { MAX_BUTTONS, RCS_BUTTON_TYPES } from '../constants';
10
+ import messages from '../messages';
11
+
12
+ /** Reuse rich-card buttons UI per carousel card. */
13
+ const CarouselCardButtons = ({
14
+ cardIndex,
15
+ suggestions,
16
+ isEditFlow,
17
+ isFullMode,
18
+ hostName,
19
+ formatMessage,
20
+ onSuggestionsChange,
21
+ }) => {
22
+ const suggestionsForCard = suggestions || [];
23
+ return (
24
+ <>
25
+ <CapHeader
26
+ className="rcs-button-cta"
27
+ title={(
28
+ <CapRow type="flex">
29
+ <CapHeading type="h4">
30
+ {formatMessage(messages.btnLabel)}
31
+ </CapHeading>
32
+ </CapRow>
33
+ )}
34
+ description={!isEditFlow && (
35
+ <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
36
+ )}
37
+ />
38
+ <CapActionButton
39
+ buttonType={RCS_BUTTON_TYPES.NONE}
40
+ updateButtonChange={(data, btnIndex) => {
41
+ // Match existing behavior: allow CapActionButton to manage save gating.
42
+ const updated = cloneDeep(suggestionsForCard);
43
+ if (btnIndex === MAX_BUTTONS) {
44
+ onSuggestionsChange(data);
45
+ return;
46
+ }
47
+ updated[btnIndex] = data;
48
+ onSuggestionsChange(updated);
49
+ }}
50
+ deleteButtonHandler={(btnIndex) => {
51
+ if (cardIndex === 0 && btnIndex === 0) {
52
+ return;
53
+ }
54
+ const savedCount = (suggestionsForCard || []).filter((suggestion) => suggestion && suggestion.isSaved).length;
55
+ const target = (suggestionsForCard || []).find((suggestion) => suggestion && suggestion.index === btnIndex);
56
+ if (cardIndex === 0 && target?.isSaved && savedCount <= 1) {
57
+ return;
58
+ }
59
+ const updated = cloneDeep(suggestionsForCard)
60
+ .filter((i) => i.index !== btnIndex)
61
+ .map((i, idx) => ({ ...i, index: idx }));
62
+ onSuggestionsChange(updated);
63
+ }}
64
+ suggestions={suggestionsForCard}
65
+ isEditFlow={isEditFlow}
66
+ isFullMode={isFullMode}
67
+ maxButtons={MAX_BUTTONS}
68
+ host={hostName}
69
+ minSavedSuggestions={cardIndex === 0 ? 1 : 0}
70
+ hideDeleteSuggestionIndexes={cardIndex === 0 ? [0] : []}
71
+ />
72
+ </>
73
+ );
74
+ };
75
+
76
+ CarouselCardButtons.propTypes = {
77
+ cardIndex: PropTypes.number.isRequired,
78
+ suggestions: PropTypes.array,
79
+ isEditFlow: PropTypes.bool,
80
+ isFullMode: PropTypes.bool,
81
+ hostName: PropTypes.string,
82
+ formatMessage: PropTypes.func.isRequired,
83
+ onSuggestionsChange: PropTypes.func.isRequired,
84
+ };
85
+
86
+ CarouselCardButtons.defaultProps = {
87
+ suggestions: [],
88
+ isEditFlow: false,
89
+ isFullMode: false,
90
+ hostName: '',
91
+ };
92
+
93
+ export default React.memo(CarouselCardButtons);
@@ -0,0 +1,136 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
4
+ import CapVideoUpload from '../../../v2Components/CapVideoUpload';
5
+ import CapImageUpload from '../../../v2Components/CapImageUpload';
6
+ import {
7
+ RCS,
8
+ RCS_MEDIA_TYPES,
9
+ ALLOWED_EXTENSIONS_VIDEO_REGEX,
10
+ RCS_CAROUSEL_VIDEO_SIZE,
11
+ ALLOWED_IMAGE_EXTENSIONS_REGEX,
12
+ RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS,
13
+ RCS_THUMBNAIL_MAX_SIZE,
14
+ RCS_THUMBNAIL_MIN_SIZE,
15
+ RCS_CAROUSEL_IMAGE_DIMENSIONS,
16
+ RCS_CAROUSEL_IMG_SIZE,
17
+ } from '../constants';
18
+ import {
19
+ getCarouselImageAssetIndex,
20
+ getCarouselVideoAssetIndex,
21
+ getCarouselThumbnailAssetIndex,
22
+ } from '../carouselUtils';
23
+ import messages from '../messages';
24
+
25
+ const CarouselCardMedia = ({
26
+ cardIndex,
27
+ card,
28
+ dimKey,
29
+ isFullMode,
30
+ isEditFlow,
31
+ rcsData,
32
+ uploadRcsImage,
33
+ uploadRcsVideo,
34
+ formatMessage,
35
+ onFieldChange,
36
+ }) => {
37
+ if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
38
+ return (
39
+ <>
40
+ <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Video</CapHeading>
41
+ <CapVideoUpload
42
+ index={getCarouselVideoAssetIndex(cardIndex)}
43
+ allowedExtensionsRegex={ALLOWED_EXTENSIONS_VIDEO_REGEX}
44
+ videoSize={RCS_CAROUSEL_VIDEO_SIZE}
45
+ isFullMode={isFullMode}
46
+ uploadAsset={uploadRcsVideo}
47
+ uploadedAssetList={card.videoAsset || {}}
48
+ onVideoUploadUpdateAssestList={(_, val) => {
49
+ onFieldChange('videoAsset', val);
50
+ }}
51
+ videoData={rcsData}
52
+ className="cap-custom-video-upload"
53
+ formClassName={"rcs-video-upload"}
54
+ channel={RCS}
55
+ errorMessage={formatMessage(messages.videoErrorMessage)}
56
+ showVideoNameAndDuration={false}
57
+ showReUploadButton={!isEditFlow && isFullMode}
58
+ channelSpecificStyle={!isFullMode}
59
+ />
60
+
61
+ <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
62
+ <CapImageUpload
63
+ allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
64
+ imgWidth={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.width}
65
+ imgHeight={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.height}
66
+ imgSize={RCS_THUMBNAIL_MAX_SIZE}
67
+ uploadAsset={uploadRcsImage}
68
+ isFullMode={isFullMode}
69
+ imageSrc={card.thumbnailSrc}
70
+ updateImageSrc={(val) => onFieldChange('thumbnailSrc', val)}
71
+ updateOnReUpload={() => onFieldChange('thumbnailSrc', '')}
72
+ minImgSize={RCS_THUMBNAIL_MIN_SIZE}
73
+ index={getCarouselThumbnailAssetIndex(cardIndex)}
74
+ className="cap-custom-image-upload"
75
+ key={`rcs-carousel-thumb-${cardIndex}-${dimKey}`}
76
+ imageData={rcsData}
77
+ channel={RCS}
78
+ channelSpecificStyle={!isFullMode}
79
+ skipDimensionValidation={true}
80
+ showReUploadButton={!isEditFlow && isFullMode}
81
+ disabled={isEditFlow || !isFullMode}
82
+ />
83
+ </>
84
+ );
85
+ }
86
+
87
+ // Default: IMAGE
88
+ return (
89
+ <>
90
+ <CapHeading type="h4" className="rcs-image-dimensions-label">{formatMessage(messages.mediaImage)}</CapHeading>
91
+ <CapImageUpload
92
+ allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
93
+ imgWidth={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.width}
94
+ imgHeight={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.height}
95
+ imgSize={RCS_CAROUSEL_IMG_SIZE}
96
+ uploadAsset={uploadRcsImage}
97
+ isFullMode={isFullMode}
98
+ imageSrc={card.imageSrc}
99
+ updateImageSrc={(val) => onFieldChange('imageSrc', val)}
100
+ updateOnReUpload={() => onFieldChange('imageSrc', '')}
101
+ index={getCarouselImageAssetIndex(cardIndex)}
102
+ className="cap-custom-image-upload"
103
+ key={`rcs-carousel-image-${cardIndex}-${dimKey}`}
104
+ imageData={rcsData}
105
+ channel={RCS}
106
+ channelSpecificStyle={!isFullMode}
107
+ skipDimensionValidation={true}
108
+ showReUploadButton={!isEditFlow && isFullMode}
109
+ disabled={isEditFlow || !isFullMode}
110
+ />
111
+ </>
112
+ );
113
+ };
114
+
115
+ CarouselCardMedia.propTypes = {
116
+ cardIndex: PropTypes.number.isRequired,
117
+ card: PropTypes.object,
118
+ dimKey: PropTypes.string,
119
+ isFullMode: PropTypes.bool,
120
+ isEditFlow: PropTypes.bool,
121
+ rcsData: PropTypes.object,
122
+ uploadRcsImage: PropTypes.func.isRequired,
123
+ uploadRcsVideo: PropTypes.func.isRequired,
124
+ formatMessage: PropTypes.func.isRequired,
125
+ onFieldChange: PropTypes.func.isRequired,
126
+ };
127
+
128
+ CarouselCardMedia.defaultProps = {
129
+ card: {},
130
+ dimKey: '',
131
+ isFullMode: false,
132
+ isEditFlow: false,
133
+ rcsData: {},
134
+ };
135
+
136
+ export default React.memo(CarouselCardMedia);
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
4
+ import messages from '../messages';
5
+
6
+ const CarouselCharacterCount = ({
7
+ formatMessage,
8
+ currentLength,
9
+ maxLength,
10
+ className,
11
+ }) => (
12
+ <CapLabel type="label1" className={className}>
13
+ {formatMessage(messages.templateMessageLength, {
14
+ currentLength,
15
+ maxLength,
16
+ })}
17
+ </CapLabel>
18
+ );
19
+
20
+ CarouselCharacterCount.propTypes = {
21
+ formatMessage: PropTypes.func.isRequired,
22
+ currentLength: PropTypes.number.isRequired,
23
+ maxLength: PropTypes.number.isRequired,
24
+ className: PropTypes.string,
25
+ };
26
+
27
+ CarouselCharacterCount.defaultProps = {
28
+ className: 'rcs-character-count',
29
+ };
30
+
31
+ export default React.memo(CarouselCharacterCount);
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
4
+ import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
5
+ import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
6
+ import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
7
+ import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
8
+ import { CAROUSEL_HEIGHT_OPTIONS, CAROUSEL_WIDTH_OPTIONS } from '../constants';
9
+
10
+ const CarouselDimensionSelection = ({
11
+ isEditFlow,
12
+ isFullMode,
13
+ selectedCarouselHeight,
14
+ selectedCarouselWidth,
15
+ selectedCarousel,
16
+ onHeightChange,
17
+ onWidthChange,
18
+ }) => (
19
+ <CapRow className="rcs-carousel-dimension-section">
20
+ <CapRow gutter={16} className="rcs-carousel-dimension-row">
21
+ <CapColumn span={12}>
22
+ <CapHeading type="h4" className="rcs-carousel-dimension-label">Card height</CapHeading>
23
+ {isEditFlow ? (
24
+ <CapHeading type="h5" className="rcs-creative-name-value">
25
+ {CAROUSEL_HEIGHT_OPTIONS.find((option) => option.value === selectedCarouselHeight)?.label || '-'}
26
+ </CapHeading>
27
+ ) : (
28
+ <CapSelect
29
+ id="rcs-carousel-height-select"
30
+ value={selectedCarouselHeight}
31
+ onChange={onHeightChange}
32
+ options={CAROUSEL_HEIGHT_OPTIONS}
33
+ disabled={!isFullMode}
34
+ />
35
+ )}
36
+ </CapColumn>
37
+ <CapColumn span={12}>
38
+ <CapHeading type="h4" className="rcs-carousel-dimension-label">Card width</CapHeading>
39
+ {isEditFlow ? (
40
+ <CapHeading type="h5" className="rcs-creative-name-value">
41
+ {CAROUSEL_WIDTH_OPTIONS.find((option) => option.value === selectedCarouselWidth)?.label || '-'}
42
+ </CapHeading>
43
+ ) : (
44
+ <CapSelect
45
+ id="rcs-carousel-width-select"
46
+ value={selectedCarouselWidth}
47
+ onChange={onWidthChange}
48
+ options={CAROUSEL_WIDTH_OPTIONS}
49
+ disabled={!isFullMode}
50
+ />
51
+ )}
52
+ </CapColumn>
53
+ </CapRow>
54
+ {!!selectedCarousel && (
55
+ <CapLabel type="label3" className="rcs-carousel-selected-dimension">
56
+ Selected: {selectedCarousel}
57
+ </CapLabel>
58
+ )}
59
+ </CapRow>
60
+ );
61
+
62
+ CarouselDimensionSelection.propTypes = {
63
+ isEditFlow: PropTypes.bool,
64
+ isFullMode: PropTypes.bool,
65
+ selectedCarouselHeight: PropTypes.string,
66
+ selectedCarouselWidth: PropTypes.string,
67
+ selectedCarousel: PropTypes.string,
68
+ onHeightChange: PropTypes.func.isRequired,
69
+ onWidthChange: PropTypes.func.isRequired,
70
+ };
71
+
72
+ CarouselDimensionSelection.defaultProps = {
73
+ isEditFlow: false,
74
+ isFullMode: false,
75
+ selectedCarouselHeight: '',
76
+ selectedCarouselWidth: '',
77
+ selectedCarousel: '',
78
+ };
79
+
80
+ export default React.memo(CarouselDimensionSelection);