@capillarytech/creatives-library 8.0.318 → 8.0.320
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 +15 -0
- package/package.json +1 -1
- package/services/api.js +6 -0
- package/services/tests/api.test.js +7 -0
- package/utils/common.js +6 -1
- package/utils/templateVarUtils.js +172 -0
- package/utils/tests/templateVarUtils.test.js +160 -0
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -0
- package/v2Components/CommonTestAndPreview/index.js +693 -155
- package/v2Components/CommonTestAndPreview/messages.js +41 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +15 -6
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
- 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/SendTestMessage.test.js +25 -4
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/FormBuilder/index.js +7 -1
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/v2Components/SmsFallback/constants.js +73 -0
- package/v2Components/SmsFallback/index.js +956 -0
- package/v2Components/SmsFallback/index.scss +265 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +107 -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 +197 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +8 -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/CommunicationFlow.js +291 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
- package/v2Containers/CommunicationFlow/constants.js +200 -0
- package/v2Containers/CommunicationFlow/index.js +102 -0
- package/v2Containers/CommunicationFlow/messages.js +346 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/v2Containers/CreativesContainer/constants.js +12 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/v2Containers/CreativesContainer/index.js +289 -99
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
- 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/Rcs/constants.js +32 -1
- package/v2Containers/Rcs/index.js +950 -873
- package/v2Containers/Rcs/index.scss +85 -6
- package/v2Containers/Rcs/messages.js +10 -1
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/v2Containers/Rcs/tests/index.test.js +41 -38
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
- package/v2Containers/Rcs/tests/utils.test.js +379 -1
- package/v2Containers/Rcs/utils.js +358 -10
- package/v2Containers/Sms/Create/index.js +81 -36
- 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 +609 -128
- package/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/v2Containers/SmsTrai/Edit/messages.js +9 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +6 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +61 -2
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +90 -40
- package/v2Containers/Templates/sagas.js +57 -12
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
- package/v2Containers/Templates/tests/sagas.test.js +193 -12
- 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 +86 -23
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeliverySettingsStep - Entry point
|
|
3
|
+
* Exports: DeliverySettingsSection (integrated in ChannelSelectionStep), SenderDetails
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { default as SenderDetails, parseSenderDetailsFromEntity } from './SenderDetails';
|
|
7
|
+
export { default as DeliverySettingsSection } from './DeliverySettingsSection';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DynamicControlsStep Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import PropTypes from 'prop-types';
|
|
7
|
+
import { injectIntl } from 'react-intl';
|
|
8
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
9
|
+
import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
|
|
10
|
+
import CapSwitch from '@capillarytech/cap-ui-library/CapSwitch';
|
|
11
|
+
import messages from '../../messages';
|
|
12
|
+
import './DynamicControlsStep.scss';
|
|
13
|
+
|
|
14
|
+
const DynamicControlsStep = ({
|
|
15
|
+
value,
|
|
16
|
+
onChange,
|
|
17
|
+
controls,
|
|
18
|
+
error,
|
|
19
|
+
intl,
|
|
20
|
+
}) => {
|
|
21
|
+
const { formatMessage } = intl || {};
|
|
22
|
+
// formatMessage used for section title only
|
|
23
|
+
const dynamicControls = value?.dynamicControls || {};
|
|
24
|
+
|
|
25
|
+
const handleToggle = (key, checked) => {
|
|
26
|
+
onChange({
|
|
27
|
+
...value,
|
|
28
|
+
dynamicControls: { ...dynamicControls, [key]: checked },
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (!controls?.length) return null;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<CapRow className="dynamic-controls-step">
|
|
36
|
+
<CapHeading type="h3" className="heading-style">
|
|
37
|
+
{formatMessage(messages.dynamicControlsTitle)}
|
|
38
|
+
</CapHeading>
|
|
39
|
+
|
|
40
|
+
{controls.map(({ key, label, description }) => {
|
|
41
|
+
const checked = !!dynamicControls[key];
|
|
42
|
+
const displayLabel = label || key;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<CapRow
|
|
46
|
+
key={key}
|
|
47
|
+
type="flex"
|
|
48
|
+
align="top"
|
|
49
|
+
justify="space-between"
|
|
50
|
+
className="dynamic-controls-step__row"
|
|
51
|
+
>
|
|
52
|
+
<CapRow className="dynamic-controls-step__label-wrap">
|
|
53
|
+
<CapHeading type="h4" className="dynamic-controls-step__label">
|
|
54
|
+
{displayLabel}
|
|
55
|
+
</CapHeading>
|
|
56
|
+
{description && (
|
|
57
|
+
<CapHeading type="label4" className="dynamic-controls-step__desc">
|
|
58
|
+
{description}
|
|
59
|
+
</CapHeading>
|
|
60
|
+
)}
|
|
61
|
+
</CapRow>
|
|
62
|
+
<CapSwitch
|
|
63
|
+
checked={checked}
|
|
64
|
+
onChange={(val) => handleToggle(key, val)}
|
|
65
|
+
className="dynamic-controls-step__switch"
|
|
66
|
+
/>
|
|
67
|
+
</CapRow>
|
|
68
|
+
);
|
|
69
|
+
})}
|
|
70
|
+
|
|
71
|
+
{error && (
|
|
72
|
+
<CapRow className="validation-error dynamic-controls-step__error">
|
|
73
|
+
{error}
|
|
74
|
+
</CapRow>
|
|
75
|
+
)}
|
|
76
|
+
</CapRow>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
DynamicControlsStep.propTypes = {
|
|
81
|
+
value: PropTypes.shape({
|
|
82
|
+
dynamicControls: PropTypes.object,
|
|
83
|
+
}),
|
|
84
|
+
onChange: PropTypes.func.isRequired,
|
|
85
|
+
controls: PropTypes.arrayOf(
|
|
86
|
+
PropTypes.shape({
|
|
87
|
+
key: PropTypes.string.isRequired,
|
|
88
|
+
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
|
89
|
+
description: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
|
90
|
+
}),
|
|
91
|
+
),
|
|
92
|
+
error: PropTypes.string,
|
|
93
|
+
intl: PropTypes.object.isRequired,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
DynamicControlsStep.defaultProps = {
|
|
97
|
+
value: {},
|
|
98
|
+
controls: [],
|
|
99
|
+
error: null,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default injectIntl(DynamicControlsStep);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
2
|
+
|
|
3
|
+
.dynamic-controls-step {
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
|
|
6
|
+
&__row {
|
|
7
|
+
padding: $CAP_SPACE_16 0;
|
|
8
|
+
align-items: flex-start;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
&__label-wrap {
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
align-items: flex-start;
|
|
14
|
+
flex: 1;
|
|
15
|
+
padding-right: $CAP_SPACE_24;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&__label {
|
|
19
|
+
line-height: $CAP_SPACE_20;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&__desc {
|
|
23
|
+
margin-top: $CAP_SPACE_04;
|
|
24
|
+
color: $FONT_COLOR_03;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&__switch {
|
|
28
|
+
flex-shrink: 0;
|
|
29
|
+
margin-top: 0.125rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&__error {
|
|
33
|
+
margin-top: $CAP_SPACE_08;
|
|
34
|
+
color: $CAP_RED;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
render, screen,
|
|
4
|
+
} from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
import '@testing-library/jest-dom';
|
|
7
|
+
import { IntlProvider } from 'react-intl';
|
|
8
|
+
import DynamicControlsStep from '../DynamicControlsStep';
|
|
9
|
+
|
|
10
|
+
const SAMPLE_CONTROLS = [
|
|
11
|
+
{ key: 'alpha', label: 'Alpha', description: 'Alpha help' },
|
|
12
|
+
{ key: 'beta', label: 'Beta', description: 'Beta help' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function renderWithIntl(ui) {
|
|
16
|
+
return render(
|
|
17
|
+
<IntlProvider locale="en" messages={{}} defaultLocale="en">
|
|
18
|
+
{ui}
|
|
19
|
+
</IntlProvider>,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DynamicControlsHarness({ initialDynamicControls = {} }) {
|
|
24
|
+
const [value, setValue] = useState({ dynamicControls: initialDynamicControls });
|
|
25
|
+
return (
|
|
26
|
+
<DynamicControlsStep
|
|
27
|
+
value={value}
|
|
28
|
+
onChange={setValue}
|
|
29
|
+
controls={SAMPLE_CONTROLS}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('DynamicControlsStep', () => {
|
|
35
|
+
it('renders nothing when there are no controls', () => {
|
|
36
|
+
const { container } = renderWithIntl(
|
|
37
|
+
<DynamicControlsStep
|
|
38
|
+
value={{}}
|
|
39
|
+
onChange={jest.fn()}
|
|
40
|
+
controls={[]}
|
|
41
|
+
/>,
|
|
42
|
+
);
|
|
43
|
+
expect(container.firstChild).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('shows each switch on (true) or off (false) from dynamicControls for every field', () => {
|
|
47
|
+
renderWithIntl(
|
|
48
|
+
<DynamicControlsHarness initialDynamicControls={{ alpha: true, beta: false }} />,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(screen.getByText('Other controls')).toBeInTheDocument();
|
|
52
|
+
expect(screen.getByText('Alpha')).toBeInTheDocument();
|
|
53
|
+
expect(screen.getByText('Beta')).toBeInTheDocument();
|
|
54
|
+
|
|
55
|
+
const switches = screen.getAllByRole('switch');
|
|
56
|
+
expect(switches).toHaveLength(2);
|
|
57
|
+
expect(switches[0]).toBeChecked();
|
|
58
|
+
expect(switches[1]).not.toBeChecked();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('toggles every control through false and true via onChange', async () => {
|
|
62
|
+
renderWithIntl(
|
|
63
|
+
<DynamicControlsHarness initialDynamicControls={{ alpha: true, beta: false }} />,
|
|
64
|
+
);
|
|
65
|
+
const switches = screen.getAllByRole('switch');
|
|
66
|
+
|
|
67
|
+
await userEvent.click(switches[0]);
|
|
68
|
+
expect(switches[0]).not.toBeChecked();
|
|
69
|
+
|
|
70
|
+
await userEvent.click(switches[1]);
|
|
71
|
+
expect(switches[1]).toBeChecked();
|
|
72
|
+
|
|
73
|
+
await userEvent.click(switches[0]);
|
|
74
|
+
expect(switches[0]).toBeChecked();
|
|
75
|
+
|
|
76
|
+
await userEvent.click(switches[1]);
|
|
77
|
+
expect(switches[1]).not.toBeChecked();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('renders validation error when error is set', () => {
|
|
81
|
+
renderWithIntl(
|
|
82
|
+
<DynamicControlsStep
|
|
83
|
+
value={{ dynamicControls: { alpha: false } }}
|
|
84
|
+
onChange={jest.fn()}
|
|
85
|
+
controls={[SAMPLE_CONTROLS[0]]}
|
|
86
|
+
error="Dynamic controls validation failed"
|
|
87
|
+
/>,
|
|
88
|
+
);
|
|
89
|
+
expect(screen.getByText('Dynamic controls validation failed')).toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessageTypeStep Component
|
|
3
|
+
*
|
|
4
|
+
* Radio group for selecting Message Type: Promotional | Transactional
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import { injectIntl } from 'react-intl';
|
|
10
|
+
import CapRadio from '@capillarytech/cap-ui-library/CapRadio';
|
|
11
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
12
|
+
import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
|
|
13
|
+
import { CAP_SPACE_24 } from '@capillarytech/cap-ui-library/styled/variables';
|
|
14
|
+
import { MESSAGE_TYPES_OPTIONS } from '../../constants';
|
|
15
|
+
import messages from '../../messages';
|
|
16
|
+
import '../../CommunicationFlow.scss';
|
|
17
|
+
|
|
18
|
+
const MessageTypeStep = ({
|
|
19
|
+
value,
|
|
20
|
+
defaultOption,
|
|
21
|
+
options: optionsProp,
|
|
22
|
+
onChange,
|
|
23
|
+
error,
|
|
24
|
+
intl,
|
|
25
|
+
}) => {
|
|
26
|
+
const { formatMessage } = intl || {};
|
|
27
|
+
const options = optionsProp || MESSAGE_TYPES_OPTIONS;
|
|
28
|
+
|
|
29
|
+
// Use defaultOption value if value is null/undefined
|
|
30
|
+
const selectedValue = value || defaultOption?.value;
|
|
31
|
+
|
|
32
|
+
const handleChange = (messageType) => {
|
|
33
|
+
onChange(messageType);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<CapRow className="message-type-step">
|
|
38
|
+
<CapHeading type="h3" className="heading-style">
|
|
39
|
+
{formatMessage(messages.messageTypeHeading)}
|
|
40
|
+
</CapHeading>
|
|
41
|
+
|
|
42
|
+
<CapRow align="middle" type="flex" gap={CAP_SPACE_24}>
|
|
43
|
+
{options?.map((option) => (
|
|
44
|
+
<CapRadio
|
|
45
|
+
key={option?.value}
|
|
46
|
+
checked={selectedValue === option?.value}
|
|
47
|
+
onChange={() => handleChange(option?.value)}
|
|
48
|
+
disabled={option?.disabled}
|
|
49
|
+
>
|
|
50
|
+
{option?.label}
|
|
51
|
+
</CapRadio>
|
|
52
|
+
))}
|
|
53
|
+
</CapRow>
|
|
54
|
+
|
|
55
|
+
{error && (
|
|
56
|
+
<CapRow className="validation-error">
|
|
57
|
+
{error}
|
|
58
|
+
</CapRow>
|
|
59
|
+
)}
|
|
60
|
+
</CapRow>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
MessageTypeStep.propTypes = {
|
|
65
|
+
value: PropTypes.oneOf(['promotional', 'transactional']),
|
|
66
|
+
defaultOption: PropTypes.shape({
|
|
67
|
+
value: PropTypes.string.isRequired,
|
|
68
|
+
label: PropTypes.node.isRequired,
|
|
69
|
+
}),
|
|
70
|
+
options: PropTypes.arrayOf(PropTypes.shape({
|
|
71
|
+
value: PropTypes.string.isRequired,
|
|
72
|
+
label: PropTypes.node.isRequired,
|
|
73
|
+
})),
|
|
74
|
+
onChange: PropTypes.func.isRequired,
|
|
75
|
+
error: PropTypes.string,
|
|
76
|
+
intl: PropTypes.object.isRequired,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
MessageTypeStep.defaultProps = {
|
|
80
|
+
value: null,
|
|
81
|
+
defaultOption: null,
|
|
82
|
+
options: null,
|
|
83
|
+
error: null,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export default injectIntl(MessageTypeStep);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
|
+
import { IntlProvider } from 'react-intl';
|
|
6
|
+
import MessageTypeStep from '../MessageTypeStep';
|
|
7
|
+
|
|
8
|
+
const OPTIONS = [
|
|
9
|
+
{ value: 'promotional', label: 'Promotional', disabled: false },
|
|
10
|
+
{ value: 'transactional', label: 'Transactional', disabled: false },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function renderWithIntl(ui) {
|
|
14
|
+
return render(
|
|
15
|
+
<IntlProvider locale="en" messages={{}} defaultLocale="en">
|
|
16
|
+
{ui}
|
|
17
|
+
</IntlProvider>,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('MessageTypeStep', () => {
|
|
22
|
+
it('shows the step heading and one radio per option', () => {
|
|
23
|
+
const onChange = jest.fn();
|
|
24
|
+
renderWithIntl(
|
|
25
|
+
<MessageTypeStep
|
|
26
|
+
value="promotional"
|
|
27
|
+
options={OPTIONS}
|
|
28
|
+
defaultOption={{ value: 'promotional', label: 'Promotional' }}
|
|
29
|
+
onChange={onChange}
|
|
30
|
+
/>,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(screen.getByText(/message type/i)).toBeInTheDocument();
|
|
34
|
+
expect(screen.getByRole('radio', { name: /promotional/i })).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByRole('radio', { name: /transactional/i })).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByRole('radio', { name: /promotional/i })).toBeChecked();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('uses defaultOption when value is null so the default appears selected', () => {
|
|
40
|
+
const onChange = jest.fn();
|
|
41
|
+
renderWithIntl(
|
|
42
|
+
<MessageTypeStep
|
|
43
|
+
value={null}
|
|
44
|
+
options={OPTIONS}
|
|
45
|
+
defaultOption={{ value: 'transactional', label: 'Transactional' }}
|
|
46
|
+
onChange={onChange}
|
|
47
|
+
/>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(screen.getByRole('radio', { name: /transactional/i })).toBeChecked();
|
|
51
|
+
expect(screen.getByRole('radio', { name: /promotional/i })).not.toBeChecked();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('calls onChange with the chosen message type when the user switches radios', async () => {
|
|
55
|
+
const onChange = jest.fn();
|
|
56
|
+
renderWithIntl(
|
|
57
|
+
<MessageTypeStep
|
|
58
|
+
value="promotional"
|
|
59
|
+
options={OPTIONS}
|
|
60
|
+
defaultOption={{ value: 'promotional', label: 'Promotional' }}
|
|
61
|
+
onChange={onChange}
|
|
62
|
+
/>,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
await userEvent.click(screen.getByRole('radio', { name: /transactional/i }));
|
|
66
|
+
expect(onChange).toHaveBeenCalledWith('transactional');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('disables individual options when configured', () => {
|
|
70
|
+
const onChange = jest.fn();
|
|
71
|
+
const withDisabled = [
|
|
72
|
+
{ value: 'promotional', label: 'Promotional', disabled: true },
|
|
73
|
+
{ value: 'transactional', label: 'Transactional', disabled: false },
|
|
74
|
+
];
|
|
75
|
+
renderWithIntl(
|
|
76
|
+
<MessageTypeStep
|
|
77
|
+
value="transactional"
|
|
78
|
+
options={withDisabled}
|
|
79
|
+
onChange={onChange}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(screen.getByRole('radio', { name: /promotional/i })).toBeDisabled();
|
|
84
|
+
expect(screen.getByRole('radio', { name: /transactional/i })).not.toBeDisabled();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('shows validation error text below the radios', () => {
|
|
88
|
+
const onChange = jest.fn();
|
|
89
|
+
renderWithIntl(
|
|
90
|
+
<MessageTypeStep
|
|
91
|
+
value="promotional"
|
|
92
|
+
options={OPTIONS}
|
|
93
|
+
onChange={onChange}
|
|
94
|
+
error="Please choose a message type"
|
|
95
|
+
/>,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(screen.getByText('Please choose a message type')).toBeInTheDocument();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { STEPS } from '../constants';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enabled steps in order from config.features.*.required flags.
|
|
5
|
+
* Channel selection, incentives, delivery, and dynamic controls are toggled independently.
|
|
6
|
+
*/
|
|
7
|
+
export const getEnabledSteps = (config) => {
|
|
8
|
+
const { features = {} } = config;
|
|
9
|
+
const steps = [];
|
|
10
|
+
|
|
11
|
+
if (features.messageTypeData?.required) {
|
|
12
|
+
steps.push(STEPS.MESSAGE_TYPE);
|
|
13
|
+
}
|
|
14
|
+
if (features.communicationStrategyData?.required) {
|
|
15
|
+
steps.push(STEPS.COMMUNICATION_STRATEGY);
|
|
16
|
+
}
|
|
17
|
+
if (features.contentTemplateData?.required) {
|
|
18
|
+
steps.push(STEPS.CHANNEL_SELECTION);
|
|
19
|
+
}
|
|
20
|
+
if (features.incentivesData?.required) {
|
|
21
|
+
steps.push(STEPS.INCENTIVES);
|
|
22
|
+
}
|
|
23
|
+
if (features.deliverySettingsData?.required) {
|
|
24
|
+
steps.push(STEPS.DELIVERY_SETTINGS);
|
|
25
|
+
}
|
|
26
|
+
if (features.dynamicControlsData?.required) {
|
|
27
|
+
steps.push(STEPS.DYNAMIC_CONTROLS);
|
|
28
|
+
}
|
|
29
|
+
return steps;
|
|
30
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styled wrapper for CapSlideBox used by CreativesContainer and RCS SMS fallback
|
|
3
|
+
* so header/content/footer margins match.
|
|
4
|
+
*/
|
|
5
|
+
import styled from 'styled-components';
|
|
6
|
+
import { CAP_SPACE_16 } from '@capillarytech/cap-ui-library/styled/variables';
|
|
7
|
+
|
|
8
|
+
const CreativesSlideBoxWrapper = styled.div`
|
|
9
|
+
.cap-slide-box-v2-container {
|
|
10
|
+
/*
|
|
11
|
+
* Liquid-error spacing must stay *inside* the content column. margin-bottom on
|
|
12
|
+
* .slidebox-content-container added to the in-flow height past 100vh, so the outer
|
|
13
|
+
* .cap-slide-box-v2-container (overflow-y: auto in cap-ui) gained a second scrollbar.
|
|
14
|
+
*/
|
|
15
|
+
.slidebox-header {
|
|
16
|
+
margin-bottom: ${({ slideBoxWrapperMargin }) => `${slideBoxWrapperMargin}`};
|
|
17
|
+
padding: 0 rem;
|
|
18
|
+
&.has-footer {
|
|
19
|
+
overflow-x: hidden;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
.slidebox-content-container {
|
|
23
|
+
margin-bottom: 0;
|
|
24
|
+
padding: 0 rem;
|
|
25
|
+
padding-bottom: ${({ slideBoxWrapperMargin }) => `${slideBoxWrapperMargin}`};
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
&.has-footer {
|
|
28
|
+
overflow-x: hidden;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
.slidebox-footer {
|
|
32
|
+
/* Only apply margin-bottom to footer when ErrorInfoNote is shown in footer (BEE editor) */
|
|
33
|
+
/* For HTML Editor, errors are shown in ValidationErrorDisplay (inside content area), so no footer margin needed */
|
|
34
|
+
margin-bottom: ${({ shouldApplyFooterMargin }) => (shouldApplyFooterMargin ? `${CAP_SPACE_16}` : '0')};
|
|
35
|
+
padding: 0 rem;
|
|
36
|
+
&.has-footer {
|
|
37
|
+
overflow-x: hidden;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
export default CreativesSlideBoxWrapper;
|
|
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
import get from 'lodash/get';
|
|
5
5
|
import isEmpty from 'lodash/isEmpty';
|
|
6
|
+
import pick from 'lodash/pick';
|
|
6
7
|
import cloneDeep from 'lodash/cloneDeep';
|
|
7
8
|
import TemplatesV2 from '../TemplatesV2';
|
|
8
9
|
import TemplatePreview from '../../v2Components/TemplatePreview';
|
|
@@ -25,6 +26,7 @@ import Viber from '../Viber';
|
|
|
25
26
|
import Whatsapp from '../Whatsapp';
|
|
26
27
|
import InApp from '../InApp';
|
|
27
28
|
import Rcs from '../Rcs';
|
|
29
|
+
import { isRcsTextOnlyCardMediaType, resolveRcsCardPreviewStrings } from '../Rcs/utils';
|
|
28
30
|
import { getWhatsappContent } from '../Whatsapp/utils';
|
|
29
31
|
import * as commonUtil from '../../utils/common';
|
|
30
32
|
import Zalo from '../Zalo';
|
|
@@ -179,6 +181,8 @@ export function SlideBoxContent(props) {
|
|
|
179
181
|
isTestAndPreviewMode,
|
|
180
182
|
onHtmlEditorValidationStateChange,
|
|
181
183
|
} = props;
|
|
184
|
+
const localTemplatesConfig = props.localTemplatesConfig || pick(props, constants.LOCAL_TEMPLATE_CONFIG_KEYS);
|
|
185
|
+
const useLocalTemplates = !!get(localTemplatesConfig, 'useLocalTemplates');
|
|
182
186
|
const type = (messageDetails.type || '').toLowerCase(); // type is context in get tags values : outbound | dvs | referral | loyalty | coupons
|
|
183
187
|
const query = { type: !isFullMode && 'embedded', module: isFullMode ? 'default' : 'library', isEditFromCampaigns: (templateData || {}).isEditFromCampaigns};
|
|
184
188
|
const creativesLocationProps = {
|
|
@@ -398,12 +402,37 @@ export function SlideBoxContent(props) {
|
|
|
398
402
|
}
|
|
399
403
|
case constants.RCS: {
|
|
400
404
|
const template = cloneDeep(templateDataObject);
|
|
401
|
-
const
|
|
405
|
+
const cardPath = 'versions.base.content.RCS.rcsContent.cardContent[0]';
|
|
406
|
+
const card = get(template, cardPath, {}) || {};
|
|
407
|
+
const {
|
|
408
|
+
description = '',
|
|
409
|
+
media: { mediaUrl = '' } = {},
|
|
410
|
+
title = '',
|
|
411
|
+
mediaType: cardMediaType,
|
|
412
|
+
suggestions = [],
|
|
413
|
+
cardVarMapped: nestedCardVarMapped,
|
|
414
|
+
} = card;
|
|
415
|
+
const rootMirror = templateDataObject?.rcsCardVarMapped;
|
|
416
|
+
const nestedRecord =
|
|
417
|
+
nestedCardVarMapped != null && typeof nestedCardVarMapped === 'object'
|
|
418
|
+
? nestedCardVarMapped
|
|
419
|
+
: {};
|
|
420
|
+
const rootRecord =
|
|
421
|
+
rootMirror != null && typeof rootMirror === 'object' ? rootMirror : {};
|
|
422
|
+
const mergedCardVarMapped = { ...rootRecord, ...nestedRecord };
|
|
423
|
+
const textOnlyCard = isRcsTextOnlyCardMediaType(cardMediaType);
|
|
424
|
+
const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
|
|
425
|
+
title,
|
|
426
|
+
description,
|
|
427
|
+
mergedCardVarMapped,
|
|
428
|
+
!isFullMode,
|
|
429
|
+
textOnlyCard,
|
|
430
|
+
);
|
|
402
431
|
return {
|
|
403
432
|
rcsPreviewContent: {
|
|
404
433
|
rcsImageSrc: mediaUrl,
|
|
405
|
-
rcsTitle
|
|
406
|
-
rcsDesc
|
|
434
|
+
rcsTitle,
|
|
435
|
+
rcsDesc,
|
|
407
436
|
...(suggestions.length > 0 && {
|
|
408
437
|
buttonText: suggestions[0]?.text,
|
|
409
438
|
}),
|
|
@@ -428,7 +457,7 @@ export function SlideBoxContent(props) {
|
|
|
428
457
|
|
|
429
458
|
return (
|
|
430
459
|
<CreativesWrapper>
|
|
431
|
-
{
|
|
460
|
+
{slidBoxContent === 'templates' && (!isFullMode || useLocalTemplates) && (
|
|
432
461
|
<TemplatesV2
|
|
433
462
|
isFullMode={isFullMode}
|
|
434
463
|
onSelectTemplate={onSelectTemplate}
|
|
@@ -460,6 +489,7 @@ export function SlideBoxContent(props) {
|
|
|
460
489
|
eventContextTags={eventContextTags}
|
|
461
490
|
loyaltyMetaData={loyaltyMetaData}
|
|
462
491
|
isLoyaltyModule={isLoyaltyModule}
|
|
492
|
+
localTemplatesConfig={localTemplatesConfig}
|
|
463
493
|
/>
|
|
464
494
|
)}
|
|
465
495
|
{isPreview && (
|
|
@@ -628,6 +658,7 @@ export function SlideBoxContent(props) {
|
|
|
628
658
|
route={{ name: 'sms' }}
|
|
629
659
|
isCreateSms={isCreateSms}
|
|
630
660
|
isComponent
|
|
661
|
+
templateData={templateData}
|
|
631
662
|
isGetFormData={isGetFormData}
|
|
632
663
|
getFormSubscriptionData={getFormData}
|
|
633
664
|
getLiquidTags={getLiquidTags}
|
|
@@ -1203,6 +1234,7 @@ export function SlideBoxContent(props) {
|
|
|
1203
1234
|
)}
|
|
1204
1235
|
{isCreateRcs && (<Rcs
|
|
1205
1236
|
{...rcsCommonProps}
|
|
1237
|
+
templateData={templateData}
|
|
1206
1238
|
showLiquidErrorInFooter={showLiquidErrorInFooter}
|
|
1207
1239
|
showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
|
|
1208
1240
|
handleTestAndPreview={handleTestAndPreview}
|
|
@@ -48,6 +48,8 @@ function SlideBoxFooter(props) {
|
|
|
48
48
|
isAnonymousType = false,
|
|
49
49
|
templateData = {},
|
|
50
50
|
hasPersonalizationTokenError: hasPersonalizationTokenErrorProp = false,
|
|
51
|
+
/** When set (e.g. SMS library create), overrides `creativesTemplatesSave` (“Done”) for the primary button */
|
|
52
|
+
primarySaveButtonMessage,
|
|
51
53
|
} = props;
|
|
52
54
|
// Calculate if buttons should be disabled
|
|
53
55
|
// Only apply validation state checks for EMAIL channel in HTML Editor mode (not BEE/DragDrop)
|
|
@@ -186,7 +188,9 @@ function SlideBoxFooter(props) {
|
|
|
186
188
|
onClick={onSave}
|
|
187
189
|
disabled={isTemplateNameEmpty || fetchingCmsData || shouldDisableButtons || hasPersonalizationTokenError}
|
|
188
190
|
>
|
|
189
|
-
{
|
|
191
|
+
{primarySaveButtonMessage ? (
|
|
192
|
+
<FormattedMessage {...primarySaveButtonMessage} />
|
|
193
|
+
) : isFullMode ? (
|
|
190
194
|
getFullModeSaveBtn(slidBoxContent, isCreatingTemplate)
|
|
191
195
|
) : (
|
|
192
196
|
<FormattedMessage {...messages.creativesTemplatesSave} />
|
|
@@ -262,6 +266,10 @@ SlideBoxFooter.propTypes = {
|
|
|
262
266
|
templateData: PropTypes.object,
|
|
263
267
|
formData: PropTypes.array,
|
|
264
268
|
hasPersonalizationTokenError: PropTypes.bool,
|
|
269
|
+
primarySaveButtonMessage: PropTypes.shape({
|
|
270
|
+
id: PropTypes.string,
|
|
271
|
+
defaultMessage: PropTypes.string,
|
|
272
|
+
}),
|
|
265
273
|
};
|
|
266
274
|
|
|
267
275
|
SlideBoxFooter.defaultProps = {
|
|
@@ -289,5 +297,6 @@ SlideBoxFooter.defaultProps = {
|
|
|
289
297
|
selectedEmailCreateMode: '',
|
|
290
298
|
formData: [],
|
|
291
299
|
hasPersonalizationTokenError: false,
|
|
300
|
+
primarySaveButtonMessage: undefined,
|
|
292
301
|
};
|
|
293
302
|
export default SlideBoxFooter;
|