@capillarytech/creatives-library 9.0.54 → 9.0.55-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.
@@ -63,7 +63,6 @@ export const ENABLE_AI_SUGGESTIONS = 'ENABLE_AI_SUGGESTIONS';
63
63
  export const AI_CONTENT_BOT_DISABLED = 'AI_CONTENT_BOT_DISABLED';
64
64
  export const AI_DOCUMENTATION_BOT_ENABLED = 'AI_DOCUMENTATION_BOT_ENABLED';
65
65
  export const ENABLE_PRODUCT_SUPPORT_VIDEOS = 'ENABLE_PRODUCT_SUPPORT_VIDEOS';
66
- export const SUPPORT_ENGAGEMENT_MODULE = 'SUPPORT_ENGAGEMENT_MODULE';
67
66
  export const EMBEDDED = 'embedded';
68
67
  // --- Tag/Validation Constants ---
69
68
  export const CARD_RELATED_TAGS = [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.54",
4
+ "version": "9.0.55-alpha.1",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -701,6 +701,26 @@ export const createCentralCommsMetaId = (payload, metaType = TRANSACTION) => {
701
701
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
702
702
  };
703
703
 
704
+ // CCS CommDefinition API (new Veyron /api/v1/commdefinition surface, proxied
705
+ // through cap-creatives-api's /comm-definitions). Used by CommunicationFlow's
706
+ // own Save action — separate from the legacy messageMeta functions above,
707
+ // which remain in use by CreativesContainer's own save flow (Cap/sagas.js).
708
+ export const createCommDefinition = (payload) => {
709
+ const url = `${API_ENDPOINT}/comm-definitions`;
710
+ return request(url, getAPICallObject('POST', payload, false, false, false, true));
711
+ };
712
+
713
+ // Opens a new DRAFT version with the given content on an EXISTING
714
+ // CommDefinition (same id/referenceId) — used by CommunicationFlow's edit-mode
715
+ // save (re-editing a rejected alert) instead of createCommDefinition, so the
716
+ // alert keeps its identity rather than becoming a brand-new CommDefinition.
717
+ // Route verified against a real Postman sample against Veyron's own
718
+ // /commdefinition/{id}/edit endpoint.
719
+ export const editCommDefinition = (commDefinitionId, payload) => {
720
+ const url = `${API_ENDPOINT}/comm-definitions/${commDefinitionId}/edit`;
721
+ return request(url, getAPICallObject('POST', payload, false, false, false, true));
722
+ };
723
+
704
724
  export const getCentralCommsMetaIds = (metaIds, metaType = TRANSACTION) => {
705
725
  const url = `${API_ENDPOINT}/common/central-comms/meta-id/${metaType}?metaIds=${metaIds}`;
706
726
  return request(url, getAPICallObject('GET', null, false, false, false, true));
package/utils/common.js CHANGED
@@ -99,10 +99,6 @@ export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
99
99
  null,
100
100
  SUPPORT_CK_EDITOR,
101
101
  );
102
- export const hasSupportEngagementModule = Auth.hasFeatureAccess.bind(
103
- null,
104
- SUPPORT_ENGAGEMENT_MODULE,
105
- );
106
102
 
107
103
  export const hasGiftVoucherFeature = Auth.hasFeatureAccess.bind(
108
104
  null,
@@ -139,6 +139,26 @@ const UnifiedPreview = ({
139
139
  );
140
140
  }
141
141
 
142
+ case CHANNELS.LINE:
143
+ // LINE's real content today is a single text message (CCS's
144
+ // lineMessageContent.messageBody is a JSON string of LINE's native
145
+ // messages[] array — see testAndPreviewDataTransform.js's LINE case,
146
+ // which already resolves it down to plain text before it reaches
147
+ // here) — reuse the same simple chat-bubble preview as SMS. Rich LINE
148
+ // message types (image/video/sticker/imageMap/carousel/flex) are NOT
149
+ // rendered specially yet; they'll show as empty/raw text until this
150
+ // gets richer LINE-specific handling.
151
+ return (
152
+ <SmsPreviewContent
153
+ content={typeof content === 'string' ? content : (content?.resolvedBody || '')}
154
+ device={device}
155
+ isUpdating={isUpdating}
156
+ error={error}
157
+ formatMessage={formatMessage}
158
+ showHeader={showHeader}
159
+ />
160
+ );
161
+
142
162
  case CHANNELS.WHATSAPP: {
143
163
  // WhatsApp content is an object with templateMsg, media, CTA, etc.
144
164
  const whatsappContent = typeof content === 'object' ? content : {};
@@ -85,6 +85,7 @@ export const CHANNELS = {
85
85
  VIBER: 'VIBER',
86
86
  ZALO: 'ZALO',
87
87
  WEBPUSH: 'WEBPUSH',
88
+ LINE: 'LINE',
88
89
  };
89
90
 
90
91
  /** Unified preview tab keys when RCS shows RCS + SMS fallback panes. */
@@ -100,6 +101,7 @@ export const CHANNELS_USING_ANDROID_PREVIEW_DEVICE = [
100
101
  CHANNELS.INAPP,
101
102
  CHANNELS.MOBILEPUSH,
102
103
  CHANNELS.VIBER,
104
+ CHANNELS.LINE,
103
105
  ];
104
106
 
105
107
  /** RCS createMessageMeta test payload — rich card envelope literals. */
@@ -3813,7 +3813,7 @@ const CommonTestAndPreview = (props) => {
3813
3813
  header={slideboxHeader}
3814
3814
  handleClose={handleClose}
3815
3815
  show={show}
3816
- size="size-xl"
3816
+ size="size-l"
3817
3817
  content={(
3818
3818
  <CapSpin
3819
3819
  spinning={isCustomerDataLoading}
@@ -479,6 +479,112 @@ describe('UnifiedPreview', () => {
479
479
  });
480
480
  });
481
481
 
482
+ describe('Channel Routing - LINE', () => {
483
+ it('should render SmsPreviewContent for LINE channel with string content', () => {
484
+ const props = {
485
+ ...defaultProps,
486
+ channel: CHANNELS.LINE,
487
+ content: 'Hello world',
488
+ };
489
+
490
+ render(
491
+ <TestWrapper>
492
+ <ComponentToRender {...props} />
493
+ </TestWrapper>
494
+ );
495
+
496
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
497
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('Hello world');
498
+ });
499
+
500
+ it('should handle object content with resolvedBody for LINE channel', () => {
501
+ const props = {
502
+ ...defaultProps,
503
+ channel: CHANNELS.LINE,
504
+ content: {
505
+ resolvedBody: 'some text',
506
+ },
507
+ };
508
+
509
+ render(
510
+ <TestWrapper>
511
+ <ComponentToRender {...props} />
512
+ </TestWrapper>
513
+ );
514
+
515
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
516
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('some text');
517
+ });
518
+
519
+ it('should not crash and render empty content for LINE channel when content is undefined', () => {
520
+ const props = {
521
+ ...defaultProps,
522
+ channel: CHANNELS.LINE,
523
+ content: undefined,
524
+ };
525
+
526
+ render(
527
+ <TestWrapper>
528
+ <ComponentToRender {...props} />
529
+ </TestWrapper>
530
+ );
531
+
532
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
533
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('');
534
+ });
535
+
536
+ it('should not crash and render empty content for LINE channel when content is null', () => {
537
+ const props = {
538
+ ...defaultProps,
539
+ channel: CHANNELS.LINE,
540
+ content: null,
541
+ };
542
+
543
+ render(
544
+ <TestWrapper>
545
+ <ComponentToRender {...props} />
546
+ </TestWrapper>
547
+ );
548
+
549
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
550
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('');
551
+ });
552
+
553
+ it('should render empty content for LINE channel when content is not a string and has no resolvedBody', () => {
554
+ const props = {
555
+ ...defaultProps,
556
+ channel: CHANNELS.LINE,
557
+ content: { someOtherField: 'value' },
558
+ };
559
+
560
+ render(
561
+ <TestWrapper>
562
+ <ComponentToRender {...props} />
563
+ </TestWrapper>
564
+ );
565
+
566
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
567
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('');
568
+ });
569
+
570
+ it('should not render the Unknown Channel fallback for LINE channel', () => {
571
+ const props = {
572
+ ...defaultProps,
573
+ channel: CHANNELS.LINE,
574
+ content: 'Hello world',
575
+ };
576
+
577
+ render(
578
+ <TestWrapper>
579
+ <ComponentToRender {...props} />
580
+ </TestWrapper>
581
+ );
582
+
583
+ expect(screen.queryByText('Unknown Channel')).toBeNull();
584
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
585
+ });
586
+ });
587
+
482
588
  describe('Channel Routing - WHATSAPP', () => {
483
589
  it('should render WhatsAppPreviewContent for WHATSAPP channel', () => {
484
590
  const props = {
@@ -42,6 +42,7 @@ import {
42
42
  CLEAR_PREVIEW_ERRORS,
43
43
  // Channel Constants
44
44
  CHANNELS,
45
+ CHANNELS_USING_ANDROID_PREVIEW_DEVICE,
45
46
  // Device Constants
46
47
  DESKTOP,
47
48
  MOBILE,
@@ -180,15 +181,22 @@ describe('CommonTestAndPreview Constants', () => {
180
181
  expect(CHANNELS.VIBER).toBe('VIBER');
181
182
  expect(CHANNELS.ZALO).toBe('ZALO');
182
183
  expect(CHANNELS.WEBPUSH).toBe('WEBPUSH');
184
+ expect(CHANNELS.LINE).toBe('LINE');
183
185
  });
184
186
 
185
187
  it('should have all required channel keys', () => {
186
- const expectedChannels = ['EMAIL', 'SMS', 'RCS', 'WHATSAPP', 'INAPP', 'MOBILEPUSH', 'VIBER', 'ZALO', 'WEBPUSH'];
188
+ const expectedChannels = ['EMAIL', 'SMS', 'RCS', 'WHATSAPP', 'INAPP', 'MOBILEPUSH', 'VIBER', 'ZALO', 'WEBPUSH', 'LINE'];
187
189
  expect(Object.keys(CHANNELS)).toEqual(expect.arrayContaining(expectedChannels));
188
190
  expect(Object.keys(CHANNELS).length).toBe(expectedChannels.length);
189
191
  });
190
192
  });
191
193
 
194
+ describe('CHANNELS_USING_ANDROID_PREVIEW_DEVICE', () => {
195
+ it('should include CHANNELS.LINE', () => {
196
+ expect(CHANNELS_USING_ANDROID_PREVIEW_DEVICE).toContain(CHANNELS.LINE);
197
+ });
198
+ });
199
+
192
200
  describe('Device Constants', () => {
193
201
  it('should export all device constants with correct values', () => {
194
202
  expect(DESKTOP).toBe('desktop');
@@ -17,10 +17,11 @@ import { createStructuredSelector } from 'reselect';
17
17
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
18
18
  import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
19
19
  import CapButton from '@capillarytech/cap-ui-library/CapButton';
20
+ import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
20
21
  // import injectSaga from '../../utils/injectSaga'; // cap-coupons flows disabled
21
22
  // import injectReducer from '../../utils/injectReducer';
22
23
  import { makeSelectAuthenticated } from '../Cap/selectors';
23
- import { createCentralCommsMetaId, getCentralCommsMetaIds } from '../../services/api';
24
+ import { createCommDefinition, editCommDefinition } from '../../services/api';
24
25
  import DynamicControlsStep from './steps/DynamicControlsStep';
25
26
  import MessageTypeStep from './steps/MessageTypeStep';
26
27
  import CommunicationStrategyStep from './steps/CommunicationStrategyStep';
@@ -35,9 +36,11 @@ import {
35
36
  CHANNELS,
36
37
  INCENTIVE_TYPES,
37
38
  DYNAMIC_CONTROLS_CONFIG,
38
- CHANNEL_CONTENT_KEY_MAP,
39
- CHANNEL_DELIVERY_KEY_MAP,
40
- CAMPAIGNS,
39
+ CCS_STRATEGY_TYPE_SINGLE,
40
+ CCS_CHANNEL_CONTENT_KEY_MAP,
41
+ CCS_CHANNEL_DELIVERY_KEY_MAP,
42
+ CCS_CHANNEL_NAME_MAP,
43
+ CCS_CONTENT_TRANSFORMS,
41
44
  } from './constants';
42
45
  import { getEnabledSteps } from './utils/getEnabledSteps';
43
46
  import messages from './messages';
@@ -64,6 +67,25 @@ const hasDeliverySettingForChannel = (channelSetting, channel) => {
64
67
  return !!settings && Object.values(settings).some((v) => v !== null && v !== '' && v !== undefined);
65
68
  };
66
69
 
70
+ // createCommDefinition resolves (rather than rejects) with the CCS error envelope
71
+ // for 4xx/5xx responses — see api.js's request()/checkStatus. A duplicate
72
+ // referenceId within the org comes back as this specific 409.
73
+ const isDuplicateReferenceIdError = (res) => res?.success === false && res?.status?.message === 'REFERENCE_ID_EXISTS';
74
+
75
+ /**
76
+ * CCS requires referenceId on create; the consumer-supplied Alert/comm name is
77
+ * mandatory but referenceId is optional in every consumer's own form. Generate
78
+ * a stable fallback from the name rather than failing the save.
79
+ */
80
+ const buildCcsReferenceId = (name) => {
81
+ const slug = (name || 'COMM')
82
+ .trim()
83
+ .toUpperCase()
84
+ .replace(/[^A-Z0-9]+/g, '_')
85
+ .replace(/^_+|_+$/g, '') || 'COMM';
86
+ return `${slug}_${Date.now()}`;
87
+ };
88
+
67
89
  const CommunicationFlow = ({
68
90
  config,
69
91
  initialData,
@@ -91,6 +113,7 @@ const CommunicationFlow = ({
91
113
  };
92
114
  });
93
115
  const [validationErrors, setValidationErrors] = useState({});
116
+ const [saveError, setSaveError] = useState(null);
94
117
 
95
118
  // Memoize enabled steps
96
119
  const enabledSteps = useMemo(() => getEnabledSteps(config), [config]);
@@ -150,74 +173,108 @@ const CommunicationFlow = ({
150
173
  }, []);
151
174
 
152
175
  const handleSave = useCallback(async () => {
176
+ setSaveError(null);
153
177
  const aggregatedData = getAggregatedData();
154
178
  const shouldUseCCS = config?.useCCS !== false;
179
+ let ccsCommDefinition = null;
155
180
 
156
181
  if (shouldUseCCS) {
157
- const ouId = config?.context?.ouId || -1;
158
- const module = config?.context?.module
159
- || (config?.consumer ? config.consumer.toUpperCase() : CAMPAIGNS);
182
+ const isMultiChannel = [CHANNEL_PRIORITY, AB_TEST].includes(aggregatedData.communicationStrategy);
183
+ const contentItem = (aggregatedData.contentItems || [])[0];
184
+ // Consumer-supplied name (e.g. CapNotify's Alert Name field) CommunicationFlow
185
+ // has no name input of its own, so this is mandatory input from config.context.
186
+ const name = config?.context?.name;
160
187
 
161
- const channelContentKeyMap = CHANNEL_CONTENT_KEY_MAP;
162
- const channelDeliveryKeyMap = CHANNEL_DELIVERY_KEY_MAP;
188
+ // CCS create is SINGLE-strategy only this phase (D1); CHANNEL_PRIORITY/AB_TEST
189
+ // carry multiple content items with no CCS equivalent yet.
190
+ if (!isMultiChannel && contentItem && name) {
191
+ const rawChannel = (contentItem.channel || '').toUpperCase();
192
+ const channel = CCS_CHANNEL_NAME_MAP[rawChannel] || rawChannel;
193
+ const contentKey = CCS_CHANNEL_CONTENT_KEY_MAP[channel];
194
+ const deliveryKey = CCS_CHANNEL_DELIVERY_KEY_MAP[channel];
195
+ const contentTransform = CCS_CONTENT_TRANSFORMS[channel];
196
+ const contentPayload = contentTransform ? contentTransform(contentItem.templateData) : contentItem.templateData;
197
+ const { dynamicControls = {} } = aggregatedData;
198
+ const channelSettings = aggregatedData.deliverySetting?.channelSetting?.[channel] || {};
199
+ const referenceId = config?.context?.referenceId || buildCcsReferenceId(name);
163
200
 
164
- const contentItems = aggregatedData.contentItems || [];
165
- const { dynamicControls = {} } = aggregatedData;
201
+ const payload = {
202
+ referenceId,
203
+ name,
204
+ description: config?.context?.description || undefined,
205
+ strategyType: CCS_STRATEGY_TYPE_SINGLE,
206
+ settings: {
207
+ additionalSettings: {
208
+ useTinyUrl: dynamicControls.useTinyUrl ?? false,
209
+ encryptUrl: dynamicControls.sendToControlCustomers ?? false,
210
+ linkTrackingEnabled: dynamicControls.overrideDailyLimit ?? false,
211
+ userSubscriptionDisabled: dynamicControls.sendToBrandPocs ?? false,
212
+ },
213
+ executionParams: {},
214
+ },
215
+ singleChannelStrategy: {
216
+ variant: {
217
+ channel,
218
+ // `channel` spreads LAST in both objects below — contentItem.templateData
219
+ // (e.g. mobile push) carries the UI's own internal channel identifier
220
+ // (MOBILEPUSH), which must never win over CCS's normalized enum value
221
+ // (PUSH) once spread order puts it after.
222
+ ...(contentKey && { [contentKey]: { ...contentPayload, channel } }),
223
+ ...(deliveryKey && { [deliveryKey]: { channelSettings: { ...channelSettings, channel } } }),
224
+ },
225
+ },
226
+ };
166
227
 
167
- const additionalSettings = {
168
- useTinyUrl: dynamicControls.useTinyUrl ?? false,
169
- encryptUrl: dynamicControls.sendToControlCustomers ?? false,
170
- linkTrackingEnabled: dynamicControls.overrideDailyLimit ?? false,
171
- userSubscriptionDisabled: dynamicControls.sendToBrandPocs ?? false,
172
- };
228
+ // Editing an existing (e.g. rejected) alert: config.context.existingCommDefinitionId
229
+ // opens a new DRAFT version on that SAME CommDefinition instead of creating a
230
+ // brand-new one, so the alert keeps its id/referenceId across the edit.
231
+ const { existingCommDefinitionId } = config?.context || {};
173
232
 
174
- if (contentItems.length > 0) {
175
233
  try {
176
- const responses = await Promise.all(
177
- contentItems.map((item) => {
178
- const channel = (item.channel || '').toUpperCase();
179
- const contentKey = channelContentKeyMap[channel];
180
- const deliveryKey = channelDeliveryKeyMap[channel];
181
- const payload = {
182
- centralCommsPayload: {
183
- ouId,
184
- channel,
185
- module,
186
- executionParams: {},
187
- clientName: 'EMF',
188
- ...(contentKey && {
189
- [contentKey]: { channel, ...item.templateData },
190
- }),
191
- ...(deliveryKey && {
192
- [deliveryKey]: {
193
- additionalSettings,
194
- channelSettings: {
195
- channel,
196
- ...(aggregatedData.deliverySetting?.channelSetting?.[channel] || {}),
197
- },
198
- },
199
- }),
200
- },
234
+ const res = existingCommDefinitionId
235
+ ? await editCommDefinition(existingCommDefinitionId, {
236
+ strategyType: payload.strategyType,
237
+ settings: payload.settings,
238
+ singleChannelStrategy: payload.singleChannelStrategy,
239
+ })
240
+ : await createCommDefinition(payload);
241
+ if (isDuplicateReferenceIdError(res)) {
242
+ // Duplicate referenceId in this org — block the save so the user can
243
+ // change it, rather than silently proceeding without a CCS comm.
244
+ setSaveError(formatMessage(messages.duplicateReferenceIdError));
245
+ return;
246
+ }
247
+ const data = res?.response?.data;
248
+ if (existingCommDefinitionId) {
249
+ if (data) {
250
+ ccsCommDefinition = {
251
+ id: existingCommDefinitionId,
252
+ referenceId: referenceId || data.referenceId,
253
+ version: data.version?.version ?? data.version ?? 1,
254
+ status: data.status,
201
255
  };
202
- return createCentralCommsMetaId(payload);
203
- }),
204
- );
205
-
206
- const metaIds = responses
207
- .map((res) => res?.response?.data?.id)
208
- .filter(Boolean)
209
- .join(',');
210
-
211
- if (metaIds) {
212
- const getResponse = await getCentralCommsMetaIds(metaIds);
256
+ }
257
+ } else if (data?.id) {
258
+ ccsCommDefinition = {
259
+ id: data.id,
260
+ referenceId: data.referenceId,
261
+ version: data.version?.version ?? 1,
262
+ status: data.status,
263
+ };
213
264
  }
214
265
  } catch (error) {
215
- console.error('[CommunicationFlow] CCS createCentralCommsMetaId error:', error);
266
+ console.error('[CommunicationFlow] CCS createCommDefinition error:', error);
216
267
  }
268
+ } else if (!name) {
269
+ console.warn('[CommunicationFlow] Skipping CCS createCommDefinition — config.context.name is required');
270
+ } else if (isMultiChannel) {
271
+ console.warn('[CommunicationFlow] Skipping CCS createCommDefinition — CHANNEL_PRIORITY/AB_TEST strategies are not supported by CCS create yet (SINGLE only)');
272
+ } else if (!contentItem) {
273
+ console.warn('[CommunicationFlow] Skipping CCS createCommDefinition — no content item found (contentItems is empty)');
217
274
  }
218
275
  }
219
276
 
220
- onSave(aggregatedData);
277
+ onSave(ccsCommDefinition ? { ...aggregatedData, ccsCommDefinition } : aggregatedData);
221
278
  }, [getAggregatedData, config, onSave]);
222
279
 
223
280
  // Call onChange callback when stepData changes
@@ -317,6 +374,11 @@ const CommunicationFlow = ({
317
374
  {renderSteps()}
318
375
  {onSave && (
319
376
  <CapRow useLegacy className="communication-flow-container__footer">
377
+ {saveError && (
378
+ <CapLabel type="label2" className="communication-flow-container__save-error">
379
+ {saveError}
380
+ </CapLabel>
381
+ )}
320
382
  <CapButton type="primary" onClick={handleSave} disabled={isSaveDisabled}>
321
383
  {formatMessage(messages.save)}
322
384
  </CapButton>
@@ -137,7 +137,7 @@
137
137
  }
138
138
 
139
139
  &__footer {
140
- padding: $CAP_SPACE_16 0 $CAP_SPACE_08;
140
+ padding: 4.142rem 0 $CAP_SPACE_08;
141
141
  }
142
142
  }
143
143
 
@@ -19,6 +19,7 @@ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
19
19
  import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
20
20
  import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
21
21
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
22
+ import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
22
23
  import CommunicationFlow from './index';
23
24
  import { CHANNELS, DEFAULT_COMMUNICATION_STRATEGY_OPTIONS, DYNAMIC_CONTROLS_CONFIG } from './constants';
24
25
  import {
@@ -41,7 +42,7 @@ const resolveValue = (val) => {
41
42
 
42
43
  const SENDER_ID_RESOLVERS = {
43
44
  [SMS]: (setting) => resolveValue(setting?.gsmSenderId),
44
- [EMAIL]: (setting) => resolveValue(setting?.senderEmail),
45
+ [EMAIL]: (setting) => resolveValue(setting?.senderId),
45
46
  [VIBER]: (setting) => resolveValue(setting?.sender) || resolveValue(setting?.gsmSenderId),
46
47
  [WHATSAPP]: (setting) => resolveValue(setting?.senderMobNum),
47
48
  [RCS]: (setting) => resolveValue(setting?.senderMobNum) || resolveValue(setting?.rcsSender),
@@ -74,6 +75,8 @@ const CommunicationFlowCard = ({
74
75
  onChange,
75
76
  cap,
76
77
  intl,
78
+ disabled,
79
+ disabledTooltip,
77
80
  }) => {
78
81
  const { formatMessage } = intl || {};
79
82
  const [showSlideBox, setShowSlideBox] = useState(false);
@@ -91,8 +94,9 @@ const CommunicationFlowCard = ({
91
94
  }, [onCancel]);
92
95
 
93
96
  const handleOpen = useCallback(() => {
97
+ if (disabled) return;
94
98
  setShowSlideBox(true);
95
- }, []);
99
+ }, [disabled]);
96
100
 
97
101
  const firstItem = savedData?.contentItems?.[0];
98
102
  const channelConfig = firstItem
@@ -182,9 +186,17 @@ const CommunicationFlowCard = ({
182
186
  <CapImage src={addCreativesIllustration} />
183
187
  </CapColumn>
184
188
  <CapColumn span={14} className="empty-card-action-col">
185
- <CapButton type="secondary" onClick={handleOpen}>
186
- {formatMessage(messages.addCreatives)}
187
- </CapButton>
189
+ {disabled && disabledTooltip ? (
190
+ <CapTooltip title={disabledTooltip}>
191
+ <CapButton type="secondary" disabled>
192
+ {formatMessage(messages.addCreatives)}
193
+ </CapButton>
194
+ </CapTooltip>
195
+ ) : (
196
+ <CapButton type="secondary" onClick={handleOpen} disabled={disabled}>
197
+ {formatMessage(messages.addCreatives)}
198
+ </CapButton>
199
+ )}
188
200
  </CapColumn>
189
201
  </CapRow>
190
202
  </CapCard>
@@ -196,7 +208,7 @@ const CommunicationFlowCard = ({
196
208
  <CapSlideBox
197
209
  show={showSlideBox}
198
210
  handleClose={handleClose}
199
- size="size-xl"
211
+ size="size-l"
200
212
  header={(
201
213
  <CapHeader
202
214
  title={formatMessage(messages.addMessage)}
@@ -227,6 +239,8 @@ CommunicationFlowCard.propTypes = {
227
239
  onChange: PropTypes.func,
228
240
  cap: PropTypes.object,
229
241
  intl: PropTypes.object.isRequired,
242
+ disabled: PropTypes.bool, // Disables the "Add creatives" trigger (e.g. until a consumer-required field is filled)
243
+ disabledTooltip: PropTypes.node, // Shown on hover when disabled is true
230
244
  };
231
245
 
232
246
  CommunicationFlowCard.defaultProps = {
@@ -235,6 +249,8 @@ CommunicationFlowCard.defaultProps = {
235
249
  onCancel: null,
236
250
  onChange: null,
237
251
  cap: null,
252
+ disabled: false,
253
+ disabledTooltip: null,
238
254
  };
239
255
 
240
256
  export default injectIntl(CommunicationFlowCard);