@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.
Files changed (139) hide show
  1. package/constants/unified.js +15 -0
  2. package/package.json +1 -1
  3. package/services/api.js +6 -0
  4. package/services/tests/api.test.js +7 -0
  5. package/utils/common.js +6 -1
  6. package/utils/templateVarUtils.js +172 -0
  7. package/utils/tests/templateVarUtils.test.js +160 -0
  8. package/v2Components/CapTagList/index.js +10 -0
  9. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  16. package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
  17. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
  18. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  19. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
  20. package/v2Components/CommonTestAndPreview/constants.js +38 -0
  21. package/v2Components/CommonTestAndPreview/index.js +693 -155
  22. package/v2Components/CommonTestAndPreview/messages.js +41 -3
  23. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  24. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  25. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
  26. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
  27. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  29. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
  30. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  31. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
  32. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  33. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  34. package/v2Components/FormBuilder/index.js +7 -1
  35. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  36. package/v2Components/SmsFallback/constants.js +73 -0
  37. package/v2Components/SmsFallback/index.js +956 -0
  38. package/v2Components/SmsFallback/index.scss +265 -0
  39. package/v2Components/SmsFallback/messages.js +78 -0
  40. package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
  41. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  42. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  43. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  44. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  45. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
  46. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  47. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  48. package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
  49. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  50. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  51. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  52. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  53. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  54. package/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
  55. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
  56. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
  57. package/v2Containers/CommunicationFlow/constants.js +200 -0
  58. package/v2Containers/CommunicationFlow/index.js +102 -0
  59. package/v2Containers/CommunicationFlow/messages.js +346 -0
  60. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
  61. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
  62. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
  63. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
  64. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
  65. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
  66. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
  67. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
  68. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
  69. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
  70. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
  71. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
  72. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
  73. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
  74. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
  75. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
  76. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
  77. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
  78. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
  79. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
  80. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
  81. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
  82. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
  83. package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
  84. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  85. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  86. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  87. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  88. package/v2Containers/CreativesContainer/constants.js +12 -0
  89. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  90. package/v2Containers/CreativesContainer/index.js +289 -99
  91. package/v2Containers/CreativesContainer/index.scss +51 -1
  92. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  93. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
  94. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
  95. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  96. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
  97. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
  98. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  99. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  100. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  101. package/v2Containers/Rcs/constants.js +32 -1
  102. package/v2Containers/Rcs/index.js +950 -873
  103. package/v2Containers/Rcs/index.scss +85 -6
  104. package/v2Containers/Rcs/messages.js +10 -1
  105. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
  106. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
  107. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  108. package/v2Containers/Rcs/tests/index.test.js +41 -38
  109. package/v2Containers/Rcs/tests/mockData.js +38 -0
  110. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
  111. package/v2Containers/Rcs/tests/utils.test.js +379 -1
  112. package/v2Containers/Rcs/utils.js +358 -10
  113. package/v2Containers/Sms/Create/index.js +81 -36
  114. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  115. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  116. package/v2Containers/SmsTrai/Create/index.js +9 -4
  117. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  118. package/v2Containers/SmsTrai/Edit/index.js +609 -128
  119. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  120. package/v2Containers/SmsTrai/Edit/messages.js +9 -4
  121. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
  122. package/v2Containers/SmsWrapper/index.js +37 -8
  123. package/v2Containers/TagList/index.js +6 -0
  124. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  125. package/v2Containers/Templates/_templates.scss +61 -2
  126. package/v2Containers/Templates/actions.js +11 -0
  127. package/v2Containers/Templates/constants.js +2 -0
  128. package/v2Containers/Templates/index.js +90 -40
  129. package/v2Containers/Templates/sagas.js +57 -12
  130. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  131. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  132. package/v2Containers/Templates/tests/sagas.test.js +193 -12
  133. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  134. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  135. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  136. package/v2Containers/TemplatesV2/index.js +86 -23
  137. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  138. package/v2Containers/Whatsapp/index.js +3 -20
  139. 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
+ }
@@ -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,5 @@
1
+ /**
2
+ * DynamicControlsStep - Entry point
3
+ */
4
+
5
+ export { default } from './DynamicControlsStep';
@@ -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,5 @@
1
+ /**
2
+ * MessageTypeStep - Entry point
3
+ */
4
+
5
+ export { default } from './MessageTypeStep';
@@ -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 { description = "", media: { mediaUrl = "" } = {}, title = "", suggestions = [] } = get(template, 'versions.base.content.RCS.rcsContent.cardContent[0]', {});
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: title,
406
- rcsDesc: description,
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
- {!isFullMode && slidBoxContent === 'templates' && (
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
- {isFullMode ? (
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;