@capillarytech/creatives-library 9.0.13 → 9.0.14

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 (38) hide show
  1. package/package.json +1 -1
  2. package/services/api.js +10 -0
  3. package/services/tests/api.test.js +83 -0
  4. package/v2Components/CommonTestAndPreview/UnifiedPreview/WhatsAppPreviewContent.js +5 -3
  5. package/v2Components/CommonTestAndPreview/index.js +7 -0
  6. package/v2Components/NavigationBar/index.js +27 -0
  7. package/v2Components/NavigationBar/messages.js +4 -0
  8. package/v2Components/NavigationBar/tests/index.test.js +19 -0
  9. package/v2Components/NewCallTask/index.js +6 -1
  10. package/v2Components/TemplatePreview/index.js +4 -2
  11. package/v2Containers/Cap/index.js +3 -1
  12. package/v2Containers/CommunicationFlow/CommunicationFlow.js +130 -20
  13. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +154 -0
  14. package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +240 -0
  15. package/v2Containers/CommunicationFlow/DemoPage.js +47 -0
  16. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +369 -2
  17. package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +619 -0
  18. package/v2Containers/CommunicationFlow/Tests/DemoPage.test.js +77 -0
  19. package/v2Containers/CommunicationFlow/Tests/getContentBody.test.js +933 -0
  20. package/v2Containers/CommunicationFlow/constants.js +45 -10
  21. package/v2Containers/CommunicationFlow/index.js +5 -2
  22. package/v2Containers/CommunicationFlow/messages.js +20 -0
  23. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +94 -31
  24. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +14 -11
  25. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +1144 -32
  26. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/extractContentForPreview.js +183 -0
  27. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +3 -0
  28. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +39 -0
  29. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +6 -2
  30. package/v2Containers/CommunicationFlow/utils/getContentBody.js +369 -0
  31. package/v2Containers/CommunicationFlow/utils/getContentBody.scss +19 -0
  32. package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +1 -1
  33. package/v2Containers/CreativesContainer/constants.js +6 -0
  34. package/v2Containers/CreativesContainer/index.js +68 -1
  35. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +2 -2
  36. package/v2Containers/Templates/index.js +2 -2
  37. package/v2Containers/TemplatesV2/index.js +9 -1
  38. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +41 -34
