@capillarytech/creatives-library 8.0.345-alpha.13 → 8.0.345-alpha.15

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 (138) hide show
  1. package/constants/unified.js +29 -0
  2. package/package.json +1 -1
  3. package/services/api.js +0 -20
  4. package/services/tests/api.test.js +13 -59
  5. package/utils/commonUtils.js +19 -1
  6. package/utils/rcsPayloadUtils.js +92 -0
  7. package/utils/templateVarUtils.js +201 -0
  8. package/utils/tests/templateVarUtils.test.js +204 -0
  9. package/v2Components/CapActionButton/constants.js +7 -0
  10. package/v2Components/CapActionButton/index.js +167 -109
  11. package/v2Components/CapActionButton/index.scss +157 -6
  12. package/v2Components/CapActionButton/messages.js +19 -3
  13. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  14. package/v2Components/CapCustomSkeleton/index.js +1 -1
  15. package/v2Components/CapCustomSkeleton/tests/__snapshots__/index.test.js.snap +12 -12
  16. package/v2Components/CapTagList/index.js +10 -0
  17. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  24. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +341 -76
  28. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  29. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  30. package/v2Components/CommonTestAndPreview/constants.js +38 -2
  31. package/v2Components/CommonTestAndPreview/index.js +676 -186
  32. package/v2Components/CommonTestAndPreview/messages.js +49 -3
  33. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  34. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  35. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  37. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  38. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  39. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  40. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  41. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  42. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  43. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  46. package/v2Components/FormBuilder/index.js +8 -10
  47. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  48. package/v2Components/SmsFallback/constants.js +73 -0
  49. package/v2Components/SmsFallback/index.js +955 -0
  50. package/v2Components/SmsFallback/index.scss +265 -0
  51. package/v2Components/SmsFallback/messages.js +78 -0
  52. package/v2Components/SmsFallback/smsFallbackUtils.js +118 -0
  53. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  54. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  55. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +277 -0
  58. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  59. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  60. package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
  61. package/v2Components/TemplatePreview/constants.js +2 -0
  62. package/v2Components/TemplatePreview/index.js +143 -28
  63. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  64. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  65. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  66. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  67. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  68. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  69. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  70. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  71. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  72. package/v2Containers/CreativesContainer/SlideBoxFooter.js +11 -4
  73. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  74. package/v2Containers/CreativesContainer/constants.js +9 -0
  75. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  76. package/v2Containers/CreativesContainer/index.js +300 -108
  77. package/v2Containers/CreativesContainer/index.scss +51 -1
  78. package/v2Containers/CreativesContainer/messages.js +0 -4
  79. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  80. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
  81. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  82. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  83. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  84. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -18
  85. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  86. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  87. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  88. package/v2Containers/Rcs/constants.js +119 -8
  89. package/v2Containers/Rcs/index.js +2379 -807
  90. package/v2Containers/Rcs/index.js.rej +1336 -0
  91. package/v2Containers/Rcs/index.scss +276 -6
  92. package/v2Containers/Rcs/index.scss.rej +74 -0
  93. package/v2Containers/Rcs/messages.js +38 -3
  94. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  95. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
  96. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  97. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
  98. package/v2Containers/Rcs/tests/index.test.js +152 -121
  99. package/v2Containers/Rcs/tests/mockData.js +38 -0
  100. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  101. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  102. package/v2Containers/Rcs/utils.js +478 -11
  103. package/v2Containers/Sms/Create/index.js +100 -40
  104. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  105. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  106. package/v2Containers/SmsTrai/Create/index.js +9 -4
  107. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  108. package/v2Containers/SmsTrai/Edit/index.js +636 -130
  109. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  110. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  111. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  112. package/v2Containers/SmsWrapper/index.js +37 -8
  113. package/v2Containers/TagList/index.js +6 -0
  114. package/v2Containers/Templates/ChannelTypeIllustration.js +6 -23
  115. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  116. package/v2Containers/Templates/_templates.scss +181 -126
  117. package/v2Containers/Templates/actions.js +11 -36
  118. package/v2Containers/Templates/constants.js +2 -23
  119. package/v2Containers/Templates/index.js +142 -333
  120. package/v2Containers/Templates/messages.js +0 -68
  121. package/v2Containers/Templates/reducer.js +0 -68
  122. package/v2Containers/Templates/sagas.js +55 -98
  123. package/v2Containers/Templates/selectors.js +0 -12
  124. package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +0 -12
  125. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  126. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1042 -1256
  127. package/v2Containers/Templates/tests/index.test.js +0 -6
  128. package/v2Containers/Templates/tests/reducer.test.js +0 -178
  129. package/v2Containers/Templates/tests/sagas.test.js +200 -436
  130. package/v2Containers/Templates/tests/selector.test.js +0 -32
  131. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  132. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  133. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  134. package/v2Containers/TemplatesV2/index.js +86 -23
  135. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  136. package/v2Containers/Whatsapp/index.js +3 -20
  137. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
  138. package/v2Containers/Assets/images/archive_Empty_Illustration.svg +0 -9
