@capillarytech/creatives-library 8.0.319 → 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 +14 -0
- package/package.json +1 -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/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 +9 -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,92 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {Object} options
|
|
5
|
+
* @param {(params: { page: number, search: string, reset: boolean }) => Promise<{ templates: Array, totalCount: number }>} options.fetchTemplates
|
|
6
|
+
* @param {number} [options.perPage=25]
|
|
7
|
+
*/
|
|
8
|
+
export function useLocalTemplateList({ fetchTemplates, perPage = 25 }) {
|
|
9
|
+
const [listData, setListData] = useState({ templates: [], totalCount: 0 });
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
const [page, setPage] = useState(1);
|
|
12
|
+
const [search, setSearchState] = useState('');
|
|
13
|
+
const searchRef = useRef('');
|
|
14
|
+
/** Drops stale responses when a newer fetch starts (search / reset while a request is in flight). */
|
|
15
|
+
const fetchGenerationRef = useRef(0);
|
|
16
|
+
const setSearch = useCallback((value) => {
|
|
17
|
+
const term = typeof value === 'string' ? value : '';
|
|
18
|
+
searchRef.current = term;
|
|
19
|
+
setSearchState(term);
|
|
20
|
+
}, []);
|
|
21
|
+
const lastFetchFullPageRef = useRef(false);
|
|
22
|
+
|
|
23
|
+
const { templates = [], totalCount = 0 } = listData ?? {};
|
|
24
|
+
const hasKnownTotal = (totalCount ?? 0) > 0;
|
|
25
|
+
const hasMoreByTotal = (totalCount ?? 0) > (templates?.length ?? 0);
|
|
26
|
+
const hasMoreByFullPage =
|
|
27
|
+
!hasKnownTotal && lastFetchFullPageRef.current && (templates?.length ?? 0) > 0;
|
|
28
|
+
const canLoadMore = (hasMoreByTotal || hasMoreByFullPage) && !loading;
|
|
29
|
+
|
|
30
|
+
const runFetch = useCallback(
|
|
31
|
+
async ({ page: p = 1, reset = true, search: searchTerm } = {}) => {
|
|
32
|
+
const term = searchTerm !== undefined ? searchTerm : searchRef.current;
|
|
33
|
+
const gen = ++fetchGenerationRef.current;
|
|
34
|
+
setLoading(true);
|
|
35
|
+
try {
|
|
36
|
+
const result = await fetchTemplates({ page: p, search: term, reset });
|
|
37
|
+
if (gen !== fetchGenerationRef.current) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const nextTemplates = result?.templates ?? [];
|
|
41
|
+
const nextTotalCount = result?.totalCount ?? 0;
|
|
42
|
+
lastFetchFullPageRef.current = nextTemplates.length >= perPage;
|
|
43
|
+
setListData((prev) => ({
|
|
44
|
+
templates: reset ? nextTemplates : [...(prev.templates || []), ...nextTemplates],
|
|
45
|
+
totalCount: nextTotalCount > 0 ? nextTotalCount : (reset ? 0 : prev.totalCount),
|
|
46
|
+
}));
|
|
47
|
+
setPage(p);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
if (gen !== fetchGenerationRef.current) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
lastFetchFullPageRef.current = false;
|
|
53
|
+
if (reset) {
|
|
54
|
+
setListData({ templates: [], totalCount: 0 });
|
|
55
|
+
setPage(1);
|
|
56
|
+
}
|
|
57
|
+
} finally {
|
|
58
|
+
if (gen === fetchGenerationRef.current) {
|
|
59
|
+
setLoading(false);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[fetchTemplates, perPage]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const loadMore = useCallback(() => {
|
|
67
|
+
if (!canLoadMore) return;
|
|
68
|
+
runFetch({ page: page + 1, reset: false, search: searchRef.current });
|
|
69
|
+
}, [canLoadMore, page, runFetch]);
|
|
70
|
+
|
|
71
|
+
const reset = useCallback(
|
|
72
|
+
(searchTerm) => {
|
|
73
|
+
const term = searchTerm !== undefined ? searchTerm : searchRef.current;
|
|
74
|
+
setSearch(term);
|
|
75
|
+
lastFetchFullPageRef.current = false;
|
|
76
|
+
runFetch({ page: 1, reset: true, search: term });
|
|
77
|
+
},
|
|
78
|
+
[runFetch, setSearch]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
templates,
|
|
83
|
+
totalCount,
|
|
84
|
+
loading,
|
|
85
|
+
page,
|
|
86
|
+
search,
|
|
87
|
+
setSearch,
|
|
88
|
+
loadMore,
|
|
89
|
+
reset,
|
|
90
|
+
canLoadMore,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -18,7 +18,7 @@ import injectReducer from '../../utils/injectReducer';
|
|
|
18
18
|
import injectSaga from '../../utils/injectSaga';
|
|
19
19
|
|
|
20
20
|
import CommonTestAndPreview from '../CommonTestAndPreview';
|
|
21
|
-
import { CHANNELS } from '../CommonTestAndPreview/constants';
|
|
21
|
+
import { CHANNELS, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../CommonTestAndPreview/constants';
|
|
22
22
|
import * as commonTestAndPreviewActions from '../CommonTestAndPreview/actions';
|
|
23
23
|
import { commonTestAndPreviewSaga } from '../CommonTestAndPreview/sagas';
|
|
24
24
|
import commonTestAndPreviewReducer from '../CommonTestAndPreview/reducer';
|
|
@@ -78,6 +78,12 @@ TestAndPreviewSlidebox.propTypes = {
|
|
|
78
78
|
content: PropTypes.string,
|
|
79
79
|
beeInstance: PropTypes.object,
|
|
80
80
|
currentTab: PropTypes.number,
|
|
81
|
+
smsFallbackContent: PropTypes.shape({
|
|
82
|
+
templateContent: PropTypes.string,
|
|
83
|
+
senderId: PropTypes.string,
|
|
84
|
+
templateName: PropTypes.string,
|
|
85
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: PropTypes.object,
|
|
86
|
+
}),
|
|
81
87
|
// Redux props are passed through
|
|
82
88
|
actions: PropTypes.object.isRequired,
|
|
83
89
|
extractedTags: PropTypes.array.isRequired,
|
|
@@ -113,6 +119,7 @@ TestAndPreviewSlidebox.defaultProps = {
|
|
|
113
119
|
wecrmAccounts: [],
|
|
114
120
|
isLoadingSenderDetails: false,
|
|
115
121
|
orgUnitId: -1,
|
|
122
|
+
smsFallbackContent: null,
|
|
116
123
|
};
|
|
117
124
|
|
|
118
125
|
const mapStateToProps = createStructuredSelector({
|
|
@@ -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
|
-
|
|
84
|
+
const previewPayload = extractPreviewFromLiquidResponse(response);
|
|
85
|
+
if (previewPayload) {
|
|
84
86
|
yield put({
|
|
85
87
|
type: UPDATE_PREVIEW_SUCCESS,
|
|
86
88
|
payload: {
|
|
87
|
-
previewData:
|
|
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
|
-
|
|
225
|
-
|
|
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:
|
|
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,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';
|
|
@@ -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;
|
|
@@ -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:
|
|
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 {
|
|
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={
|
|
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={
|
|
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
|
+
];
|