@@ -0,0 +1,183 @@
1
+ import {
2
+ VIBER, MOBILE_PUSH, MPUSH, SMS, EMAIL, WEBPUSH, ZALO, INAPP, WHATSAPP, RCS,
3
+ TEXT, IMAGE, VIDEO, DOCUMENT, CTA, QUICK_REPLY,
4
+ } from '../../../CreativesContainer/constants';
5
+ import { getWhatsappDocPreview } from '../../../Whatsapp/utils';
6
+
7
+ const extractSmsContent = (templateData) => {
8
+ const smsBody = templateData.messageBody || templateData.smsBody || '';
9
+ return {
10
+ content: smsBody,
11
+ formData: [{ 'sms-editor': smsBody, activeTab: 'base', base: { 'sms-editor': smsBody } }],
12
+ };
13
+ };
14
+
15
+ const extractEmailContent = (templateData) => {
16
+ const emailContent = templateData.emailBody || templateData.emailHtml || '';
17
+ const emailSubject = templateData.emailSubject || '';
18
+ const formData = [{ 'template-subject': emailSubject, activeTab: 'base', base: { 'template-content': emailContent } }];
19
+ formData['template-subject'] = emailSubject;
20
+ return { content: emailContent, formData };
21
+ };
22
+
23
+ const extractMobilePushDevice = ({
24
+ title = '',
25
+ message = '',
26
+ expandableDetails: {
27
+ image = '', media = [], ctas = [], carouselData = [],
28
+ } = {},
29
+ } = {}) => ({
30
+ header: title,
31
+ bodyText: message,
32
+ bodyImage: image,
33
+ bodyVideo: media[0] || null,
34
+ bodyGif: '',
35
+ actions: ctas,
36
+ carouselData,
37
+ });
38
+
39
+ const extractMobilePushContent = (templateData) => ({
40
+ content: {
41
+ androidContent: extractMobilePushDevice(templateData.androidContent),
42
+ iosContent: extractMobilePushDevice(templateData.iosContent),
43
+ },
44
+ formData: null,
45
+ accountId: templateData.accountId,
46
+ });
47
+
48
+ const extractInAppDevice = (deviceData = {}) => {
49
+ const ctas = deviceData.expandableDetails?.ctas || [];
50
+ return {
51
+ mediaPreview: { inAppImageSrcAndroid: deviceData.expandableDetails?.image || undefined },
52
+ templateTitle: deviceData.title || '',
53
+ templateMsg: deviceData.message || '',
54
+ ctaData: ctas,
55
+ templateLayoutType: deviceData.bodyType || '',
56
+ deepLinkValue: ctas[0]?.actionLink || null,
57
+ };
58
+ };
59
+
60
+ const extractInAppContent = (templateData) => ({
61
+ content: {
62
+ androidContent: extractInAppDevice(templateData.androidContent),
63
+ iosContent: {
64
+ ...extractInAppDevice(templateData.iosContent),
65
+ mediaPreview: { inAppImageSrcIos: templateData.iosContent?.expandableDetails?.image || undefined },
66
+ },
67
+ templateLayoutType: templateData.androidContent?.bodyType || '',
68
+ templateMediaType: templateData.androidContent?.expandableDetails?.image ? IMAGE : TEXT,
69
+ },
70
+ formData: null,
71
+ accountId: templateData.accountId,
72
+ });
73
+
74
+ const extractWhatsappContent = (templateData) => {
75
+ const configs = templateData.templateConfigs || {};
76
+ const media = configs.whatsappMedia || {};
77
+ const buttonType = configs.buttonType || '';
78
+ const buttons = configs.buttons || [];
79
+ return {
80
+ content: {
81
+ templateMsg: templateData.messageBody || '',
82
+ templateHeader: media.header || '',
83
+ templateFooter: media.footer || '',
84
+ whatsappImageSrc: configs.mediaType === IMAGE ? media.url : '',
85
+ whatsappVideoPreviewImg: configs.mediaType === VIDEO ? media.previewUrl : '',
86
+ docPreview: configs.mediaType === DOCUMENT && media.docParams
87
+ ? getWhatsappDocPreview({ ...media.docParams, whatsappDocImg: media.previewUrl || '' })
88
+ : null,
89
+ ctaData: buttonType === CTA ? buttons : [],
90
+ quickReplyData: buttonType === QUICK_REPLY ? buttons : [],
91
+ carouselData: configs.cards || [],
92
+ accountId: templateData.accountId,
93
+ sourceAccountIdentifier: templateData.sourceAccountIdentifier,
94
+ accountName: templateData.accountName,
95
+ },
96
+ formData: null,
97
+ accountId: templateData.accountId,
98
+ sourceAccountIdentifier: templateData.sourceAccountIdentifier,
99
+ accountName: templateData.accountName,
100
+ };
101
+ };
102
+
103
+ const parseJson = (value) => {
104
+ try {
105
+ return typeof value === 'string' ? JSON.parse(value || '{}') : (value || {});
106
+ } catch {
107
+ return {};
108
+ }
109
+ };
110
+
111
+ const extractViberContent = (templateData) => {
112
+ const parsedBody = parseJson(templateData.messageBody);
113
+ const accountDetails = parseJson(templateData.accountDetails);
114
+ const content = parsedBody.content || parsedBody;
115
+ const accountName = accountDetails?.name || accountDetails?.accountName || '';
116
+ const result = {
117
+ viberPreviewContent: {
118
+ messageContent: content.text || '',
119
+ buttonText: content.button?.text || '',
120
+ imageURL: content.image?.url || undefined,
121
+ },
122
+ accountName: accountName ? [accountName] : [],
123
+ brandName: accountName ? [accountName] : [],
124
+ };
125
+ if (content.video?.url) {
126
+ result.videoParams = {
127
+ viberVideoSrc: content.video.url,
128
+ viberVideoPreviewImg: content.video.thumbnailUrl || '',
129
+ };
130
+ }
131
+ return { content: result, formData: null };
132
+ };
133
+
134
+ const extractRcsContent = (templateData) => {
135
+ const cardContent = templateData.rcsContent?.cardContent?.[0] || {};
136
+ const media = cardContent.media || {};
137
+ return {
138
+ content: {
139
+ templateHeader: cardContent.title || '',
140
+ templateMessage: cardContent.description || '',
141
+ rcsImageSrc: media.mediaType === IMAGE ? media.mediaUrl : undefined,
142
+ rcsVideoSrc: media.mediaType === VIDEO ? media.mediaUrl : undefined,
143
+ rcsThumbnailSrc: media.mediaType === VIDEO ? media.thumbnailUrl : undefined,
144
+ suggestions: cardContent.suggestions || [],
145
+ },
146
+ formData: null,
147
+ };
148
+ };
149
+
150
+ const extractWebpushContent = (templateData) => ({
151
+ content: templateData?.messageContent?.content || {},
152
+ formData: null,
153
+ });
154
+
155
+ const extractZaloContent = (templateData) => ({
156
+ content: {
157
+ ...templateData,
158
+ templatePreviewUrl: templateData?.templateConfigs?.template || templateData?.templatePreviewUrl || '',
159
+ },
160
+ formData: null,
161
+ });
162
+
163
+ const CHANNEL_EXTRACTORS = {
164
+ [SMS]: extractSmsContent,
165
+ [EMAIL]: extractEmailContent,
166
+ [MOBILE_PUSH]: extractMobilePushContent,
167
+ [MPUSH]: extractMobilePushContent,
168
+ [INAPP]: extractInAppContent,
169
+ [WHATSAPP]: extractWhatsappContent,
170
+ [VIBER]: extractViberContent,
171
+ [RCS]: extractRcsContent,
172
+ [WEBPUSH]: extractWebpushContent,
173
+ [ZALO]: extractZaloContent,
174
+ };
175
+
176
+ const extractContentForPreview = (item) => {
177
+ const templateData = item?.templateData || {};
178
+ const upperChannel = item?.channel?.toUpperCase();
179
+ const extractor = CHANNEL_EXTRACTORS[upperChannel];
180
+ return extractor ? extractor(templateData) : { content: templateData, formData: null };
181
+ };
182
+
183
+ export default extractContentForPreview;
@@ -40,6 +40,7 @@ const CommunicationStrategyStep = ({
40
40
  key: option?.value,
41
41
  value: option?.value,
42
42
  label: customLabel,
43
+ simpleLabel: option?.label,
43
44
  disabled: option?.disabled,
44
45
  };
45
46
  }), [optionsProp, formatMessage]);
