@capillarytech/creatives-library 9.0.56-betaa.0 → 9.0.56
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 +9 -4
- package/package.json +1 -1
- package/services/api.js +25 -4
- package/utils/common.js +0 -12
- package/utils/templateVarUtils.js +16 -0
- package/utils/tests/templateVarUtils.test.js +44 -0
- package/v2Components/CapActionButton/index.js +73 -60
- package/v2Components/CapActionButton/index.scss +44 -28
- package/v2Components/CapActionButton/messages.js +7 -3
- package/v2Components/CapActionButton/tests/index.test.js +17 -1
- package/v2Components/CapWhatsappCTA/messages.js +4 -0
- package/v2Components/CapWhatsappCarouselButton/index.js +42 -33
- package/v2Components/CapWhatsappCarouselButton/index.scss +44 -2
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
- package/v2Components/CommonTestAndPreview/actions.js +13 -0
- package/v2Components/CommonTestAndPreview/constants.js +8 -0
- package/v2Components/CommonTestAndPreview/index.js +55 -3
- package/v2Components/CommonTestAndPreview/reducer.js +20 -0
- package/v2Components/CommonTestAndPreview/sagas.js +28 -0
- package/v2Components/CommonTestAndPreview/selectors.js +6 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
- package/v2Components/CommonTestAndPreview/tests/actions.test.js +15 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +101 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +41 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +84 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +12 -0
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +46 -320
- package/v2Components/FormBuilder/Functional/channels/registry.js +0 -2
- package/v2Components/FormBuilder/Functional/constants.js +8 -43
- package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +28 -175
- package/v2Components/FormBuilder/Functional/core/store/formReducer.js +6 -75
- package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +20 -10
- package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +1 -9
- package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +3 -20
- package/v2Components/FormBuilder/Functional/layout/Section.js +1 -6
- package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +2 -9
- package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +0 -17
- package/v2Components/FormBuilder/index.js +12 -34
- package/v2Components/FormBuilder/tests/entryGate.test.js +7 -94
- package/v2Components/TestAndPreviewSlidebox/index.js +9 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +140 -75
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +18 -2
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +67 -28
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +503 -51
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +98 -4
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
- package/v2Containers/CommunicationFlow/constants.js +65 -4
- package/v2Containers/CommunicationFlow/index.js +16 -23
- package/v2Containers/CommunicationFlow/messages.js +11 -3
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +27 -11
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +4 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +176 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +22 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +14 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +11 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +51 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +19 -5
- package/v2Containers/CreativesContainer/SlideBoxContent.js +15 -0
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
- package/v2Containers/CreativesContainer/index.js +29 -3
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +29 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +18 -5
- package/v2Containers/CreativesContainer/tests/index.test.js +12 -0
- package/v2Containers/MobilePush/Create/index.js +18 -1
- package/v2Containers/MobilePush/Create/test/contentValidity.test.js +96 -0
- package/v2Containers/MobilePush/Edit/index.js +18 -1
- package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +116 -0
- package/v2Containers/MobilePush/commonMethods.js +49 -1
- package/v2Containers/MobilePushNew/index.js +29 -4
- package/v2Containers/MobilePushNew/tests/index.test.js +119 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +82 -0
- package/v2Containers/MobilePushNew/utils.js +34 -1
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +34 -0
- package/v2Containers/Rcs/index.scss +15 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +115 -19
- package/v2Containers/Rcs/tests/index.test.js +67 -0
- package/v2Containers/Sms/Create/index.js +3 -1
- package/v2Containers/Sms/Edit/index.js +25 -0
- package/v2Containers/Sms/Edit/tests/index.test.js +85 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +27 -3
- package/v2Containers/Viber/index.js +24 -1
- package/v2Containers/Viber/tests/index.test.js +103 -0
- package/v2Containers/WebPush/Create/index.js +19 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +17 -12
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +5 -0
- package/v2Containers/WebPush/Create/tests/contentValidity.test.js +294 -0
- package/v2Containers/Whatsapp/index.js +25 -4
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +306 -34
- package/v2Containers/Whatsapp/tests/index.test.js +115 -0
- package/v2Containers/Zalo/index.js +28 -1
- package/v2Containers/Zalo/tests/index.test.js +99 -0
- package/v2Containers/mockdata.js +25 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/buildSubmitPayload.js +0 -10
- package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +0 -82
- package/v2Components/FormBuilder/Functional/channels/mobilepush/getEditorErrorDescriptor.js +0 -74
- package/v2Components/FormBuilder/Functional/channels/mobilepush/index.js +0 -28
- package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +0 -19
- package/v2Components/FormBuilder/Functional/channels/mobilepush/runSubmitPipeline.js +0 -45
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/getEditorErrorDescriptor.test.js +0 -162
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/modals.test.js +0 -37
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/runSubmitPipeline.test.js +0 -70
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/validate.test.js +0 -274
- package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +0 -251
- package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +0 -89
- package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +0 -440
- package/v2Components/FormBuilder/Functional/tests/mpush.crossFlowParity.test.js +0 -430
- package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +0 -282
- package/v2Components/FormBuilder/Functional/tests/mpush.editContainer.parity.test.js +0 -141
- package/v2Components/FormBuilder/Functional/tests/mpush.engine.test.js +0 -306
- package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +0 -151
- package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +0 -342
- package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +0 -110
- package/v2Components/FormBuilder/tests/mpush.characterization.test.js +0 -459
|
@@ -108,6 +108,22 @@ describe('CommunicationFlowCard', () => {
|
|
|
108
108
|
fireEvent.click(screen.getByText('Add creatives'));
|
|
109
109
|
expect(screen.getByTestId('comm-flow-mock')).toHaveAttribute('data-mode', 'create');
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
it('disables the Add creatives button when disabled prop is true', () => {
|
|
113
|
+
renderCard({ disabled: true, disabledTooltip: 'Enter an Alert name to add content' });
|
|
114
|
+
expect(screen.getByText('Add creatives').closest('button')).toBeDisabled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('does not open SlideBox when Add creatives is clicked while disabled', () => {
|
|
118
|
+
renderCard({ disabled: true, disabledTooltip: 'Enter an Alert name to add content' });
|
|
119
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
120
|
+
expect(screen.queryByTestId('slide-box')).not.toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('Add creatives button is enabled by default (disabled prop absent)', () => {
|
|
124
|
+
renderCard();
|
|
125
|
+
expect(screen.getByText('Add creatives').closest('button')).not.toBeDisabled();
|
|
126
|
+
});
|
|
111
127
|
});
|
|
112
128
|
|
|
113
129
|
describe('configured state (initialData provided)', () => {
|
|
@@ -186,6 +202,26 @@ describe('CommunicationFlowCard', () => {
|
|
|
186
202
|
renderCard({ initialData: makeSavedData() });
|
|
187
203
|
expect(screen.getByText('Other controls')).toBeInTheDocument();
|
|
188
204
|
});
|
|
205
|
+
|
|
206
|
+
it('hides the Other controls column when config.features.showDynamicControls is false', () => {
|
|
207
|
+
renderCard({
|
|
208
|
+
config: { ...baseConfig, features: { showDynamicControls: false } },
|
|
209
|
+
initialData: makeSavedData({
|
|
210
|
+
dynamicControls: {
|
|
211
|
+
sendToControlCustomers: false, sendToBrandPocs: false, useTinyUrl: false, overrideDailyLimit: false,
|
|
212
|
+
},
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
215
|
+
expect(screen.queryByText('Other controls')).not.toBeInTheDocument();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('still shows the Other controls column when showDynamicControls is unset (Engagement Module default)', () => {
|
|
219
|
+
renderCard({
|
|
220
|
+
config: { ...baseConfig, features: {} },
|
|
221
|
+
initialData: makeSavedData(),
|
|
222
|
+
});
|
|
223
|
+
expect(screen.getByText('Other controls')).toBeInTheDocument();
|
|
224
|
+
});
|
|
189
225
|
});
|
|
190
226
|
|
|
191
227
|
describe('channel config resolution', () => {
|
|
@@ -231,11 +267,11 @@ describe('CommunicationFlowCard', () => {
|
|
|
231
267
|
expect(screen.getByText(/Sender ID/)).toBeInTheDocument();
|
|
232
268
|
});
|
|
233
269
|
|
|
234
|
-
it('shows
|
|
270
|
+
it('shows senderId for EMAIL with Sender ID label', () => {
|
|
235
271
|
renderCard({
|
|
236
272
|
initialData: makeSavedData({
|
|
237
273
|
contentItems: [{ channel: 'EMAIL', templateData: {} }],
|
|
238
|
-
deliverySetting: { channelSetting: { EMAIL: {
|
|
274
|
+
deliverySetting: { channelSetting: { EMAIL: { senderId: 'test@example.com' } } },
|
|
239
275
|
}),
|
|
240
276
|
});
|
|
241
277
|
expect(screen.getByText(/test@example.com/)).toBeInTheDocument();
|
|
@@ -545,10 +581,10 @@ describe('CommunicationFlowCard', () => {
|
|
|
545
581
|
});
|
|
546
582
|
|
|
547
583
|
describe('SlideBox header', () => {
|
|
548
|
-
it('renders Add
|
|
584
|
+
it('renders Add content title in the slidebox header', () => {
|
|
549
585
|
renderCard();
|
|
550
586
|
fireEvent.click(screen.getByText('Add creatives'));
|
|
551
|
-
expect(screen.getByText('Add
|
|
587
|
+
expect(screen.getByText('Add content')).toBeInTheDocument();
|
|
552
588
|
});
|
|
553
589
|
});
|
|
554
590
|
|
|
@@ -616,4 +652,62 @@ describe('CommunicationFlowCard', () => {
|
|
|
616
652
|
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
617
653
|
});
|
|
618
654
|
});
|
|
655
|
+
|
|
656
|
+
describe('async initialData (edit page hard reload)', () => {
|
|
657
|
+
// On a hard reload of an edit page, the consumer's own by-id fetch hasn't
|
|
658
|
+
// resolved at first mount, so initialData starts as null and arrives a
|
|
659
|
+
// render or two later. savedData must pick it up then, not just at mount.
|
|
660
|
+
it('shows the summary card once initialData arrives after mount, instead of staying stuck on the empty "Add creatives" state', () => {
|
|
661
|
+
const { rerender } = render(
|
|
662
|
+
<IntlProvider locale="en" messages={{}} defaultLocale="en">
|
|
663
|
+
<CommunicationFlowCard
|
|
664
|
+
config={baseConfig}
|
|
665
|
+
initialData={null}
|
|
666
|
+
onSave={jest.fn()}
|
|
667
|
+
onCancel={jest.fn()}
|
|
668
|
+
onChange={jest.fn()}
|
|
669
|
+
cap={{}}
|
|
670
|
+
/>
|
|
671
|
+
</IntlProvider>,
|
|
672
|
+
);
|
|
673
|
+
expect(screen.getByText('Add creatives')).toBeInTheDocument();
|
|
674
|
+
|
|
675
|
+
rerender(
|
|
676
|
+
<IntlProvider locale="en" messages={{}} defaultLocale="en">
|
|
677
|
+
<CommunicationFlowCard
|
|
678
|
+
config={baseConfig}
|
|
679
|
+
initialData={makeSavedData()}
|
|
680
|
+
onSave={jest.fn()}
|
|
681
|
+
onCancel={jest.fn()}
|
|
682
|
+
onChange={jest.fn()}
|
|
683
|
+
cap={{}}
|
|
684
|
+
/>
|
|
685
|
+
</IntlProvider>,
|
|
686
|
+
);
|
|
687
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
688
|
+
expect(screen.queryByText('Add creatives')).not.toBeInTheDocument();
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
it('does not clobber content the user already saved locally when a later render passes a different initialData', () => {
|
|
692
|
+
const { rerender } = renderCard({ initialData: null });
|
|
693
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
694
|
+
fireEvent.click(screen.getByTestId('flow-save'));
|
|
695
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
696
|
+
|
|
697
|
+
rerender(
|
|
698
|
+
<IntlProvider locale="en" messages={{}} defaultLocale="en">
|
|
699
|
+
<CommunicationFlowCard
|
|
700
|
+
config={baseConfig}
|
|
701
|
+
initialData={makeSavedData({ contentItems: [{ channel: 'EMAIL', templateData: {} }] })}
|
|
702
|
+
onSave={jest.fn()}
|
|
703
|
+
onCancel={jest.fn()}
|
|
704
|
+
onChange={jest.fn()}
|
|
705
|
+
cap={{}}
|
|
706
|
+
/>
|
|
707
|
+
</IntlProvider>,
|
|
708
|
+
);
|
|
709
|
+
// Locally-saved SMS content must survive; the later initialData prop must not overwrite it.
|
|
710
|
+
expect(screen.getByText('SMS')).toBeInTheDocument();
|
|
711
|
+
});
|
|
712
|
+
});
|
|
619
713
|
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CCS_CONTENT_TRANSFORMS,
|
|
3
|
+
CCS_CHANNEL_NAME_MAP,
|
|
4
|
+
CCS_CHANNEL_CONTENT_KEY_MAP,
|
|
5
|
+
CCS_CHANNEL_DELIVERY_KEY_MAP,
|
|
6
|
+
} from '../constants';
|
|
7
|
+
|
|
8
|
+
describe('CCS_CONTENT_TRANSFORMS', () => {
|
|
9
|
+
describe('EMAIL', () => {
|
|
10
|
+
it('prefers emailBody over emailHtml when both are present', () => {
|
|
11
|
+
expect(
|
|
12
|
+
CCS_CONTENT_TRANSFORMS.EMAIL({
|
|
13
|
+
emailSubject: 'Subject', emailBody: '<p>Body</p>', emailHtml: '<p>Html</p>',
|
|
14
|
+
}),
|
|
15
|
+
).toEqual({ messageSubject: 'Subject', messageBody: '<p>Body</p>' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('falls back to emailHtml when emailBody is absent', () => {
|
|
19
|
+
expect(
|
|
20
|
+
CCS_CONTENT_TRANSFORMS.EMAIL({ emailSubject: 'Subject', emailHtml: '<p>Html</p>' }),
|
|
21
|
+
).toEqual({ messageSubject: 'Subject', messageBody: '<p>Html</p>' });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('defaults templateData to {} when called with no argument', () => {
|
|
25
|
+
expect(CCS_CONTENT_TRANSFORMS.EMAIL()).toEqual({
|
|
26
|
+
messageSubject: undefined, messageBody: undefined,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('SMS', () => {
|
|
32
|
+
it('uses messageBody when present', () => {
|
|
33
|
+
expect(CCS_CONTENT_TRANSFORMS.SMS({ messageBody: 'Hello' })).toEqual({ message: 'Hello' });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('falls back to an empty string when messageBody is absent', () => {
|
|
37
|
+
expect(CCS_CONTENT_TRANSFORMS.SMS({})).toEqual({ message: '' });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('defaults templateData to {} when called with no argument', () => {
|
|
41
|
+
expect(CCS_CONTENT_TRANSFORMS.SMS()).toEqual({ message: '' });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('WEBPUSH', () => {
|
|
46
|
+
it('extracts fields from the nested messageContent.content wrapper', () => {
|
|
47
|
+
expect(
|
|
48
|
+
CCS_CONTENT_TRANSFORMS.WEBPUSH({
|
|
49
|
+
messageContent: {
|
|
50
|
+
content: {
|
|
51
|
+
messageSubject: 'Subject', accountId: 'acc_1', content: 'Body', offers: [{ id: 1 }],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
).toEqual({ messageSubject: 'Subject', accountId: 'acc_1', content: 'Body' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('falls back to the flat templateData when messageContent.content is absent', () => {
|
|
59
|
+
expect(
|
|
60
|
+
CCS_CONTENT_TRANSFORMS.WEBPUSH({
|
|
61
|
+
messageSubject: 'Subject', accountId: 'acc_1', content: 'Body',
|
|
62
|
+
}),
|
|
63
|
+
).toEqual({ messageSubject: 'Subject', accountId: 'acc_1', content: 'Body' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('defaults templateData to {} when called with no argument', () => {
|
|
67
|
+
expect(CCS_CONTENT_TRANSFORMS.WEBPUSH()).toEqual({
|
|
68
|
+
messageSubject: undefined, accountId: undefined, content: undefined,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('CCS_CHANNEL_NAME_MAP', () => {
|
|
75
|
+
it('normalizes the UI\'s MOBILEPUSH identifier to CCS\'s PUSH enum value', () => {
|
|
76
|
+
expect(CCS_CHANNEL_NAME_MAP.MOBILEPUSH).toBe('PUSH');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('CCS_CHANNEL_CONTENT_KEY_MAP / CCS_CHANNEL_DELIVERY_KEY_MAP', () => {
|
|
81
|
+
it('maps the PUSH channel to its mpush-prefixed content and delivery keys', () => {
|
|
82
|
+
expect(CCS_CHANNEL_CONTENT_KEY_MAP.PUSH).toBe('mpushMessageContent');
|
|
83
|
+
expect(CCS_CHANNEL_DELIVERY_KEY_MAP.PUSH).toBe('mpushDeliverySettings');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -38,9 +38,68 @@ export const CHANNEL_PRIORITY = 'CHANNEL_PRIORITY';
|
|
|
38
38
|
export const AB_TEST = 'AB_TEST';
|
|
39
39
|
export const SINGLE_TEMPLATE = 'SINGLE_TEMPLATE';
|
|
40
40
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// CCS create is only supported for SINGLE (CHANNEL_PRIORITY/AB_TEST skip CCS entirely).
|
|
42
|
+
export const CCS_STRATEGY_TYPE_SINGLE = 'SINGLE';
|
|
43
|
+
|
|
44
|
+
// Mirrors CCS SingleChannelStrategy field names; kept separate from the legacy
|
|
45
|
+
// messageMeta CHANNEL_CONTENT_KEY_MAP/CHANNEL_DELIVERY_KEY_MAP.
|
|
46
|
+
export const CCS_CHANNEL_CONTENT_KEY_MAP = {
|
|
47
|
+
SMS: 'smsMessageContent',
|
|
48
|
+
EMAIL: 'emailMessageContent',
|
|
49
|
+
WHATSAPP: 'whatsappMessageContent',
|
|
50
|
+
// CCS's channel enum for mobile push is 'PUSH', but content/delivery keys stay 'mpush*'.
|
|
51
|
+
PUSH: 'mpushMessageContent',
|
|
52
|
+
INAPP: 'inAppMessageContent',
|
|
53
|
+
ANDROID: 'androidMessageContent',
|
|
54
|
+
IOS: 'iosMessageContent',
|
|
55
|
+
ZALO: 'zaloMessageContent',
|
|
56
|
+
VIBER: 'viberMessageContent',
|
|
57
|
+
LINE: 'lineMessageContent',
|
|
58
|
+
WEBPUSH: 'webPushMessageContent',
|
|
59
|
+
RCS: 'rcsMessageContent',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Transforms legacy messageMeta field names to CCS's before sending, for channels
|
|
63
|
+
// where the two shapes don't already match.
|
|
64
|
+
export const CCS_CONTENT_TRANSFORMS = {
|
|
65
|
+
EMAIL: (templateData = {}) => ({
|
|
66
|
+
messageSubject: templateData.emailSubject,
|
|
67
|
+
messageBody: templateData.emailBody || templateData.emailHtml,
|
|
68
|
+
}),
|
|
69
|
+
SMS: (templateData = {}) => ({
|
|
70
|
+
message: templateData.messageBody || '',
|
|
71
|
+
}),
|
|
72
|
+
WEBPUSH: (templateData = {}) => {
|
|
73
|
+
const inner = templateData.messageContent?.content || templateData;
|
|
74
|
+
return {
|
|
75
|
+
messageSubject: inner.messageSubject,
|
|
76
|
+
accountId: inner.accountId,
|
|
77
|
+
content: inner.content,
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const CCS_CHANNEL_DELIVERY_KEY_MAP = {
|
|
83
|
+
SMS: 'smsDeliverySettings',
|
|
84
|
+
EMAIL: 'emailDeliverySettings',
|
|
85
|
+
WHATSAPP: 'whatsappDeliverySettings',
|
|
86
|
+
PUSH: 'mpushDeliverySettings',
|
|
87
|
+
INAPP: 'inAppDeliverySettings',
|
|
88
|
+
ANDROID: 'androidDeliverySettings',
|
|
89
|
+
IOS: 'iosDeliverySettings',
|
|
90
|
+
ZALO: 'zaloDeliverySettings',
|
|
91
|
+
VIBER: 'viberDeliverySettings',
|
|
92
|
+
LINE: 'lineDeliverySettings',
|
|
93
|
+
WEBPUSH: 'webPushDeliverySettings',
|
|
94
|
+
RCS: 'rcsDeliverySettings',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// UI uses `MOBILEPUSH`; CCS uses `PUSH` for the channel enum. Other channels pass through.
|
|
98
|
+
export const CCS_CHANNEL_NAME_MAP = {
|
|
99
|
+
MOBILEPUSH: 'PUSH',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Channel config is the shared source of truth: paneKey maps TemplatesV2 panes, channelProp maps CreativesContainer tabs.
|
|
44
103
|
export const CHANNELS = [
|
|
45
104
|
{
|
|
46
105
|
value: 'SMS',
|
|
@@ -232,4 +291,6 @@ export const INCENTIVE_TYPES = [
|
|
|
232
291
|
{
|
|
233
292
|
value: 'badges', label: <FormattedMessage {...messages.badges} />, isActive: false, disabled: false,
|
|
234
293
|
},
|
|
235
|
-
];
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
export const REFERENCE_ID_EXISTS = 'REFERENCE_ID_EXISTS';
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import PropTypes from 'prop-types';
|
|
11
11
|
import CommunicationFlow from './CommunicationFlow';
|
|
12
|
-
import { hasSupportEngagementModule } from '../../utils/common';
|
|
13
12
|
|
|
14
13
|
const CommunicationFlowContainer = ({
|
|
15
14
|
config,
|
|
@@ -18,26 +17,20 @@ const CommunicationFlowContainer = ({
|
|
|
18
17
|
onCancel,
|
|
19
18
|
onChange,
|
|
20
19
|
...otherProps
|
|
21
|
-
}) =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
onCancel={onCancel}
|
|
32
|
-
onChange={onChange}
|
|
33
|
-
{...otherProps}
|
|
34
|
-
/>
|
|
35
|
-
);
|
|
36
|
-
};
|
|
20
|
+
}) => (
|
|
21
|
+
<CommunicationFlow
|
|
22
|
+
config={config}
|
|
23
|
+
initialData={initialData}
|
|
24
|
+
onSave={onSave}
|
|
25
|
+
onCancel={onCancel}
|
|
26
|
+
onChange={onChange}
|
|
27
|
+
{...otherProps}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
37
30
|
|
|
38
31
|
CommunicationFlowContainer.propTypes = {
|
|
39
32
|
config: PropTypes.shape({
|
|
40
|
-
consumer: PropTypes.oneOf(['campaigns', 'loyalty', 'adiona']).isRequired,
|
|
33
|
+
consumer: PropTypes.oneOf(['campaigns', 'loyalty', 'adiona', 'coupons', 'cartPromotions']).isRequired,
|
|
41
34
|
channel: PropTypes.string, // optional if channel step is skipped
|
|
42
35
|
mode: PropTypes.oneOf(['create', 'edit', 'preview']).isRequired,
|
|
43
36
|
channelsToHide: PropTypes.arrayOf(PropTypes.string),
|
|
@@ -87,13 +80,13 @@ CommunicationFlowContainer.propTypes = {
|
|
|
87
80
|
controls: PropTypes.array,
|
|
88
81
|
}),
|
|
89
82
|
}),
|
|
90
|
-
context: PropTypes.object, // ouId, campaignId, programId,
|
|
91
|
-
useCCS: PropTypes.bool, //
|
|
83
|
+
context: PropTypes.object, // ouId, campaignId, programId, name, referenceId, description
|
|
84
|
+
useCCS: PropTypes.bool, // skips the CCS createCommDefinition call on save when false; defaults to true
|
|
92
85
|
}).isRequired,
|
|
93
86
|
initialData: PropTypes.object, // for edit/preview mode
|
|
94
|
-
onSave: PropTypes.func.isRequired, // (data) => void
|
|
95
|
-
onCancel: PropTypes.func.isRequired, // () => void
|
|
96
|
-
onChange: PropTypes.func, // (data) => void
|
|
87
|
+
onSave: PropTypes.func.isRequired, // (data) => void, data.ccsCommDefinition set when CCS create succeeded
|
|
88
|
+
onCancel: PropTypes.func.isRequired, // () => void
|
|
89
|
+
onChange: PropTypes.func, // (data) => void
|
|
97
90
|
};
|
|
98
91
|
|
|
99
92
|
CommunicationFlowContainer.defaultProps = {
|
|
@@ -64,9 +64,9 @@ export default {
|
|
|
64
64
|
},
|
|
65
65
|
|
|
66
66
|
// Slidebox header
|
|
67
|
-
|
|
68
|
-
id: `${prefix}.
|
|
69
|
-
defaultMessage: 'Add
|
|
67
|
+
addContent: {
|
|
68
|
+
id: `${prefix}.addContent`,
|
|
69
|
+
defaultMessage: 'Add content',
|
|
70
70
|
},
|
|
71
71
|
|
|
72
72
|
// Step prompts
|
|
@@ -363,4 +363,12 @@ export default {
|
|
|
363
363
|
id: `${prefix}.senderNotConfiguredError`,
|
|
364
364
|
defaultMessage: 'Selected domain gateway id is not correct. Please change the domain id or contact the gateway team to register them with Capillary.',
|
|
365
365
|
},
|
|
366
|
+
duplicateReferenceIdError: {
|
|
367
|
+
id: `${prefix}.duplicateReferenceIdError`,
|
|
368
|
+
defaultMessage: 'This reference ID is already in use in your organization. Please use a different reference ID.',
|
|
369
|
+
},
|
|
370
|
+
genericSaveError: {
|
|
371
|
+
id: `${prefix}.genericSaveError`,
|
|
372
|
+
defaultMessage: 'Something went wrong while saving your content. Please try again.',
|
|
373
|
+
},
|
|
366
374
|
};
|
|
@@ -45,6 +45,7 @@ const ChannelSelectionStep = ({
|
|
|
45
45
|
onChange,
|
|
46
46
|
channelsToHide = [],
|
|
47
47
|
channelsToDisable = [],
|
|
48
|
+
disablePreviewAndTest = false,
|
|
48
49
|
creativesMode = 'create',
|
|
49
50
|
selectedOfferDetails = [],
|
|
50
51
|
incentivesData,
|
|
@@ -63,6 +64,7 @@ const ChannelSelectionStep = ({
|
|
|
63
64
|
const [showIncentivesMenuMap, setShowIncentivesMenuMap] = useState({});
|
|
64
65
|
const [showTestAndPreview, setShowTestAndPreview] = useState(false);
|
|
65
66
|
const [testAndPreviewItem, setTestAndPreviewItem] = useState(null);
|
|
67
|
+
const [domainPropertiesData, setDomainPropertiesData] = useState(null);
|
|
66
68
|
const { formatMessage } = intl || {};
|
|
67
69
|
|
|
68
70
|
// Available channels (filter out hidden ones)
|
|
@@ -76,6 +78,19 @@ const ChannelSelectionStep = ({
|
|
|
76
78
|
const selectedChannelLowerCase = selectedChannel?.toLowerCase();
|
|
77
79
|
return CHANNELS.find((channel) => channel?.value?.toLowerCase() === selectedChannelLowerCase || channel?.channelProp === selectedChannelLowerCase) || null;
|
|
78
80
|
}, [selectedChannel]);
|
|
81
|
+
|
|
82
|
+
const editingZaloHostName = useMemo(() => {
|
|
83
|
+
if (!editingContentId) return '';
|
|
84
|
+
const editingItem = contentItems.find((c) => c.contentId === editingContentId);
|
|
85
|
+
if (editingItem?.channel?.toUpperCase() !== ZALO) return '';
|
|
86
|
+
const accountId = editingItem?.templateData?.accountId;
|
|
87
|
+
if (!accountId) return '';
|
|
88
|
+
const zaloDomains = domainPropertiesData?.ZALO || [];
|
|
89
|
+
const matchedDomain = zaloDomains.find(
|
|
90
|
+
(domain) => String(domain?.domainProperties?.connectionProperties?.oa_id) === String(accountId),
|
|
91
|
+
);
|
|
92
|
+
return matchedDomain?.domainProperties?.hostName || '';
|
|
93
|
+
}, [editingContentId, contentItems, domainPropertiesData]);
|
|
79
94
|
/**
|
|
80
95
|
* Handle CreativesContainer close
|
|
81
96
|
*/
|
|
@@ -236,6 +251,7 @@ const ChannelSelectionStep = ({
|
|
|
236
251
|
};
|
|
237
252
|
|
|
238
253
|
const handleChannelsVisibleChange = (visible) => {
|
|
254
|
+
if (visible && renderChannelDropdownOverlay() === null) return;
|
|
239
255
|
setShowChannelsMenu(visible);
|
|
240
256
|
};
|
|
241
257
|
|
|
@@ -358,7 +374,7 @@ const ChannelSelectionStep = ({
|
|
|
358
374
|
>
|
|
359
375
|
{formatMessage(messages.edit)}
|
|
360
376
|
</CapMenu.Item>
|
|
361
|
-
{!PREVIEW_TEST_UNSUPPORTED_CHANNELS.includes(item?.channel?.toUpperCase()) && (
|
|
377
|
+
{!disablePreviewAndTest && !PREVIEW_TEST_UNSUPPORTED_CHANNELS.includes(item?.channel?.toUpperCase()) && (
|
|
362
378
|
<CapMenu.Item
|
|
363
379
|
id="preview-menu-item"
|
|
364
380
|
className="ant-dropdown-menu-item"
|
|
@@ -407,10 +423,6 @@ const ChannelSelectionStep = ({
|
|
|
407
423
|
return (
|
|
408
424
|
<>
|
|
409
425
|
<CapRow className="channel-selection-step">
|
|
410
|
-
<CapHeading type="h3" className="margin-b-8">
|
|
411
|
-
{formatMessage(messages.contentTemplate)}
|
|
412
|
-
</CapHeading>
|
|
413
|
-
|
|
414
426
|
<CapRow
|
|
415
427
|
useLegacy
|
|
416
428
|
className={`content-template-container ${contentItems?.length > 0 ? 'content-template-container--has-content' : ''}`}
|
|
@@ -422,11 +434,12 @@ const ChannelSelectionStep = ({
|
|
|
422
434
|
)}
|
|
423
435
|
|
|
424
436
|
{contentItems?.length === 0 ? (
|
|
425
|
-
<CapRow className=
|
|
437
|
+
<CapRow className={`content-template-section ${contentItems?.length === 0 ? 'no-content-items' : ''}`}>
|
|
426
438
|
<CapDropdown
|
|
427
439
|
overlay={renderChannelDropdownOverlay() || <CapMenu />}
|
|
428
440
|
onVisibleChange={handleChannelsVisibleChange}
|
|
429
441
|
visible={showChannelsMenu}
|
|
442
|
+
trigger={['click']}
|
|
430
443
|
>
|
|
431
444
|
<CapButton
|
|
432
445
|
isAddBtn
|
|
@@ -470,6 +483,8 @@ const ChannelSelectionStep = ({
|
|
|
470
483
|
deliverySettingsData={deliverySettingsData}
|
|
471
484
|
deliverySetting={value?.deliverySetting}
|
|
472
485
|
onDeliverySettingChange={(deliverySetting) => onChange({ deliverySetting })}
|
|
486
|
+
onDomainPropertiesLoaded={setDomainPropertiesData}
|
|
487
|
+
orgUnitId={config?.context?.ouId}
|
|
473
488
|
intl={intl}
|
|
474
489
|
/>
|
|
475
490
|
)}
|
|
@@ -490,14 +505,13 @@ const ChannelSelectionStep = ({
|
|
|
490
505
|
getCreativesData={handleCreativesData}
|
|
491
506
|
handleCloseCreatives={handleCloseCreatives}
|
|
492
507
|
isFullMode={false}
|
|
493
|
-
|
|
508
|
+
hostName={editingZaloHostName}
|
|
509
|
+
messageDetails={{ type: (config?.context?.module || '').toLowerCase() || 'default' }}
|
|
510
|
+
location={{ query: { type: 'embedded', module: config?.context?.module } }}
|
|
494
511
|
templateData={editingContentId ? (() => {
|
|
495
512
|
const saved = contentItems.find((c) => c.contentId === editingContentId)?.templateData;
|
|
496
|
-
// getTemplateData in CreativesContainer reads 'content', 'accountId', 'messageSubject' at
|
|
497
|
-
// top-level and needs 'type' for SlideBoxContent to set isEditWebPush. Our stored WEBPUSH
|
|
498
|
-
// templateData wraps those fields inside messageContent.content, so extract them here.
|
|
499
513
|
if (saved?.channel?.toUpperCase() === WEBPUSH && saved?.messageContent?.content) {
|
|
500
|
-
return { ...saved.messageContent.content, type: WEBPUSH };
|
|
514
|
+
return { ...saved.messageContent.content, type: WEBPUSH, channel: WEBPUSH };
|
|
501
515
|
}
|
|
502
516
|
return saved;
|
|
503
517
|
})() : null}
|
|
@@ -555,6 +569,7 @@ ChannelSelectionStep.propTypes = {
|
|
|
555
569
|
onChange: PropTypes.func.isRequired,
|
|
556
570
|
channelsToHide: PropTypes.array,
|
|
557
571
|
channelsToDisable: PropTypes.array,
|
|
572
|
+
disablePreviewAndTest: PropTypes.bool,
|
|
558
573
|
creativesMode: PropTypes.oneOf(['create', 'edit', 'preview']),
|
|
559
574
|
selectedOfferDetails: PropTypes.array,
|
|
560
575
|
incentivesData: PropTypes.shape({
|
|
@@ -576,6 +591,7 @@ ChannelSelectionStep.defaultProps = {
|
|
|
576
591
|
error: null,
|
|
577
592
|
channelsToHide: [],
|
|
578
593
|
channelsToDisable: [],
|
|
594
|
+
disablePreviewAndTest: false,
|
|
579
595
|
creativesMode: 'create',
|
|
580
596
|
selectedOfferDetails: [],
|
|
581
597
|
incentivesData: null,
|