@@ -34,6 +34,7 @@ import {
34
34
  GET_PREFILLED_VALUES_SUCCESS,
35
35
  GET_PREFILLED_VALUES_FAILURE,
36
36
  } from './constants';
37
+ import { extractPreviewFromLiquidResponse } from '../CommonTestAndPreview/previewApiUtils';
37
38
 
38
39
  // Search Customers Saga
39
40
  export function* searchCustomersSaga(action) {
@@ -80,11 +81,12 @@ export function* updatePreviewSaga(action) {
80
81
  const customValues = action.payload.resolvedTags;
81
82
 
82
83
  const response = yield call(Api.updateEmailPreview, action.payload);
83
- if (response?.data) {
84
+ const previewPayload = extractPreviewFromLiquidResponse(response);
85
+ if (previewPayload) {
84
86
  yield put({
85
87
  type: UPDATE_PREVIEW_SUCCESS,
86
88
  payload: {
87
- previewData: response.data,
89
+ previewData: previewPayload,
88
90
  customValues, // Pass custom values to be preserved
89
91
  },
90
92
  });
@@ -221,8 +223,13 @@ export function* createMessageMetaSaga(action) {
221
223
  export function* getPrefilledValuesSaga(action) {
222
224
  try {
223
225
  const response = yield call(Api.updateEmailPreview, action.payload);
224
- if (response?.data) {
225
- yield put({ type: GET_PREFILLED_VALUES_SUCCESS, payload: { values: response?.data?.resolvedTagValues } });
226
+ const body =
227
+ response?.data !== undefined && response?.data !== null
228
+ ? response.data
229
+ : response;
230
+ const resolvedTagValues = body?.resolvedTagValues;
231
+ if (resolvedTagValues != null) {
232
+ yield put({ type: GET_PREFILLED_VALUES_SUCCESS, payload: { values: resolvedTagValues } });
226
233
  } else {
227
234
  yield put({ type: GET_PREFILLED_VALUES_FAILURE, payload: { error: response.error || 'Failed to fetch prefilled values' } });
228
235
  }
@@ -136,7 +136,9 @@ describe('TestAndPreviewSlidebox Sagas', () => {
136
136
  describe('updatePreviewSaga', () => {
137
137
  it('should handle successful preview update', () => {
138
138
  const mockResponse = {
139
- data: 'Test Preview Data',
139
+ data: {
140
+ resolvedBody: 'Test Preview Data',
141
+ },
140
142
  };
141
143
  const customValues = { test: 'value' };
142
144
  return expectSaga(sagas.updatePreviewSaga, {
@@ -0,0 +1,2 @@
1
+ /** Default prefix before variable name in variable-slot placeholders. */
2
+ export const VAR_SEGMENT_PLACEHOLDER_PREFIX = 'enter the value for ';
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Shared message editor that renders template text with {{var}} and/or DLT `{#var#}` segments as
3
+ * variable inputs and static text as headings.
4
+ * Reused by RCS (title/description), SmsTrai Edit (SMS fallback), and WhatsApp (edit message/header).
5
+ */
6
+ import React from 'react';
7
+ import PropTypes from 'prop-types';
8
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
9
+ import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
10
+ import CapInput from '@capillarytech/cap-ui-library/CapInput';
11
+ import {
12
+ splitTemplateVarString,
13
+ DEFAULT_MUSTACHE_VAR_REGEX,
14
+ isAnyTemplateVarToken,
15
+ } from '../../utils/templateVarUtils';
16
+
17
+ import './index.scss';
18
+ import { VAR_SEGMENT_PLACEHOLDER_PREFIX } from './constants';
19
+
20
+ const { TextArea } = CapInput;
21
+
22
+ export function VarSegmentMessageEditor({
23
+ templateString = '',
24
+ valueMap = {},
25
+ onChange,
26
+ onFocus,
27
+ placeholderPrefix = VAR_SEGMENT_PLACEHOLDER_PREFIX,
28
+ getPlaceholder,
29
+ wrapperClassName = 'rcs_text_area_wrapper',
30
+ rowClassName = 'rcs-edit-template-message-input',
31
+ headingClassName = 'rcs-edit-template-message-split',
32
+ varRegex,
33
+ readOnly = false,
34
+ disabled = false,
35
+ footerContent,
36
+ renderVarFooter,
37
+ }) {
38
+ const segments = splitTemplateVarString(templateString, varRegex || DEFAULT_MUSTACHE_VAR_REGEX);
39
+ if (!segments?.length) return null;
40
+
41
+ return (
42
+ <div className={wrapperClassName}>
43
+ <CapRow className={rowClassName}>
44
+ {segments.map((segmentToken, segmentIndex) => {
45
+ const isVar =
46
+ typeof segmentToken === 'string' && isAnyTemplateVarToken(segmentToken);
47
+ if (isVar) {
48
+ const varSegmentFieldId = `${segmentToken}_${segmentIndex}`;
49
+ const slotValueFromMap = valueMap?.[varSegmentFieldId];
50
+ // Missing key: show empty (not the raw {{…}} token) so cleared slots and incomplete maps
51
+ // cannot resurrect the token; placeholder still guides the user.
52
+ const value =
53
+ slotValueFromMap !== undefined && slotValueFromMap !== null ? slotValueFromMap : '';
54
+ if (readOnly) {
55
+ return (
56
+ <CapHeading
57
+ key={varSegmentFieldId}
58
+ type="h4"
59
+ className={`${headingClassName} var-segment-message-editor__read-only-value`.trim()}
60
+ >
61
+ {value}
62
+ </CapHeading>
63
+ );
64
+ }
65
+ const fromGet = getPlaceholder && getPlaceholder(segmentToken, segmentIndex);
66
+ const placeholder =
67
+ fromGet !== undefined && fromGet !== null && fromGet !== ''
68
+ ? fromGet
69
+ : `${placeholderPrefix}${segmentToken}`;
70
+ return (
71
+ <div key={varSegmentFieldId} className="var-segment-message-editor__var-slot">
72
+ <TextArea
73
+ id={varSegmentFieldId}
74
+ placeholder={placeholder}
75
+ autosize={{ minRows: 1, maxRows: 3 }}
76
+ value={value}
77
+ onFocus={() => onFocus && onFocus(varSegmentFieldId)}
78
+ onChange={(e) =>
79
+ onChange && onChange(varSegmentFieldId, e?.target?.value ?? '')}
80
+ disabled={disabled}
81
+ />
82
+ {renderVarFooter
83
+ ? renderVarFooter(segmentToken, segmentIndex, varSegmentFieldId)
84
+ : null}
85
+ </div>
86
+ );
87
+ }
88
+ if (segmentToken) {
89
+ return (
90
+ <CapHeading
91
+ key={`static_${segmentIndex}_${segmentToken}`}
92
+ type="h4"
93
+ className={headingClassName}
94
+ >
95
+ {segmentToken}
96
+ </CapHeading>
97
+ );
98
+ }
99
+ return null;
100
+ })}
101
+ </CapRow>
102
+ {footerContent}
103
+ </div>
104
+ );
105
+ }
106
+
107
+ VarSegmentMessageEditor.propTypes = {
108
+ templateString: PropTypes.string,
109
+ valueMap: PropTypes.object,
110
+ onChange: PropTypes.func,
111
+ onFocus: PropTypes.func,
112
+ placeholderPrefix: PropTypes.string,
113
+ getPlaceholder: PropTypes.func,
114
+ wrapperClassName: PropTypes.string,
115
+ rowClassName: PropTypes.string,
116
+ headingClassName: PropTypes.string,
117
+ varRegex: PropTypes.object,
118
+ readOnly: PropTypes.bool,
119
+ disabled: PropTypes.bool,
120
+ footerContent: PropTypes.node,
121
+ /** Optional hint below a variable field (e.g. DLT `{#var#}` max length). */
122
+ renderVarFooter: PropTypes.func,
123
+ };
124
+
125
+ export default VarSegmentMessageEditor;
@@ -0,0 +1,46 @@
1
+ @import '~@capillarytech/cap-ui-library/styles/_variables';
2
+
3
+ /* Same look as RCS edit message block: background, spacing, text color */
4
+ .rcs_text_area_wrapper {
5
+ .rcs-edit-template-message-input {
6
+ background-color: $CAP_G10;
7
+ padding: $CAP_SPACE_12 $CAP_SPACE_16 $CAP_SPACE_16;
8
+ }
9
+
10
+ .rcs-edit-template-message-split {
11
+ margin: 0 0 $CAP_SPACE_04 0;
12
+ overflow: hidden;
13
+ text-overflow: ellipsis;
14
+ color: $FONT_COLOR_04;
15
+ font-weight: 500;
16
+ }
17
+
18
+ /* Variable chips: match RCS edit (white field, light border, 4px radius) */
19
+ .rcs-edit-template-message-input .ant-input,
20
+ .rcs-edit-template-message-input textarea.ant-input {
21
+ margin: 0 0 0.125rem 0;
22
+ border-radius: 0.25rem;
23
+ border: 0.0625rem solid $CAP_G07;
24
+ background-color: $CAP_WHITE;
25
+ overflow: hidden;
26
+ }
27
+
28
+ /* Small gap between tag border and the next line (static text) */
29
+ .rcs-edit-template-message-input :not(:first-child) {
30
+ margin-top: $CAP_SPACE_08;
31
+ }
32
+
33
+ .rcs-edit-template-message-input > *:last-child {
34
+ margin-bottom: 0;
35
+ }
36
+
37
+ .var-segment-message-editor__var-slot {
38
+ display: flex;
39
+ flex-direction: column;
40
+ width: 100%;
41
+ }
42
+ }
43
+
44
+ .var-segment-message-editor__read-only-value {
45
+ margin: 0;
46
+ }
@@ -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';
@@ -180,6 +182,8 @@ export function SlideBoxContent(props) {
180
182
  isTestAndPreviewMode,
181
183
  onHtmlEditorValidationStateChange,
182
184
  } = props;
185
+ const localTemplatesConfig = props.localTemplatesConfig || pick(props, constants.LOCAL_TEMPLATE_CONFIG_KEYS);
186
+ const useLocalTemplates = !!get(localTemplatesConfig, 'useLocalTemplates');
183
187
  const type = (messageDetails.type || '').toLowerCase(); // type is context in get tags values : outbound | dvs | referral | loyalty | coupons
184
188
  const query = { type: !isFullMode && 'embedded', module: isFullMode ? 'default' : 'library', isEditFromCampaigns: (templateData || {}).isEditFromCampaigns};
185
189
  const creativesLocationProps = {
@@ -399,12 +403,37 @@ export function SlideBoxContent(props) {
399
403
  }
400
404
  case constants.RCS: {
401
405
  const template = cloneDeep(templateDataObject);
402
- const { description = "", media: { mediaUrl = "" } = {}, title = "", suggestions = [] } = get(template, 'versions.base.content.RCS.rcsContent.cardContent[0]', {});
406
+ const cardPath = 'versions.base.content.RCS.rcsContent.cardContent[0]';
407
+ const card = get(template, cardPath, {}) || {};
408
+ const {
409
+ description = '',
410
+ media: { mediaUrl = '' } = {},
411
+ title = '',
412
+ mediaType: cardMediaType,
413
+ suggestions = [],
414
+ cardVarMapped: nestedCardVarMapped,
415
+ } = card;
416
+ const rootMirror = templateDataObject?.rcsCardVarMapped;
417
+ const nestedRecord =
418
+ nestedCardVarMapped != null && typeof nestedCardVarMapped === 'object'
419
+ ? nestedCardVarMapped
420
+ : {};
421
+ const rootRecord =
422
+ rootMirror != null && typeof rootMirror === 'object' ? rootMirror : {};
423
+ const mergedCardVarMapped = { ...rootRecord, ...nestedRecord };
424
+ const textOnlyCard = isRcsTextOnlyCardMediaType(cardMediaType);
425
+ const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
426
+ title,
427
+ description,
428
+ mergedCardVarMapped,
429
+ !isFullMode,
430
+ textOnlyCard,
431
+ );
403
432
  return {
404
433
  rcsPreviewContent: {
405
434
  rcsImageSrc: mediaUrl,
406
- rcsTitle: title,
407
- rcsDesc: description,
435
+ rcsTitle,
436
+ rcsDesc,
408
437
  ...(suggestions.length > 0 && {
409
438
  buttonText: suggestions[0]?.text,
410
439
  }),
@@ -430,7 +459,7 @@ export function SlideBoxContent(props) {
430
459
 
431
460
  return (
432
461
  <CreativesWrapper>
433
- {!isFullMode && slidBoxContent === 'templates' && (
462
+ {slidBoxContent === 'templates' && (!isFullMode || useLocalTemplates) && (
434
463
  <TemplatesV2
435
464
  isFullMode={isFullMode}
436
465
  onSelectTemplate={onSelectTemplate}
@@ -463,6 +492,7 @@ export function SlideBoxContent(props) {
463
492
  waitEventContextTags={waitEventContextTags}
464
493
  loyaltyMetaData={loyaltyMetaData}
465
494
  isLoyaltyModule={isLoyaltyModule}
495
+ localTemplatesConfig={localTemplatesConfig}
466
496
  />
467
497
  )}
468
498
  {isPreview && (
@@ -633,6 +663,7 @@ export function SlideBoxContent(props) {
633
663
  route={{ name: 'sms' }}
634
664
  isCreateSms={isCreateSms}
635
665
  isComponent
666
+ templateData={templateData}
636
667
  isGetFormData={isGetFormData}
637
668
  getFormSubscriptionData={getFormData}
638
669
  getLiquidTags={getLiquidTags}
@@ -1239,6 +1270,7 @@ export function SlideBoxContent(props) {
1239
1270
  )}
1240
1271
  {isCreateRcs && (<Rcs
1241
1272
  {...rcsCommonProps}
1273
+ templateData={templateData}
1242
1274
  showLiquidErrorInFooter={showLiquidErrorInFooter}
1243
1275
  showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
1244
1276
  handleTestAndPreview={handleTestAndPreview}
@@ -24,7 +24,6 @@ function SlideBoxFooter(props) {
24
24
  slidBoxContent,
25
25
  onSave,
26
26
  onEditTemplate,
27
- isTemplateArchived,
28
27
  onCreateNextStep,
29
28
  isFullMode,
30
29
  fetchingCmsData,
@@ -49,6 +48,8 @@ function SlideBoxFooter(props) {
49
48
  isAnonymousType = false,
50
49
  templateData = {},
51
50
  hasPersonalizationTokenError: hasPersonalizationTokenErrorProp = false,
51
+ /** When set (e.g. SMS library create), overrides `creativesTemplatesSave` (“Done”) for the primary button */
52
+ primarySaveButtonMessage,
52
53
  } = props;
53
54
  // Calculate if buttons should be disabled
54
55
  // Only apply validation state checks for EMAIL channel in HTML Editor mode (not BEE/DragDrop)
@@ -187,7 +188,9 @@ function SlideBoxFooter(props) {
187
188
  onClick={onSave}
188
189
  disabled={isTemplateNameEmpty || fetchingCmsData || shouldDisableButtons || hasPersonalizationTokenError}
189
190
  >
190
- {isFullMode ? (
191
+ {primarySaveButtonMessage ? (
192
+ <FormattedMessage {...primarySaveButtonMessage} />
193
+ ) : isFullMode ? (
191
194
  getFullModeSaveBtn(slidBoxContent, isCreatingTemplate)
192
195
  ) : (
193
196
  <FormattedMessage {...messages.creativesTemplatesSave} />
@@ -215,7 +218,7 @@ function SlideBoxFooter(props) {
215
218
  <FormattedMessage {...(continueButtonLabel || messages.continue)} />
216
219
  </CapButton>
217
220
  )}
218
- {slidBoxContent === PREVIEW && !isTemplateArchived && (
221
+ {slidBoxContent === PREVIEW && (
219
222
  <CapButton onClick={onEditTemplate} type="secondary">
220
223
  <FormattedMessage {...messages.creativesTemplatesEdit} />
221
224
  </CapButton>
@@ -228,7 +231,6 @@ SlideBoxFooter.propTypes = {
228
231
  slidBoxContent: PropTypes.node,
229
232
  onSave: PropTypes.func,
230
233
  onEditTemplate: PropTypes.func,
231
- isTemplateArchived: PropTypes.bool,
232
234
  onCreateNextStep: PropTypes.func,
233
235
  shouldShowContinueFooter: PropTypes.func,
234
236
  shouldShowDoneFooter: PropTypes.func,
@@ -264,6 +266,10 @@ SlideBoxFooter.propTypes = {
264
266
  templateData: PropTypes.object,
265
267
  formData: PropTypes.array,
266
268
  hasPersonalizationTokenError: PropTypes.bool,
269
+ primarySaveButtonMessage: PropTypes.shape({
270
+ id: PropTypes.string,
271
+ defaultMessage: PropTypes.string,
272
+ }),
267
273
  };
268
274
 
269
275
  SlideBoxFooter.defaultProps = {
@@ -291,5 +297,6 @@ SlideBoxFooter.defaultProps = {
291
297
  selectedEmailCreateMode: '',
292
298
  formData: [],
293
299
  hasPersonalizationTokenError: false,
300
+ primarySaveButtonMessage: undefined,
294
301
  };
295
302
  export default SlideBoxFooter;
@@ -16,6 +16,7 @@ import { isTraiDLTEnable } from '../../utils/common';
16
16
  import { formatString } from '../../utils/Formatter';
17
17
  import {
18
18
  CAP_SPACE_12,
19
+ CAP_SPACE_16,
19
20
  } from '@capillarytech/cap-ui-library/styled/variables';
20
21
  import { WHATSAPP_HELP_DOC_LINK, JOURNEY } from './constants';
21
22
 
@@ -24,7 +25,7 @@ const StyledLabel = styled(CapLabelInline)`
24
25
  margin-right: ${CAP_SPACE_12};
25
26
  `;
26
27
  const PrefixWrapper = styled.div`
27
- margin-right: 16px;
28
+ margin-right: ${CAP_SPACE_16};
28
29
  `;
29
30
  const renderData = (type, value, channel) => (
30
31
  <StyledLabel className={channel?.toLowerCase() === ZALO ? 'zalo-template-name-spacing' : ''} type={type}>
@@ -33,7 +34,25 @@ const renderData = (type, value, channel) => (
33
34
  );
34
35
 
35
36
  export function SlideBoxHeader(props) {
36
- const { slidBoxContent, templateData, onShowTemplates, creativesMode, isFullMode, showPrefix, shouldShowTemplateName, channel, templateNameRenderProp, weChatTemplateType, onWeChatMaptemplateStepChange, weChatMaptemplateStep, templateStep, smsRegister, handleClose, moduleType } = props;
37
+ const {
38
+ slidBoxContent,
39
+ templateData,
40
+ onShowTemplates,
41
+ creativesMode,
42
+ isFullMode,
43
+ showPrefix,
44
+ shouldShowTemplateName,
45
+ channel,
46
+ templateNameRenderProp,
47
+ weChatTemplateType,
48
+ onWeChatMaptemplateStepChange,
49
+ weChatMaptemplateStep,
50
+ templateStep,
51
+ smsRegister,
52
+ handleClose,
53
+ moduleType,
54
+ useLocalTemplates = false,
55
+ } = props;
37
56
  const showTemplateNameHeader = isFullMode && shouldShowTemplateName;
38
57
  const mapTemplateCreate = !showTemplateNameHeader && slidBoxContent === 'createTemplate' && weChatTemplateType === MAP_TEMPLATE && templateStep !== 'modeSelection';
39
58
  const isTraiDlt = isTraiDLTEnable(isFullMode, smsRegister);
@@ -81,6 +100,9 @@ export function SlideBoxHeader(props) {
81
100
  window.open(WHATSAPP_HELP_DOC_LINK, '_blank');
82
101
  };
83
102
 
103
+ const showCreativesTemplatesBackButton =
104
+ !isFullMode && (moduleType === JOURNEY || useLocalTemplates);
105
+
84
106
  return (
85
107
  <div key="creatives-container-slidebox-header-content">
86
108
  {slidBoxContent === 'templates' && !showTemplateNameHeader && (
@@ -89,7 +111,7 @@ export function SlideBoxHeader(props) {
89
111
  description={![NO_COMMUNICATION, FTP].includes(channel) &&
90
112
  <FormattedMessage {...messages.creativeTemplatesDesc} />
91
113
  }
92
- prefix={!isFullMode && moduleType === JOURNEY &&
114
+ prefix={showCreativesTemplatesBackButton &&
93
115
  <PrefixWrapper>
94
116
  <CapIcons.backIcon onClick={handleClose} />
95
117
  </PrefixWrapper>
@@ -135,7 +157,7 @@ export function SlideBoxHeader(props) {
135
157
  }
136
158
  </>
137
159
  }
138
- prefix={creativesMode !== 'edit' && !isFullMode && showPrefix &&
160
+ prefix={!isFullMode && showPrefix &&
139
161
  <PrefixWrapper>
140
162
  <CapIcons.backIcon onClick={onShowTemplates} />
141
163
  </PrefixWrapper>
@@ -191,5 +213,8 @@ SlideBoxHeader.propTypes = {
191
213
  shouldShowTemplateName: PropTypes.bool,
192
214
  templateNameRenderProp: PropTypes.func,
193
215
  smsRegister: PropTypes.any,
216
+ handleClose: PropTypes.func,
217
+ moduleType: PropTypes.string,
218
+ useLocalTemplates: PropTypes.bool,
194
219
  };
195
220
  export default SlideBoxHeader;
@@ -67,3 +67,12 @@ export const ALLOWED_CHANNELS_FOR_ANONYMOUS = ['mobilepush', 'webpush'];
67
67
  export const ALL_CHANNELS_NEW = [
68
68
  'sms', 'email', 'whatsapp', 'facebook', 'line', 'viber', 'rcs', 'zalo', 'inapp', 'call_task', 'ftp',
69
69
  ];
70
+
71
+ export const LOCAL_TEMPLATE_CONFIG_KEYS = ['useLocalTemplates', 'localTemplates', 'localTemplatesLoading', 'localTemplatesFilterContent', 'localTemplatesUseSkeleton', 'localTemplatesOnPageChange'];
72
+
73
+ /** Keys passed from parents into `Templates` when using local SMS template list (extends `LOCAL_TEMPLATE_CONFIG_KEYS`). */
74
+ export const LOCAL_TEMPLATE_CONFIG_KEYS_FOR_PICK = [
75
+ ...LOCAL_TEMPLATE_CONFIG_KEYS,
76
+ 'localTemplatesLoadingTip',
77
+ 'localTemplatesFooterContent',
78
+ ];
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared logic for CreativesContainer slidebox + embedded flows (e.g. RCS SMS fallback)
3
+ * that mirror the same footer liquid errors and layout margins.
4
+ */
5
+ import get from 'lodash/get';
6
+ import {
7
+ CAP_SPACE_32,
8
+ CAP_SPACE_56,
9
+ CAP_SPACE_64,
10
+ } from '@capillarytech/cap-ui-library/styled/variables';
11
+ import * as constants from './constants';
12
+
13
+ /**
14
+ * Returns true if value is "deep empty": no errors present.
15
+ * Same rules as CreativesContainer (used for liquid / standard error payloads).
16
+ */
17
+ export function isDeepEmpty(value) {
18
+ if (value == null) return true;
19
+ if (typeof value === 'string') return value.length === 0;
20
+ if (Array.isArray(value)) return value.length === 0;
21
+ if (typeof value === 'object') {
22
+ return Object.values(value).every(isDeepEmpty);
23
+ }
24
+ return false;
25
+ }
26
+
27
+ /**
28
+ * Header/content margin below slidebox chrome when ErrorInfoNote stacks errors — same formula as CreativesContainer#render.
29
+ */
30
+ export function getSlideBoxWrapperMarginFromLiquidErrors(liquidErrorMessage) {
31
+ return (get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
32
+ && get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0)
33
+ ? CAP_SPACE_64
34
+ : get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0
35
+ ? CAP_SPACE_56
36
+ : get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
37
+ ? CAP_SPACE_32
38
+ : 0;
39
+ }
40
+
41
+ /**
42
+ * Maps FormBuilder `showLiquidErrorInFooter` args to slidebox footer state.
43
+ * Returns `null` when CreativesContainer intentionally skips updating (Mobile Push OLD empty clear).
44
+ */
45
+ export function computeLiquidFooterUpdateFromFormBuilder(
46
+ errorMessagesFromFormBuilder,
47
+ currentFormBuilderTab,
48
+ { previousIsLiquidValidationError, currentChannelUpper } = {},
49
+ ) {
50
+ const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
51
+ const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
52
+ const hasLiquid = !isDeepEmpty(liquidMsgs);
53
+ const hasStandard = !isDeepEmpty(standardMsgs);
54
+ const isLiquidValidationError = hasLiquid || hasStandard;
55
+ const isMobilePush = currentChannelUpper === constants.MOBILE_PUSH;
56
+ if (!hasLiquid && !hasStandard && previousIsLiquidValidationError && isMobilePush) {
57
+ return null;
58
+ }
59
+ return {
60
+ isLiquidValidationError,
61
+ liquidErrorMessage: errorMessagesFromFormBuilder,
62
+ activeFormBuilderTab:
63
+ currentFormBuilderTab === 1
64
+ ? constants.ANDROID
65
+ : (currentFormBuilderTab === 2 ? constants.IOS : null),
66
+ };
67
+ }