@@ -63,6 +64,8 @@ const CommunicationStrategyStep = ({
63
64
  onChange={handleChange}
64
65
  placeholder={formatMessage(messages.communicationStrategyPlaceholder)}
65
66
  options={options}
67
+ propToBeMadeLabel="simpleLabel"
68
+ optionLabelProp="label"
66
69
  style={{ width: '20.375rem' }}
67
70
  disabled={disabled}
68
71
  />
@@ -174,6 +174,45 @@ const DeliverySettingsSection = ({
174
174
  return merged;
175
175
  }, [domainPropertiesData, wecrmViberData]);
176
176
 
177
+ // Helper: returns true if a channelSetting entry has at least one real (non-empty) value
178
+ const hasRealData = (settings) => settings && Object.values(settings).some((value) => value !== null && value !== '' && value !== undefined);
179
+
180
+ // When the domain properties API response arrives, auto-save the default sender IDs
181
+ // into channelSetting so they are included in the payload even if the user never
182
+ // opens the SenderDetails slidebox.
183
+ //
184
+ // Rules:
185
+ // - Only run for channels that are currently selected (deliveryChannels)
186
+ // - Only save a channel if the API returned a real sender ID for it (not empty)
187
+ // - Never overwrite a channel the user has already configured manually
188
+ useEffect(() => {
189
+ if (!entityWithWecrmViber || !onDeliverySettingChange || deliveryChannels.length === 0) return;
190
+
191
+ // Convert the API entity into the channelSetting shape (e.g. { SMS: { gsmSenderId: '...' } })
192
+ const senderDetailsFromAPI = parseSenderDetailsFromEntity(entityWithWecrmViber, { whatsappSourceAccountId });
193
+ const channelSettingFromAPI = buildChannelSettingFromFieldValues(senderDetailsFromAPI);
194
+
195
+ const savedChannelSetting = deliverySetting?.channelSetting || {};
196
+ const updatedChannelSetting = { ...savedChannelSetting };
197
+ let didChange = false;
198
+
199
+ deliveryChannels.forEach((channel) => {
200
+ const apiDataForChannel = channelSettingFromAPI[channel];
201
+ const userSavedDataForChannel = savedChannelSetting[channel];
202
+
203
+ const apiHasRealSenderId = hasRealData(apiDataForChannel);
204
+ const userAlreadyConfigured = hasRealData(userSavedDataForChannel);
205
+
206
+ if (apiHasRealSenderId && !userAlreadyConfigured) {
207
+ updatedChannelSetting[channel] = apiDataForChannel;
208
+ didChange = true;
209
+ }
210
+ });
211
+
212
+ if (!didChange) return;
213
+ onDeliverySettingChange({ ...deliverySetting, channelSetting: updatedChannelSetting });
214
+ }, [entityWithWecrmViber]); // eslint-disable-line react-hooks/exhaustive-deps
215
+
177
216
  const parsedSenderDetails = useMemo(() => {
178
217
  if (!entityWithWecrmViber) return null;
179
218
  const fromEntity = parseSenderDetailsFromEntity(entityWithWecrmViber, { whatsappSourceAccountId });
@@ -278,10 +278,10 @@ describe('DeliverySettingsSection — marketer flows', () => {
278
278
 
279
279
  await waitFor(() => {
280
280
  expect(screen.getByText('zalo-sender-77')).toBeInTheDocument();
281
- expect(screen.getByText('Account')).toBeInTheDocument();
282
- expect(screen.getByText('line@account-id')).toBeInTheDocument();
283
281
  expect(screen.getByText('+12025550123')).toBeInTheDocument();
284
282
  });
283
+ // LINE is excluded from delivery settings (CHANNELS_WITHOUT_DELIVERY), so no account row
284
+ expect(screen.queryByText('line@account-id')).not.toBeInTheDocument();
285
285
  });
286
286
  });
287
287
 
@@ -538,6 +538,10 @@ describe('DeliverySettingsSection — marketer flows', () => {
538
538
  });
539
539
 
540
540
  await waitFor(() => expect(screen.getByText('Sender details')).toBeInTheDocument());
541
+ // The auto-save effect fires when domainProperties loads, calling onDeliverySettingChange
542
+ // with the API default (+1002003001). Wait for it, then clear so only the manual save is checked.
543
+ await waitFor(() => expect(onDeliverySettingChange).toHaveBeenCalled());
544
+ onDeliverySettingChange.mockClear();
541
545
  await openSenderDetailsFromSummary();
542
546
 
543
547
  const slidebox = document.querySelector('.sender-details');
@@ -0,0 +1,369 @@
1
+ import React from 'react';
2
+ import CapImage from '@capillarytech/cap-ui-library/CapImage';
3
+ import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
4
+ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
5
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
6
+ import { CAP_SPACE_04 } from '@capillarytech/cap-ui-library/styled/variables';
7
+ import { getWhatsappDocPreview, getWhatsappQuickReply, getWhatsappCarouselButtonView } from '../../Whatsapp/utils';
8
+ import { PHONE_NUMBER } from '../../Whatsapp/constants';
9
+ import {
10
+ VIBER, MOBILE_PUSH, MPUSH, SMS, EMAIL, INAPP, WEBPUSH, RCS, ZALO, WHATSAPP, LINE, IMAGE, VIDEO, DOCUMENT, CTA,
11
+ } from '../../CreativesContainer/constants';
12
+ import videoPlay from '../../../assets/videoPlay.svg';
13
+ import './getContentBody.scss';
14
+
15
+ const getContentBody = (item) => {
16
+ const templateData = item?.templateData || {};
17
+ const channel = item?.channel?.toUpperCase();
18
+
19
+ switch (channel) {
20
+ case SMS: {
21
+ const smsText = templateData.messageBody || templateData.smsBody || '';
22
+ return <CapLabel type="label2">{smsText}</CapLabel>;
23
+ }
24
+
25
+ case EMAIL: {
26
+ return <CapLabel type="label2">{templateData.emailSubject || ''}</CapLabel>;
27
+ }
28
+
29
+ case MOBILE_PUSH:
30
+ case MPUSH: {
31
+ const deviceData = templateData.androidContent || templateData.iosContent || {};
32
+ const title = deviceData.title || '';
33
+ const message = deviceData.message || '';
34
+ const image = deviceData.expandableDetails?.image || '';
35
+ const media = deviceData.expandableDetails?.media?.[0] || null;
36
+ const ctas = deviceData.expandableDetails?.ctas || [];
37
+ const carouselData = deviceData.expandableDetails?.carouselData || [];
38
+ return (
39
+ <CapRow className="mobilepush-container">
40
+ <CapRow className="app-header">
41
+ <CapRow className="app-header-left">
42
+ <CapRow className="app-icon" />
43
+ <CapLabel type="label4">{title}</CapLabel>
44
+ </CapRow>
45
+ </CapRow>
46
+ <CapLabel className="mobilepush-message" type="label1">{message}</CapLabel>
47
+ {image && !media && !carouselData.length && (
48
+ <CapImage src={image} className="mobilepush-image mobilepush-image-padding" />
49
+ )}
50
+ {media?.url && (
51
+ <CapRow className="video-preview">
52
+ <video controls={false} tabIndex="0">
53
+ <source src={media.url} type="video/mp4" />
54
+ </video>
55
+ <CapRow className="icon-position">
56
+ <CapImage className="video-icon" src={videoPlay} />
57
+ </CapRow>
58
+ </CapRow>
59
+ )}
60
+ {carouselData.length > 0 && (
61
+ <CapRow className="scroll-container">
62
+ {carouselData.map((data, index) => (
63
+ <CapRow key={`carousel-${index}`} className="whatsapp-carousel-container" role="group">
64
+ <CapRow className="whatsapp-carousel-card">
65
+ {data?.imageUrl && (
66
+ <CapImage src={data?.imageUrl} className="whatsapp-image" />
67
+ )}
68
+ </CapRow>
69
+ </CapRow>
70
+ ))}
71
+ </CapRow>
72
+ )}
73
+ {ctas.length > 0 && (
74
+ <CapRow className="actions">
75
+ {ctas.map((cta) => (
76
+ <CapLabel className="action" key={cta?.actionText}>
77
+ {cta?.actionText?.toUpperCase()}
78
+ </CapLabel>
79
+ ))}
80
+ </CapRow>
81
+ )}
82
+ </CapRow>
83
+ );
84
+ }
85
+
86
+ case INAPP: {
87
+ const deviceData = templateData.androidContent || templateData.iosContent || {};
88
+ const title = deviceData.title || '';
89
+ const message = deviceData.message || '';
90
+ const image = deviceData.expandableDetails?.image || '';
91
+ const ctas = deviceData.expandableDetails?.ctas || [];
92
+ return (
93
+ <>
94
+ {title && <CapLabel type="label2">{title}</CapLabel>}
95
+ {message && <CapLabel type="label1">{message}</CapLabel>}
96
+ {image && <CapImage src={image} className="mobilepush-image" />}
97
+ {ctas.length > 0 && (
98
+ <CapRow className="actions">
99
+ {ctas.map((cta) => (
100
+ <CapLabel className="action" key={cta?.actionText}>
101
+ {cta?.actionText?.toUpperCase()}
102
+ </CapLabel>
103
+ ))}
104
+ </CapRow>
105
+ )}
106
+ </>
107
+ );
108
+ }
109
+
110
+ case WEBPUSH: {
111
+ const innerContent = templateData?.messageContent?.content?.content || {};
112
+ const notificationTitle = innerContent.title || '';
113
+ const notificationMessage = innerContent.message || '';
114
+ const iconImageUrl = innerContent.iconImageUrl || '';
115
+ const mediaImage = innerContent.expandableDetails?.image || '';
116
+ const rawCtas = innerContent.cta ? [innerContent.cta] : (innerContent.ctas || []);
117
+ const visibleCtas = rawCtas.slice(0, 2);
118
+ return (
119
+ <CapRow className="sms-template-content webpush-template-content">
120
+ <CapRow className="webpush-template-card">
121
+ <CapRow className="webpush-template-meta">
122
+ {iconImageUrl
123
+ ? <CapImage src={iconImageUrl} alt="Brand Icon" className="webpush-brand-icon" />
124
+ : <CapRow className="webpush-brand-icon-placeholder" />
125
+ }
126
+ <CapRow className="webpush-template-text">
127
+ <CapRow className="webpush-template-header">
128
+ <CapLabel className="webpush-template-title">{notificationTitle}</CapLabel>
129
+ </CapRow>
130
+ {notificationMessage && (
131
+ <CapLabel className="webpush-template-message">{notificationMessage}</CapLabel>
132
+ )}
133
+ {mediaImage && (
134
+ <CapImage src={mediaImage} alt="Media" className="webpush-media-image" />
135
+ )}
136
+ </CapRow>
137
+ </CapRow>
138
+ {visibleCtas.length > 0 && (
139
+ <CapRow className={`webpush-template-cta-section${visibleCtas.length === 1 ? ' single-cta' : ''}`}>
140
+ {visibleCtas.map((cta, index) => (
141
+ <CapRow className="webpush-template-cta-item" key={`webpush-cta-${index}`}>
142
+ <CapLabel className="webpush-template-cta-text">{cta.text || cta.actionText}</CapLabel>
143
+ </CapRow>
144
+ ))}
145
+ </CapRow>
146
+ )}
147
+ </CapRow>
148
+ </CapRow>
149
+ );
150
+ }
151
+
152
+ case VIBER: {
153
+ let content = {};
154
+ try {
155
+ const parsed = typeof templateData.messageBody === 'string'
156
+ ? JSON.parse(templateData.messageBody || '{}')
157
+ : (templateData.messageBody || {});
158
+ content = parsed.content || parsed;
159
+ } catch {
160
+ content = {};
161
+ }
162
+ const { text = '', image = {}, button = {}, video = {} } = content;
163
+ return (
164
+ <>
165
+ {text && <CapLabel type="label1">{text}</CapLabel>}
166
+ {image?.url && (
167
+ <CapImage src={image.url} className="viber-image" />
168
+ )}
169
+ {video?.url && (
170
+ <CapRow className="video-preview">
171
+ <CapImage src={video.thumbnailUrl || video.url} className="viber-image" />
172
+ <CapRow className="icon-position">
173
+ <CapImage className="video-icon" src={videoPlay} />
174
+ </CapRow>
175
+ </CapRow>
176
+ )}
177
+ {button?.text && (
178
+ <CapRow className="button-content viber-button-content">{button.text}</CapRow>
179
+ )}
180
+ </>
181
+ );
182
+ }
183
+
184
+ case WHATSAPP: {
185
+ const configs = templateData.templateConfigs || {};
186
+ const media = configs.whatsappMedia || {};
187
+ const mediaType = configs.mediaType || '';
188
+ const buttonType = configs.buttonType || '';
189
+ const buttons = configs.buttons || [];
190
+ const carouselData = configs.cards || [];
191
+ const bodyText = templateData.messageBody || '';
192
+ const header = media.header || '';
193
+ const footer = media.footer || '';
194
+ const imageUrl = mediaType === IMAGE ? media.url : '';
195
+ const videoPreviewImg = mediaType === VIDEO ? media.previewUrl : '';
196
+ const docPreview = mediaType === DOCUMENT && media.docParams
197
+ ? getWhatsappDocPreview({ ...media.docParams, whatsappDocImg: media.previewUrl || '' })
198
+ : null;
199
+ const hasMedia = imageUrl || videoPreviewImg || docPreview;
200
+ return (
201
+ <>
202
+ <CapRow className="whatsapp-container">
203
+ {imageUrl && <CapImage src={imageUrl} className="whatsapp-image" />}
204
+ {videoPreviewImg && (
205
+ <CapRow className="video-preview">
206
+ <CapImage src={videoPreviewImg} className="whatsapp-image" />
207
+ <CapRow className="icon-position">
208
+ <CapImage className="video-icon" src={videoPlay} />
209
+ </CapRow>
210
+ </CapRow>
211
+ )}
212
+ {docPreview && <CapRow className="whatsapp-doc">{docPreview}</CapRow>}
213
+ <CapRow className={hasMedia ? 'whatsapp-message-with-media' : 'whatsapp-message-without-media'}>
214
+ {header && <CapLabel className="whatsapp-template-list-header-preview">{header}</CapLabel>}
215
+ <CapLabel type="label9">{bodyText}</CapLabel>
216
+ {footer && <CapLabel className="whatsapp-template-list-footer-preview">{footer}</CapLabel>}
217
+ </CapRow>
218
+ {buttonType === CTA && buttons.map((cta) => cta.text && (
219
+ <CapLabel type="label21" className="whatsapp-cta-label" key={cta.text}>
220
+ <CapIcon
221
+ type={cta.type === PHONE_NUMBER ? 'call' : 'launch'}
222
+ size="xs"
223
+ svgProps={{ style: { marginRight: CAP_SPACE_04 } }}
224
+ />
225
+ {cta.text}
226
+ </CapLabel>
227
+ ))}
228
+ {getWhatsappQuickReply({ buttonType, buttons }, true)}
229
+ </CapRow>
230
+ {carouselData.length > 0 && (
231
+ <CapRow className="scroll-container">
232
+ {carouselData.map((data, index) => (
233
+ <CapRow key={`carousel-${index}`} className="whatsapp-carousel-container" role="group">
234
+ <CapRow className="whatsapp-carousel-card">
235
+ {data?.mediaType?.toUpperCase() === IMAGE && (
236
+ <CapImage src={data?.imageUrl} className="whatsapp-image" />
237
+ )}
238
+ {data?.mediaType?.toUpperCase() === VIDEO && (
239
+ <CapRow className="video-preview">
240
+ <CapImage src={data?.videoPreviewImg} className="whatsapp-image" />
241
+ <CapRow className="icon-position">
242
+ <CapImage className="video-icon" src={videoPlay} />
243
+ </CapRow>
244
+ </CapRow>
245
+ )}
246
+ <CapRow className={data?.imageUrl || data?.videoPreviewImg ? 'whatsapp-message-with-media' : 'whatsapp-message-without-media'}>
247
+ <CapLabel type="label9" className="whatsapp-carousel-body">{data?.bodyText}</CapLabel>
248
+ </CapRow>
249
+ {getWhatsappCarouselButtonView(data?.buttons, false)}
250
+ </CapRow>
251
+ </CapRow>
252
+ ))}
253
+ </CapRow>
254
+ )}
255
+ </>
256
+ );
257
+ }
258
+
259
+ case RCS: {
260
+ const cardContent = templateData.rcsContent?.cardContent?.[0] || {};
261
+ const media = cardContent.media || {};
262
+ const title = cardContent.title || '';
263
+ const description = cardContent.description || '';
264
+ const suggestions = cardContent.suggestions || [];
265
+ return (
266
+ <>
267
+ {media.mediaType === IMAGE && (
268
+ <CapImage src={media.mediaUrl} className="rcs-image" />
269
+ )}
270
+ {media.mediaType === VIDEO && (
271
+ <CapRow className="video-preview">
272
+ <CapImage src={media.thumbnailUrl || media.mediaUrl} className="rcs-image" />
273
+ <CapRow className="icon-position">
274
+ <CapImage className="video-icon" src={videoPlay} />
275
+ </CapRow>
276
+ </CapRow>
277
+ )}
278
+ {title && <CapLabel type="label4">{title}</CapLabel>}
279
+ {description && <CapLabel type="label1">{description}</CapLabel>}
280
+ {suggestions.length > 0 && (
281
+ <CapRow className="actions">
282
+ {suggestions.map((s) => (
283
+ <CapLabel className="action" key={s.label || s.text || s.postback?.data}>{s.label || s.text}</CapLabel>
284
+ ))}
285
+ </CapRow>
286
+ )}
287
+ </>
288
+ );
289
+ }
290
+
291
+ case ZALO: {
292
+ // templateData = { ...createPayload() } — name is nested under templateConfigs.name
293
+ const zaloName = templateData?.templateConfigs?.name || '';
294
+ return (
295
+ <CapLabel type="label19" className="zalo-listing-content desc">
296
+ {zaloName}
297
+ </CapLabel>
298
+ );
299
+ }
300
+
301
+ case LINE: {
302
+ let lineContent = {};
303
+ try {
304
+ lineContent = typeof templateData.messageBody === 'string'
305
+ ? JSON.parse(templateData.messageBody || '{}')
306
+ : (templateData.messageBody || {});
307
+ } catch {
308
+ lineContent = {};
309
+ }
310
+ const [firstMessage = {}] = lineContent.messages || [];
311
+ const { type: msgType = '' } = firstMessage;
312
+
313
+ if (msgType === 'flex') {
314
+ const flexContents = firstMessage.contents?.contents || [];
315
+ const heroUrl = flexContents[0]?.hero?.url || firstMessage.contents?.hero?.url || '';
316
+ return heroUrl ? <CapImage src={heroUrl} className="line-image" /> : null;
317
+ }
318
+
319
+ if (msgType === 'template') {
320
+ const columns = firstMessage.template?.columns || [];
321
+ return (
322
+ <CapRow>
323
+ {columns.map(({ imageUrl, action: { label: actionLabel } = {} }, idx) => (
324
+ <CapImage
325
+ key={`${imageUrl}_${idx}`}
326
+ src={imageUrl}
327
+ alt={actionLabel}
328
+ className="line-template-image"
329
+ />
330
+ ))}
331
+ </CapRow>
332
+ );
333
+ }
334
+
335
+ if (msgType === 'video') {
336
+ const videoUrl = firstMessage.video?.originalContentUrl || '';
337
+ return videoUrl ? (
338
+ <CapRow className="video-preview">
339
+ <video controls={false} tabIndex="0">
340
+ <source src={videoUrl} type="video/mp4" />
341
+ </video>
342
+ <CapRow className="icon-position">
343
+ <CapImage className="video-icon" src={videoPlay} />
344
+ </CapRow>
345
+ </CapRow>
346
+ ) : null;
347
+ }
348
+
349
+ // image / sticker / imagemap
350
+ const imageUrl = firstMessage.originalContentUrl
351
+ || firstMessage.animatedStickerUrl
352
+ || firstMessage.stickerUrl
353
+ || (firstMessage.baseUrl ? `${firstMessage.baseUrl}/1040` : '');
354
+ if (imageUrl) {
355
+ return <CapImage src={imageUrl} className="line-image" />;
356
+ }
357
+
358
+ // text
359
+ return firstMessage.text
360
+ ? <CapLabel type="label2">{firstMessage.text}</CapLabel>
361
+ : null;
362
+ }
363
+
364
+ default:
365
+ return <CapLabel type="label2">{templateData.messageBody || templateData.message || item.channel || ''}</CapLabel>;
366
+ }
367
+ };
368
+
369
+ export default getContentBody;
@@ -0,0 +1,19 @@
1
+ @import '~@capillarytech/cap-ui-library/styles/variables';
2
+
3
+ .viber-button-content {
4
+ margin-top: 0.5rem;
5
+ }
6
+
7
+ .whatsapp-cta-label {
8
+ text-align: center;
9
+ margin-top: 0.75rem;
10
+ }
11
+
12
+ .whatsapp-cta-icon {
13
+ margin-right: 0.25rem;
14
+ }
15
+
16
+ .line-template-image {
17
+ width: 13rem;
18
+ height: 17.4375rem;
19
+ }
@@ -23,7 +23,7 @@ export const getEnabledSteps = (config) => {
23
23
  if (features.deliverySettingsData?.required) {
24
24
  steps.push(STEPS.DELIVERY_SETTINGS);
25
25
  }
26
- if (features.dynamicControlsData?.required) {
26
+ if (features.showDynamicControls) {
27
27
  steps.push(STEPS.DYNAMIC_CONTROLS);
28
28
  }
29
29
  return steps;
@@ -43,6 +43,12 @@ export const LIQUID_SUPPORTED_CHANNELS = [EMAIL, SMS, MOBILE_PUSH, INAPP];
43
43
  export const EMF = "EMF";
44
44
  export const VENENO = "VENENO";
45
45
  export const TEXT = "TEXT";
46
+ export const IMAGE = "IMAGE";
47
+ export const VIDEO = "VIDEO";
48
+ export const DOCUMENT = "DOCUMENT";
49
+ export const CTA = "CTA";
50
+ export const QUICK_REPLY = "QUICK_REPLY";
51
+ export const BEE = "BEE";
46
52
  export const IOS = "IOS";
47
53
  export const ANDROID = "ANDROID";
48
54
  export const EXTERNAL_URL = "EXTERNAL_URL";