@capillarytech/creatives-library 9.0.26 → 9.0.29-alpha.1
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/constants/unified.js +29 -0
- package/global-styles.js +1 -1
- package/package.json +1 -1
- package/services/tests/api.test.js +35 -20
- package/utils/commonUtils.js +22 -1
- package/utils/rcsPayloadUtils.js +102 -0
- package/utils/templateVarUtils.js +198 -0
- package/utils/tests/rcsPayloadUtils.test.js +295 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +21 -1
- package/v2Components/CapActionButton/index.js +260 -144
- package/v2Components/CapActionButton/index.scss +245 -14
- package/v2Components/CapActionButton/messages.js +32 -3
- package/v2Components/CapActionButton/tests/index.test.js +74 -19
- package/v2Components/CapImageUpload/index.js +2 -2
- package/v2Components/CapTagList/index.js +9 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +58 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +186 -22
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +381 -80
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +35 -2
- package/v2Components/CommonTestAndPreview/index.js +770 -231
- package/v2Components/CommonTestAndPreview/messages.js +45 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +25 -6
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +412 -257
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +337 -63
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +108 -15
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
- package/v2Components/CommonTestAndPreview/tests/utils.test.js +151 -0
- package/v2Components/CommonTestAndPreview/utils.js +84 -0
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
- package/v2Components/SmsFallback/constants.js +94 -0
- package/v2Components/SmsFallback/index.js +958 -0
- package/v2Components/SmsFallback/index.scss +266 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +471 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +45 -25
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +147 -32
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
- package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +27 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +37 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
- package/v2Containers/CreativesContainer/constants.js +11 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +77 -0
- package/v2Containers/CreativesContainer/index.js +328 -106
- package/v2Containers/CreativesContainer/index.scss +102 -1
- package/v2Containers/CreativesContainer/tests/CreativesSlideBoxWrapper.test.js +58 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.smsDltPreview.test.js +73 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +37 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +103 -34
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +193 -15
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +88 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +549 -57
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/Email/index.js +3 -54
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +4 -9
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/Rcs/carouselUtils.js +224 -0
- package/v2Containers/Rcs/components/CarouselCard.js +317 -0
- package/v2Containers/Rcs/components/CarouselCardButtons.js +113 -0
- package/v2Containers/Rcs/components/CarouselCardMedia.js +136 -0
- package/v2Containers/Rcs/components/CarouselCharacterCount.js +31 -0
- package/v2Containers/Rcs/components/CarouselDimensionSelection.js +80 -0
- package/v2Containers/Rcs/constants.js +132 -16
- package/v2Containers/Rcs/index.js +1808 -949
- package/v2Containers/Rcs/index.scss +443 -8
- package/v2Containers/Rcs/messages.js +45 -22
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +484 -0
- package/v2Containers/Rcs/tests/CarouselCard.test.js +464 -0
- package/v2Containers/Rcs/tests/CarouselCardButtons.test.js +211 -0
- package/v2Containers/Rcs/tests/CarouselCardMedia.test.js +160 -0
- package/v2Containers/Rcs/tests/CarouselCharacterCount.test.js +50 -0
- package/v2Containers/Rcs/tests/CarouselDimensionSelection.test.js +119 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74176 -39409
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
- package/v2Containers/Rcs/tests/carouselUtils.test.js +916 -0
- package/v2Containers/Rcs/tests/index.test.js +219 -40
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +873 -0
- package/v2Containers/Rcs/tests/utils.test.js +682 -30
- package/v2Containers/Rcs/utils.js +514 -12
- package/v2Containers/Sms/Create/index.js +115 -48
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +678 -169
- package/v2Containers/SmsTrai/Edit/index.scss +126 -0
- package/v2Containers/SmsTrai/Edit/messages.js +14 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +5 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +196 -12
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +131 -59
- package/v2Containers/Templates/sagas.js +57 -13
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +129 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
- package/v2Containers/Templates/tests/sagas.test.js +199 -16
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +88 -25
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -237,39 +237,6 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
237
237
|
this.props.actions.getCmsSetting(BEE_PLUGIN, dragDropId, 'create', undefined, isBEESupport, isBEEAppEnable);
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
-
// The <Email> direct path (SlideBoxContent routes here when
|
|
241
|
-
// templateData.base.is_drag_drop is true) bypasses EmailWrapper/useEmailWrapper,
|
|
242
|
-
// so Templates.BEETemplate is never seeded and a created/edited drag-drop would
|
|
243
|
-
// open as the HTML editor. Seed isDragDrop + the BEE plugin from the inline
|
|
244
|
-
// templateData so it opens as BEE.
|
|
245
|
-
if (_.isEmpty(this.props.Templates.BEETemplate) && !_.isEmpty(this.props.templateData)) {
|
|
246
|
-
const td = this.props.templateData;
|
|
247
|
-
const activeTab = _.get(td, 'versions.base.activeTab', 'en');
|
|
248
|
-
// This path bypasses setEditData, so seed the subject from the inline
|
|
249
|
-
// templateData too (otherwise the title is empty for a created drag-drop).
|
|
250
|
-
const inlineSubject = _.get(td, 'base.subject')
|
|
251
|
-
|| _.get(td, `versions.base.${activeTab}.subject`)
|
|
252
|
-
|| _.get(td, 'versions.base.subject')
|
|
253
|
-
|| _.get(td, 'emailSubject')
|
|
254
|
-
|| _.get(td, 'subject');
|
|
255
|
-
if (inlineSubject) {
|
|
256
|
-
formData['template-subject'] = inlineSubject;
|
|
257
|
-
}
|
|
258
|
-
const inlineIsDragDrop = _.get(td, 'base.is_drag_drop')
|
|
259
|
-
|| _.get(td, `versions.base.${activeTab}.is_drag_drop`)
|
|
260
|
-
|| _.get(td, 'versions.base.is_drag_drop')
|
|
261
|
-
|| _.get(td, 'is_drag_drop');
|
|
262
|
-
if ((inlineIsDragDrop === true || inlineIsDragDrop === 1) && isBEEAppEnable) {
|
|
263
|
-
this.setState({ isDragDrop: true });
|
|
264
|
-
const inlineDragDropId = _.get(td, 'base.drag_drop_id')
|
|
265
|
-
|| _.get(td, 'base.id')
|
|
266
|
-
|| _.get(td, `versions.base.${activeTab}.drag_drop_id`)
|
|
267
|
-
|| _.get(td, `versions.base.${activeTab}.id`)
|
|
268
|
-
|| _.get(td, 'drag_drop_id')
|
|
269
|
-
|| _.get(td, 'id');
|
|
270
|
-
this.props.actions.getCmsSetting(BEE_PLUGIN, inlineDragDropId, this.props.params.id ? 'open' : 'create', undefined, isBEESupport, isBEEAppEnable);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
240
|
const hasUploadedContent = !this.props.params?.id && this.props.Templates?.selectedEmailLayout && !_.isEmpty(formData);
|
|
274
241
|
this.setState({
|
|
275
242
|
content: (this.props.Templates.selectedEmailLayout ? this.props.Templates.selectedEmailLayout : ''),
|
|
@@ -586,21 +553,10 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
586
553
|
const beeToken = `BEEeditor${currentTab > 1 ? currentTab : ''}token`;
|
|
587
554
|
let beeJsonValue = '';
|
|
588
555
|
const selectedId = _.get(this.props, 'Email.templateDetails._id', '') || _.get(this.props, 'Templates.BEETemplate.versions.base.drag_drop_id', '');
|
|
589
|
-
|
|
590
|
-
// Embedded journey content: the inline templateData carries the user's
|
|
591
|
-
// edited BEE json (base.json_content), which is more current than the
|
|
592
|
-
// reusable template fetched via getTemplateDetails. Prefer it so a
|
|
593
|
-
// re-edit shows the saved edit instead of the original template. For
|
|
594
|
-
// the create/gallery flow this key is absent, so we fall through to the
|
|
595
|
-
// existing sources (template details / BEETemplate) unchanged.
|
|
596
|
-
beeJsonValue = _.get(nextProps, 'templateData.base.json_content', '')
|
|
597
|
-
|| _.get(nextProps, 'templateData.json_content', '');
|
|
598
|
-
|
|
556
|
+
|
|
599
557
|
// Get beeJsonValue from template data - check multiple sources
|
|
600
558
|
// First check if it's already in formData (from setEditData)
|
|
601
|
-
|
|
602
|
-
beeJsonValue = _.get(this.state, `formData[${currentTab - 1}][${langId}].json-content`, '');
|
|
603
|
-
}
|
|
559
|
+
beeJsonValue = _.get(this.state, `formData[${currentTab - 1}][${langId}].json-content`, '');
|
|
604
560
|
|
|
605
561
|
// Also check formData.base
|
|
606
562
|
if (!beeJsonValue) {
|
|
@@ -1183,14 +1139,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
1183
1139
|
const type = this.props.location.query.type;
|
|
1184
1140
|
|
|
1185
1141
|
formData['template-name'] = editData.name;
|
|
1186
|
-
|
|
1187
|
-
// emailSubject) over the fetched template's subject, so subject edits persist
|
|
1188
|
-
// on re-open. setEditData runs once (guarded by editDataSet), so this is a
|
|
1189
|
-
// one-time load-time override and won't fight typing in the subject field.
|
|
1190
|
-
const inlineSubject = _.get(this.props, 'templateData.base.subject', '')
|
|
1191
|
-
|| _.get(this.props, 'templateData.emailSubject', '')
|
|
1192
|
-
|| _.get(this.props, 'templateData.subject', '');
|
|
1193
|
-
const subject = inlineSubject || _.get(editData, 'versions.base.subject', '');
|
|
1142
|
+
const subject = _.get(editData, 'versions.base.subject', '');
|
|
1194
1143
|
formData['template-subject'] = subject;
|
|
1195
1144
|
formData.base = editData.versions.base;
|
|
1196
1145
|
|
|
@@ -234,18 +234,13 @@ const useEmailWrapper = ({
|
|
|
234
234
|
// New flow: When template details are loaded and it's a BEE template, set it in Templates.BEETemplate
|
|
235
235
|
// This allows Email component to properly initialize and call getCmsSetting
|
|
236
236
|
const hasTemplateDetails = Email?.templateDetails && !isEmpty(Email.templateDetails);
|
|
237
|
-
// Inline templateData (e.g. a freshly-created drag-drop content, or a
|
|
238
|
-
// library add-creative -> select-template) is authoritative when no template
|
|
239
|
-
// details were fetched. Without seeding BEETemplate from it, setBEETemplate
|
|
240
|
-
// never fires for that flow and a BEE template opens as the HTML editor.
|
|
241
|
-
const hasTemplateDataProp = templateData && !isEmpty(templateData);
|
|
242
237
|
// Note: We check Email?.BEETemplate as a proxy, but the actual BEETemplate is in Templates reducer
|
|
243
238
|
// The Email component will detect it via this.props.Templates.BEETemplate
|
|
244
239
|
const hasBEETemplate = Email?.BEETemplate && !isEmpty(Email.BEETemplate);
|
|
245
240
|
|
|
246
|
-
if (
|
|
241
|
+
if (hasTemplateDetails && !hasBEETemplate && templatesActions?.setBEETemplate && !beeTemplateSetRef.current) {
|
|
247
242
|
// Check if it's a BEE template
|
|
248
|
-
const editTemplateData =
|
|
243
|
+
const editTemplateData = Email.templateDetails;
|
|
249
244
|
const getIsDragDrop = (data) => {
|
|
250
245
|
if (!data) return false;
|
|
251
246
|
const baseDragDrop = get(data, 'versions.base.is_drag_drop', false);
|
|
@@ -301,10 +296,10 @@ const useEmailWrapper = ({
|
|
|
301
296
|
}
|
|
302
297
|
|
|
303
298
|
// Reset ref when template changes (template details cleared)
|
|
304
|
-
if (!hasTemplateDetails &&
|
|
299
|
+
if (!hasTemplateDetails && beeTemplateSetRef.current) {
|
|
305
300
|
beeTemplateSetRef.current = false;
|
|
306
301
|
}
|
|
307
|
-
}, [Email?.templateDetails, Email?.BEETemplate, Email?.getTemplateDetailsInProgress, Email?.fetchingCmsSettings, templatesActions, emailActions, params?.id, location?.query, checkBeeEditorEnabled, isFullMode
|
|
302
|
+
}, [Email?.templateDetails, Email?.BEETemplate, Email?.getTemplateDetailsInProgress, Email?.fetchingCmsSettings, templatesActions, emailActions, params?.id, location?.query, checkBeeEditorEnabled, isFullMode]);
|
|
308
303
|
|
|
309
304
|
const onChange = useCallback((e) => {
|
|
310
305
|
const { target: { value } } = e;
|
|
@@ -7,12 +7,12 @@ import {
|
|
|
7
7
|
|
|
8
8
|
describe("MPUSH Sagas", () => {
|
|
9
9
|
describe("v2MobilePushCreateSagas Combined", () => {
|
|
10
|
-
it
|
|
10
|
+
it("should initialize all mpush-related watcher sagas without error", () => {
|
|
11
11
|
return expectSaga(v2MobilePushCreateSagas).run();
|
|
12
12
|
});
|
|
13
13
|
});
|
|
14
14
|
describe("v2MobilePushWatchDuplicateTemplateSaga Combined", () => {
|
|
15
|
-
it
|
|
15
|
+
it("should initialize all mpush-related watcher sagas without error", () => {
|
|
16
16
|
return expectSaga(v2MobilePushWatchDuplicateTemplateSaga).run();
|
|
17
17
|
});
|
|
18
18
|
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
2
|
+
import { isUrl, isValidText } from '../Line/Container/Wrapper/utils';
|
|
3
|
+
import {
|
|
4
|
+
invalidVarRegex,
|
|
5
|
+
RCS_CTA_URL_TYPE,
|
|
6
|
+
URL_MAX_LENGTH,
|
|
7
|
+
} from '../../v2Components/CapActionButton/constants';
|
|
8
|
+
import {
|
|
9
|
+
RCS_MEDIA_TYPES,
|
|
10
|
+
RCS_BUTTON_TYPES,
|
|
11
|
+
TEMPLATE_TITLE_MAX_LENGTH,
|
|
12
|
+
RCS_RICH_CARD_MAX_LENGTH,
|
|
13
|
+
RCS_NUMERIC_VAR_TOKEN_REGEX,
|
|
14
|
+
RCS_CAROUSEL_ASSET_INDEX_BASE,
|
|
15
|
+
RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS,
|
|
16
|
+
MEDIUM,
|
|
17
|
+
} from './constants';
|
|
18
|
+
import { sanitizeCardVarMappedValue } from './utils';
|
|
19
|
+
|
|
20
|
+
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
21
|
+
|
|
22
|
+
export const getCarouselImageAssetIndex = (cardIndex) =>
|
|
23
|
+
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
|
|
24
|
+
export const getCarouselVideoAssetIndex = (cardIndex) =>
|
|
25
|
+
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 1;
|
|
26
|
+
export const getCarouselThumbnailAssetIndex = (cardIndex) =>
|
|
27
|
+
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 2;
|
|
28
|
+
|
|
29
|
+
export const isThumbnailAssetIndex = (index) => {
|
|
30
|
+
if (index === 1) return true; // standalone thumbnail
|
|
31
|
+
if (index >= RCS_CAROUSEL_ASSET_INDEX_BASE) {
|
|
32
|
+
return ((index - RCS_CAROUSEL_ASSET_INDEX_BASE) % 3) === 2; // carousel thumbnail slot
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Carousel dimension key: `${HEIGHT}_${WIDTH}` (e.g., SHORT_SMALL) */
|
|
38
|
+
export const getCarouselDimensionKey = (selectedCarouselHeight, selectedCarouselWidth) =>
|
|
39
|
+
`${selectedCarouselHeight}_${selectedCarouselWidth}`;
|
|
40
|
+
|
|
41
|
+
export const ensureFirstCardDefaultPhoneSuggestions = (cards) => {
|
|
42
|
+
const next = cloneDeep(cards || []);
|
|
43
|
+
if (next.length === 0) return next;
|
|
44
|
+
const suggestions = next[0].suggestions;
|
|
45
|
+
if (!Array.isArray(suggestions) || suggestions.length === 0) {
|
|
46
|
+
next[0] = {
|
|
47
|
+
...next[0],
|
|
48
|
+
suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return next;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const carouselButtonTextHasForbiddenChars = (value) => {
|
|
55
|
+
if (!value) return false;
|
|
56
|
+
if (value.includes('[') || value.includes(']')) return true;
|
|
57
|
+
const withoutValidVariables = value.replace(/\{\{[^}]*\}\}/g, '');
|
|
58
|
+
if (withoutValidVariables.includes('{') || withoutValidVariables.includes('}')) return true;
|
|
59
|
+
return false;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const isCompleteSavedCarouselSuggestion = (suggestion) => {
|
|
63
|
+
if (!suggestion || !suggestion.isSaved) return false;
|
|
64
|
+
const text = (suggestion.text || '').trim();
|
|
65
|
+
if (!text || !isValidText(text) || carouselButtonTextHasForbiddenChars(text)) return false;
|
|
66
|
+
if (suggestion.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
|
|
67
|
+
return String(suggestion.phoneNumber || '').length >= 5;
|
|
68
|
+
}
|
|
69
|
+
if (suggestion.type === RCS_BUTTON_TYPES.CTA) {
|
|
70
|
+
const url = String(suggestion.url || '').trim();
|
|
71
|
+
if (!url || url.length > URL_MAX_LENGTH) return false;
|
|
72
|
+
const subtype = suggestion.urlType || RCS_CTA_URL_TYPE.STATIC;
|
|
73
|
+
if (subtype === RCS_CTA_URL_TYPE.DYNAMIC) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (!isUrl(url)) return false;
|
|
77
|
+
const varMatches = url.match(invalidVarRegex);
|
|
78
|
+
return !(varMatches && varMatches.length > 0);
|
|
79
|
+
}
|
|
80
|
+
if (suggestion.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const isCarouselCardValid = (card, cardIndex) => {
|
|
87
|
+
if (!card) return false;
|
|
88
|
+
if (!card.title || !card.title.trim()) return false;
|
|
89
|
+
if ((card.title || '').length > TEMPLATE_TITLE_MAX_LENGTH) return false;
|
|
90
|
+
if (!card.description || !card.description.trim()) return false;
|
|
91
|
+
if ((card.description || '').length > RCS_RICH_CARD_MAX_LENGTH) return false;
|
|
92
|
+
let mediaOk = false;
|
|
93
|
+
if (card.mediaType === RCS_MEDIA_TYPES.IMAGE) {
|
|
94
|
+
mediaOk = !!(card.imageSrc && String(card.imageSrc).trim());
|
|
95
|
+
} else if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
|
|
96
|
+
const hasVideo = !!(card.videoAsset && card.videoAsset.videoSrc && String(card.videoAsset.videoSrc).trim());
|
|
97
|
+
const hasThumb = !!(card.thumbnailSrc && String(card.thumbnailSrc).trim());
|
|
98
|
+
mediaOk = hasVideo && hasThumb;
|
|
99
|
+
} else {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (!mediaOk) return false;
|
|
103
|
+
if (cardIndex === 0) {
|
|
104
|
+
const sugg = Array.isArray(card.suggestions) ? card.suggestions : [];
|
|
105
|
+
if (!sugg.some(isCompleteSavedCarouselSuggestion)) return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const checkDisableAddCarouselButton = (carouselData, activeCarouselIndex) => {
|
|
111
|
+
const idx = parseInt(activeCarouselIndex, 10);
|
|
112
|
+
const activeCard = carouselData?.[idx];
|
|
113
|
+
return !isCarouselCardValid(activeCard, idx);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Carousel: global variables across the whole carousel (all cards, title+body) */
|
|
117
|
+
export const getNextCarouselVarToken = (carouselData) => {
|
|
118
|
+
const nums = [];
|
|
119
|
+
(carouselData || []).forEach((card = {}) => {
|
|
120
|
+
const titleMatches = (card.title || '').match(RCS_NUMERIC_VAR_TOKEN_REGEX) || [];
|
|
121
|
+
const descMatches = (card.description || '').match(RCS_NUMERIC_VAR_TOKEN_REGEX) || [];
|
|
122
|
+
[...titleMatches, ...descMatches].forEach((tok) => {
|
|
123
|
+
const num = parseInt((tok.match(/\d+/) || [])[0], 10);
|
|
124
|
+
if (!Number.isNaN(num)) nums.push(num);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
const existing = new Set(nums);
|
|
128
|
+
let nextNumber = 1;
|
|
129
|
+
while (existing.has(nextNumber)) nextNumber++;
|
|
130
|
+
if (nextNumber > 19) return '';
|
|
131
|
+
return `{{${nextNumber}}}`;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/** Carousel: per-card character counts (same limits as rich card) */
|
|
135
|
+
export const getCarouselTitleCharacterCount = (carouselData, cardIndex) => {
|
|
136
|
+
const titleText = carouselData?.[cardIndex]?.title || '';
|
|
137
|
+
return titleText ? titleText.length : 0;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const getCarouselDescriptionCharacterCount = (carouselData, cardIndex) => {
|
|
141
|
+
const descriptionText = carouselData?.[cardIndex]?.description || '';
|
|
142
|
+
return descriptionText ? descriptionText.length : 0;
|
|
143
|
+
};
|
|
144
|
+
|
|
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 = {}) => {
|
|
152
|
+
const rawTitle = card.title || '';
|
|
153
|
+
const rawDesc = card.description || '';
|
|
154
|
+
const titleVarCount = (rawTitle.match(rcsVarRegex) || []).length;
|
|
155
|
+
const descVarCount = (rawDesc.match(rcsVarRegex) || []).length;
|
|
156
|
+
const resolvedTitle = !isFullMode
|
|
157
|
+
? resolveTemplateWithMap(rawTitle, slotOffset)
|
|
158
|
+
: rawTitle;
|
|
159
|
+
const resolvedDesc = !isFullMode
|
|
160
|
+
? resolveTemplateWithMap(rawDesc, slotOffset + titleVarCount)
|
|
161
|
+
: rawDesc;
|
|
162
|
+
if (!isFullMode) {
|
|
163
|
+
slotOffset += titleVarCount + descVarCount;
|
|
164
|
+
}
|
|
165
|
+
const videoThumb = card.thumbnailSrc || card.videoAsset?.videoThumbnail || '';
|
|
166
|
+
const videoSrc = card.videoAsset?.videoSrc || '';
|
|
167
|
+
return {
|
|
168
|
+
mediaType: (card.mediaType || '').toLowerCase(),
|
|
169
|
+
imageSrc: card.imageSrc || '',
|
|
170
|
+
videoSrc,
|
|
171
|
+
videoPreviewImg: videoThumb,
|
|
172
|
+
title: resolvedTitle,
|
|
173
|
+
bodyText: resolvedDesc,
|
|
174
|
+
suggestions: card.suggestions || [],
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/** Payload (create/edit RCS template): build `cardContent` for the carousel case. */
|
|
180
|
+
export const buildCarouselCardContentForPayload = (carouselData, {
|
|
181
|
+
isSlotMappingMode,
|
|
182
|
+
cardVarMapped,
|
|
183
|
+
selectedCarouselHeight,
|
|
184
|
+
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) => {
|
|
202
|
+
const varName = getVarNameFromToken(token);
|
|
203
|
+
if (!varName) return;
|
|
204
|
+
cardVarMappedForCard[varName] = sanitizeCardVarMappedValue(cardVarMapped?.[varName] ?? '');
|
|
205
|
+
});
|
|
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
|
+
});
|
|
@@ -0,0 +1,317 @@
|
|
|
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 CarouselCardMedia from './CarouselCardMedia';
|
|
19
|
+
import CarouselCardButtons from './CarouselCardButtons';
|
|
20
|
+
import CarouselCharacterCount from './CarouselCharacterCount';
|
|
21
|
+
|
|
22
|
+
const { TextArea } = CapInput;
|
|
23
|
+
|
|
24
|
+
/** One carousel card's editor: media, title, description, buttons — used as a tab pane in the carousel editor. */
|
|
25
|
+
const CarouselCard = ({
|
|
26
|
+
cardIndex,
|
|
27
|
+
card,
|
|
28
|
+
carouselLength,
|
|
29
|
+
isEditFlow,
|
|
30
|
+
isFullMode,
|
|
31
|
+
mediaRadioOptions,
|
|
32
|
+
cardError,
|
|
33
|
+
location,
|
|
34
|
+
tagsForPicker,
|
|
35
|
+
injectedTags,
|
|
36
|
+
selectedOfferDetails,
|
|
37
|
+
formatMessage,
|
|
38
|
+
titleMaxLength,
|
|
39
|
+
descriptionMaxLength,
|
|
40
|
+
titleCharCount,
|
|
41
|
+
descriptionCharCount,
|
|
42
|
+
dimKey,
|
|
43
|
+
rcsData,
|
|
44
|
+
uploadRcsImage,
|
|
45
|
+
uploadRcsVideo,
|
|
46
|
+
hostName,
|
|
47
|
+
onDelete,
|
|
48
|
+
onMediaTypeChange,
|
|
49
|
+
onTitleChange,
|
|
50
|
+
onDescriptionChange,
|
|
51
|
+
onTitleAddVar,
|
|
52
|
+
onDescriptionAddVar,
|
|
53
|
+
onTagSelect,
|
|
54
|
+
onContextChange,
|
|
55
|
+
renderEditMessage,
|
|
56
|
+
onFieldChange,
|
|
57
|
+
onSuggestionsChange,
|
|
58
|
+
}) => (
|
|
59
|
+
<CapCard
|
|
60
|
+
title={`Card ${cardIndex + 1}`}
|
|
61
|
+
extra={
|
|
62
|
+
!isEditFlow &&
|
|
63
|
+
(carouselLength === 1 ? (
|
|
64
|
+
<CapTooltip title={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}>
|
|
65
|
+
<span className="button-disabled-tooltip-wrapper rcs-carousel-delete-tooltip-wrap">
|
|
66
|
+
<CapButton
|
|
67
|
+
className="rcs-carousel-card-delete"
|
|
68
|
+
type="flat"
|
|
69
|
+
onClick={onDelete}
|
|
70
|
+
disabled
|
|
71
|
+
aria-label={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}
|
|
72
|
+
>
|
|
73
|
+
<CapIcon type="delete" />
|
|
74
|
+
</CapButton>
|
|
75
|
+
</span>
|
|
76
|
+
</CapTooltip>
|
|
77
|
+
) : (
|
|
78
|
+
<CapButton
|
|
79
|
+
className="rcs-carousel-card-delete"
|
|
80
|
+
type="flat"
|
|
81
|
+
onClick={onDelete}
|
|
82
|
+
aria-label={formatMessage(globalMessages.delete)}
|
|
83
|
+
>
|
|
84
|
+
<CapIcon type="delete" />
|
|
85
|
+
</CapButton>
|
|
86
|
+
))
|
|
87
|
+
}
|
|
88
|
+
className="rcs-carousel-card"
|
|
89
|
+
>
|
|
90
|
+
{/* Media selection should be at top of card */}
|
|
91
|
+
{isEditFlow ? (
|
|
92
|
+
<div className="rcs-creative-name-readonly">
|
|
93
|
+
<CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
|
|
94
|
+
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
95
|
+
{mediaRadioOptions.find((option) => option.value === card.mediaType)?.label || '-'}
|
|
96
|
+
</CapHeading>
|
|
97
|
+
</div>
|
|
98
|
+
) : (
|
|
99
|
+
<CapRow className="rcs-carousel-media-selection">
|
|
100
|
+
<CapColumn className="rcs-carousel-media-selection-heading">
|
|
101
|
+
<CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
|
|
102
|
+
</CapColumn>
|
|
103
|
+
<CapColumn>
|
|
104
|
+
<CapRadioGroup
|
|
105
|
+
id={`rcs-carousel-media-radio-${cardIndex}`}
|
|
106
|
+
options={mediaRadioOptions}
|
|
107
|
+
value={card.mediaType}
|
|
108
|
+
onChange={({ target: { value } }) => onMediaTypeChange(value)}
|
|
109
|
+
disabled={!isFullMode}
|
|
110
|
+
className="rcs-radio"
|
|
111
|
+
/>
|
|
112
|
+
</CapColumn>
|
|
113
|
+
</CapRow>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
<CapRow className={`rcs-carousel-media-upload ${isEditFlow ? 'rcs-carousel-media-upload--readonly-above' : ''}`}>
|
|
117
|
+
<CarouselCardMedia
|
|
118
|
+
cardIndex={cardIndex}
|
|
119
|
+
card={card}
|
|
120
|
+
dimKey={dimKey}
|
|
121
|
+
isFullMode={isFullMode}
|
|
122
|
+
isEditFlow={isEditFlow}
|
|
123
|
+
rcsData={rcsData}
|
|
124
|
+
uploadRcsImage={uploadRcsImage}
|
|
125
|
+
uploadRcsVideo={uploadRcsVideo}
|
|
126
|
+
formatMessage={formatMessage}
|
|
127
|
+
onFieldChange={onFieldChange}
|
|
128
|
+
/>
|
|
129
|
+
</CapRow>
|
|
130
|
+
|
|
131
|
+
{/* Title after media */}
|
|
132
|
+
<CapRow className="rcs-carousel-card-row">
|
|
133
|
+
<CapHeader
|
|
134
|
+
className="rcs-template-title-label"
|
|
135
|
+
title={<CapHeading type="h4">Card title</CapHeading>}
|
|
136
|
+
suffix={
|
|
137
|
+
(isEditFlow || !isFullMode) ? (
|
|
138
|
+
<TagList
|
|
139
|
+
label={formatMessage(globalMessages.addLabels)}
|
|
140
|
+
onTagSelect={onTagSelect}
|
|
141
|
+
location={location}
|
|
142
|
+
tags={tagsForPicker}
|
|
143
|
+
onContextChange={onContextChange}
|
|
144
|
+
injectedTags={injectedTags || {}}
|
|
145
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
146
|
+
/>
|
|
147
|
+
) : (
|
|
148
|
+
<CapButton
|
|
149
|
+
data-testid={`rcs-carousel-title-add-var-${cardIndex}`}
|
|
150
|
+
type="flat"
|
|
151
|
+
isAddBtn
|
|
152
|
+
onClick={onTitleAddVar}
|
|
153
|
+
disabled={!!cardError?.title}
|
|
154
|
+
>
|
|
155
|
+
{formatMessage(messages.addVar)}
|
|
156
|
+
</CapButton>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
/>
|
|
160
|
+
<CapRow className="rcs_text_area_wrapper">
|
|
161
|
+
{(isEditFlow || !isFullMode) ? (
|
|
162
|
+
renderEditMessage(card.title || '')
|
|
163
|
+
) : (
|
|
164
|
+
<CapInput
|
|
165
|
+
value={card.title || ''}
|
|
166
|
+
placeholder={formatMessage(messages.templateTitlePlaceholder)}
|
|
167
|
+
onChange={onTitleChange}
|
|
168
|
+
disabled={isEditFlow || !isFullMode}
|
|
169
|
+
errorMessage={cardError?.title}
|
|
170
|
+
/>
|
|
171
|
+
)}
|
|
172
|
+
</CapRow>
|
|
173
|
+
</CapRow>
|
|
174
|
+
{!isEditFlow && (
|
|
175
|
+
<CapRow className="rcs-carousel-character-count-row">
|
|
176
|
+
<CarouselCharacterCount
|
|
177
|
+
formatMessage={formatMessage}
|
|
178
|
+
currentLength={titleCharCount}
|
|
179
|
+
maxLength={titleMaxLength}
|
|
180
|
+
/>
|
|
181
|
+
</CapRow>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{/* Description after title */}
|
|
185
|
+
<CapRow className="rcs-carousel-card-row">
|
|
186
|
+
<CapHeader
|
|
187
|
+
title={<CapHeading type="h4">Card body text</CapHeading>}
|
|
188
|
+
suffix={
|
|
189
|
+
(isEditFlow || !isFullMode) ? (
|
|
190
|
+
<TagList
|
|
191
|
+
label={formatMessage(globalMessages.addLabels)}
|
|
192
|
+
onTagSelect={onTagSelect}
|
|
193
|
+
location={location}
|
|
194
|
+
tags={tagsForPicker}
|
|
195
|
+
onContextChange={onContextChange}
|
|
196
|
+
injectedTags={injectedTags || {}}
|
|
197
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
198
|
+
/>
|
|
199
|
+
) : (
|
|
200
|
+
<CapButton
|
|
201
|
+
data-testid={`rcs-carousel-desc-add-var-${cardIndex}`}
|
|
202
|
+
type="flat"
|
|
203
|
+
isAddBtn
|
|
204
|
+
onClick={onDescriptionAddVar}
|
|
205
|
+
disabled={!!cardError?.description}
|
|
206
|
+
>
|
|
207
|
+
{formatMessage(messages.addVar)}
|
|
208
|
+
</CapButton>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
/>
|
|
212
|
+
<CapRow className="rcs_text_area_wrapper">
|
|
213
|
+
{(isEditFlow || !isFullMode) ? (
|
|
214
|
+
renderEditMessage(card.description || '')
|
|
215
|
+
) : (
|
|
216
|
+
<TextArea
|
|
217
|
+
autosize={{ minRows: 3, maxRows: 5 }}
|
|
218
|
+
value={card.description || ''}
|
|
219
|
+
placeholder={formatMessage(messages.templateDescPlaceholder)}
|
|
220
|
+
onChange={onDescriptionChange}
|
|
221
|
+
disabled={isEditFlow || !isFullMode}
|
|
222
|
+
errorMessage={
|
|
223
|
+
cardError?.description && (
|
|
224
|
+
<CapError className="rcs-template-message-error">
|
|
225
|
+
{cardError.description}
|
|
226
|
+
</CapError>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
/>
|
|
230
|
+
)}
|
|
231
|
+
</CapRow>
|
|
232
|
+
</CapRow>
|
|
233
|
+
{!isEditFlow && (
|
|
234
|
+
<CapRow className="rcs-carousel-character-count-row">
|
|
235
|
+
<CarouselCharacterCount
|
|
236
|
+
formatMessage={formatMessage}
|
|
237
|
+
currentLength={descriptionCharCount}
|
|
238
|
+
maxLength={descriptionMaxLength}
|
|
239
|
+
/>
|
|
240
|
+
</CapRow>
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
<CapDivider className="rcs-carousel-card-divider" />
|
|
244
|
+
<CarouselCardButtons
|
|
245
|
+
cardIndex={cardIndex}
|
|
246
|
+
suggestions={card.suggestions || []}
|
|
247
|
+
isEditFlow={isEditFlow}
|
|
248
|
+
isFullMode={isFullMode}
|
|
249
|
+
hostName={hostName}
|
|
250
|
+
formatMessage={formatMessage}
|
|
251
|
+
onSuggestionsChange={onSuggestionsChange}
|
|
252
|
+
location={location}
|
|
253
|
+
tags={tagsForPicker}
|
|
254
|
+
injectedTags={injectedTags}
|
|
255
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
256
|
+
onContextChange={onContextChange}
|
|
257
|
+
/>
|
|
258
|
+
</CapCard>
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
CarouselCard.propTypes = {
|
|
262
|
+
cardIndex: PropTypes.number.isRequired,
|
|
263
|
+
card: PropTypes.object,
|
|
264
|
+
carouselLength: PropTypes.number,
|
|
265
|
+
isEditFlow: PropTypes.bool,
|
|
266
|
+
isFullMode: PropTypes.bool,
|
|
267
|
+
mediaRadioOptions: PropTypes.array,
|
|
268
|
+
cardError: PropTypes.object,
|
|
269
|
+
location: PropTypes.object,
|
|
270
|
+
tagsForPicker: PropTypes.array,
|
|
271
|
+
injectedTags: PropTypes.object,
|
|
272
|
+
selectedOfferDetails: PropTypes.array,
|
|
273
|
+
formatMessage: PropTypes.func.isRequired,
|
|
274
|
+
titleMaxLength: PropTypes.number,
|
|
275
|
+
descriptionMaxLength: PropTypes.number,
|
|
276
|
+
titleCharCount: PropTypes.number,
|
|
277
|
+
descriptionCharCount: PropTypes.number,
|
|
278
|
+
dimKey: PropTypes.string,
|
|
279
|
+
rcsData: PropTypes.object,
|
|
280
|
+
uploadRcsImage: PropTypes.func.isRequired,
|
|
281
|
+
uploadRcsVideo: PropTypes.func.isRequired,
|
|
282
|
+
hostName: PropTypes.string,
|
|
283
|
+
onDelete: PropTypes.func.isRequired,
|
|
284
|
+
onMediaTypeChange: PropTypes.func.isRequired,
|
|
285
|
+
onTitleChange: PropTypes.func.isRequired,
|
|
286
|
+
onDescriptionChange: PropTypes.func.isRequired,
|
|
287
|
+
onTitleAddVar: PropTypes.func.isRequired,
|
|
288
|
+
onDescriptionAddVar: PropTypes.func.isRequired,
|
|
289
|
+
onTagSelect: PropTypes.func.isRequired,
|
|
290
|
+
onContextChange: PropTypes.func,
|
|
291
|
+
renderEditMessage: PropTypes.func.isRequired,
|
|
292
|
+
onFieldChange: PropTypes.func.isRequired,
|
|
293
|
+
onSuggestionsChange: PropTypes.func.isRequired,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
CarouselCard.defaultProps = {
|
|
297
|
+
card: {},
|
|
298
|
+
carouselLength: 1,
|
|
299
|
+
isEditFlow: false,
|
|
300
|
+
isFullMode: false,
|
|
301
|
+
mediaRadioOptions: [],
|
|
302
|
+
cardError: {},
|
|
303
|
+
location: {},
|
|
304
|
+
tagsForPicker: [],
|
|
305
|
+
injectedTags: {},
|
|
306
|
+
selectedOfferDetails: [],
|
|
307
|
+
titleMaxLength: 0,
|
|
308
|
+
descriptionMaxLength: 0,
|
|
309
|
+
titleCharCount: 0,
|
|
310
|
+
descriptionCharCount: 0,
|
|
311
|
+
dimKey: '',
|
|
312
|
+
rcsData: {},
|
|
313
|
+
hostName: '',
|
|
314
|
+
onContextChange: () => {},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export default React.memo(CarouselCard);
|