@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
package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js
CHANGED
|
@@ -9,29 +9,91 @@
|
|
|
9
9
|
|
|
10
10
|
import get from 'lodash/get';
|
|
11
11
|
import { CHANNELS } from '../../constants';
|
|
12
|
+
import { RCS_CONTACT_INFO_GSM_TYPE_KEYS } from '../constants';
|
|
12
13
|
|
|
14
|
+
/** Match API contactInfo.type (some envs use different casing). */
|
|
15
|
+
const typeMatches = (itemType, expectedKey) => {
|
|
16
|
+
if (!itemType || !expectedKey) return false;
|
|
17
|
+
const a = String(itemType).toLowerCase().replace(/-/g, '_');
|
|
18
|
+
const b = String(expectedKey).toLowerCase().replace(/-/g, '_');
|
|
19
|
+
return a === b;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Include row unless explicitly invalid — many APIs omit `valid`, which would
|
|
24
|
+
* otherwise filter out every sender (RCS/SMS lists empty in UI).
|
|
25
|
+
*/
|
|
13
26
|
const getSenderOptions = (contactInfo, key) =>
|
|
14
27
|
(contactInfo || [])
|
|
15
|
-
.filter((contactInfoItem) => contactInfoItem.type
|
|
28
|
+
.filter((contactInfoItem) => typeMatches(contactInfoItem.type, key) && contactInfoItem.valid !== false)
|
|
16
29
|
.sort((contact) => (contact.default ? -1 : 0));
|
|
17
30
|
|
|
31
|
+
/** RCS APIs may label senders under several `contactInfo.type` keys — see `RCS_CONTACT_INFO_GSM_TYPE_KEYS`. */
|
|
32
|
+
const getRcsLikeGsmSenders = (contactInfo) => {
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const merged = [];
|
|
35
|
+
RCS_CONTACT_INFO_GSM_TYPE_KEYS.forEach((key) => {
|
|
36
|
+
getSenderOptions(contactInfo, key).forEach((row) => {
|
|
37
|
+
const v = row?.value;
|
|
38
|
+
// Skip empty/whitespace — they still pass `v != null` for "" and pollute RCS sender dropdowns.
|
|
39
|
+
if (v == null || String(v).trim() === '' || seen.has(v)) return;
|
|
40
|
+
seen.add(v);
|
|
41
|
+
merged.push(row);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
return merged.sort((a, b) => (a.default ? -1 : 0) - (b.default ? -1 : 0));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Campaigns domainProperties may return { entity } or { result: { entity } } / { data: { entity } }.
|
|
49
|
+
*/
|
|
50
|
+
function unwrapEntity(response) {
|
|
51
|
+
if (!response || typeof response !== 'object') return null;
|
|
52
|
+
if (response.entity != null) return response.entity;
|
|
53
|
+
const nested = get(response, 'result.entity') ?? get(response, 'data.entity');
|
|
54
|
+
if (nested != null) return nested;
|
|
55
|
+
if (response.entity === null) return null;
|
|
56
|
+
return response;
|
|
57
|
+
}
|
|
58
|
+
|
|
18
59
|
/**
|
|
19
60
|
* Parse raw API response for one channel into { domains: [...] }
|
|
20
|
-
* @param {string} channel - SMS | EMAIL | WHATSAPP
|
|
61
|
+
* @param {string} channel - SMS | EMAIL | WHATSAPP | RCS
|
|
21
62
|
* @param {Object} response - API response (e.g. { entity: { SMS: [...] } } or { entity: [...] })
|
|
22
63
|
* @returns {{ domains: Array }} - domains array compatible with campaigns DeliverySettingsV2
|
|
23
64
|
*/
|
|
24
65
|
export function parseSenderDetailsResponse(channel, response) {
|
|
25
|
-
const entity =
|
|
26
|
-
if (
|
|
66
|
+
const entity = unwrapEntity(response);
|
|
67
|
+
if (entity == null || entity === '') {
|
|
27
68
|
return { domains: [] };
|
|
28
69
|
}
|
|
29
70
|
|
|
30
|
-
//
|
|
31
|
-
|
|
71
|
+
// Normalize once: entity keys may be any casing (SMS/sms/rcs) but downstream branches use CHANNELS.* (uppercase).
|
|
72
|
+
const normalizedChannel =
|
|
73
|
+
channel == null || String(channel).trim() === ''
|
|
74
|
+
? ''
|
|
75
|
+
: String(channel).trim().toUpperCase();
|
|
76
|
+
|
|
77
|
+
// Single-channel API: entity may be { [channel]: [...] } or direct array.
|
|
78
|
+
// Some responses use different casing (e.g. rcs vs RCS) or a single domain object instead of an array.
|
|
79
|
+
let channelSenderDetails =
|
|
80
|
+
get(entity, channel, null)
|
|
81
|
+
?? (normalizedChannel ? get(entity, normalizedChannel, null) : null)
|
|
82
|
+
?? (normalizedChannel ? get(entity, normalizedChannel.toLowerCase(), null) : null);
|
|
83
|
+
|
|
32
84
|
if (channelSenderDetails == null && Array.isArray(entity)) {
|
|
33
85
|
channelSenderDetails = entity;
|
|
34
86
|
}
|
|
87
|
+
|
|
88
|
+
if (channelSenderDetails != null && !Array.isArray(channelSenderDetails)) {
|
|
89
|
+
const single = channelSenderDetails;
|
|
90
|
+
if (single && typeof single === 'object' && single.domainProperties) {
|
|
91
|
+
channelSenderDetails = [single];
|
|
92
|
+
} else {
|
|
93
|
+
return { domains: [] };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
35
97
|
if (!Array.isArray(channelSenderDetails)) {
|
|
36
98
|
return { domains: [] };
|
|
37
99
|
}
|
|
@@ -42,6 +104,7 @@ export function parseSenderDetailsResponse(channel, response) {
|
|
|
42
104
|
const { id: dgmId, priority, domainProperties = {} } = element;
|
|
43
105
|
const {
|
|
44
106
|
domainName,
|
|
107
|
+
hostName,
|
|
45
108
|
id = -1,
|
|
46
109
|
contactInfo,
|
|
47
110
|
connectionProperties: {
|
|
@@ -51,21 +114,25 @@ export function parseSenderDetailsResponse(channel, response) {
|
|
|
51
114
|
} = {},
|
|
52
115
|
} = domainProperties;
|
|
53
116
|
|
|
117
|
+
const resolvedDomainName = domainName || hostName;
|
|
118
|
+
|
|
54
119
|
const domain = {
|
|
55
120
|
dgmId,
|
|
56
|
-
domainName,
|
|
121
|
+
domainName: resolvedDomainName,
|
|
57
122
|
domainId: id,
|
|
58
123
|
priority: priority != null ? priority : 0,
|
|
59
124
|
};
|
|
60
125
|
|
|
61
|
-
if ([CHANNELS.SMS, CHANNELS.WHATSAPP].includes(
|
|
126
|
+
if ([CHANNELS.SMS, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(normalizedChannel)) {
|
|
62
127
|
domain.cdmaSenders = getSenderOptions(contactInfo, 'cdma_sender_id');
|
|
63
|
-
domain.gsmSenders =
|
|
128
|
+
domain.gsmSenders = normalizedChannel === CHANNELS.RCS
|
|
129
|
+
? getRcsLikeGsmSenders(contactInfo)
|
|
130
|
+
: getSenderOptions(contactInfo, 'gsm_sender_id');
|
|
64
131
|
domain.sourceAccountIdentifier =
|
|
65
132
|
sourceAccountIdentifier || wabaId || userid || '';
|
|
66
133
|
}
|
|
67
134
|
|
|
68
|
-
if (
|
|
135
|
+
if (normalizedChannel === CHANNELS.EMAIL) {
|
|
69
136
|
domain.emailSenders = getSenderOptions(contactInfo, 'sender_id');
|
|
70
137
|
domain.emailRepliers = getSenderOptions(contactInfo, 'reply_to_id');
|
|
71
138
|
}
|
|
@@ -76,7 +143,8 @@ export function parseSenderDetailsResponse(channel, response) {
|
|
|
76
143
|
// Sort by priority and dedupe by domainName (match campaigns behaviour)
|
|
77
144
|
domains.sort((a, b) => (a.priority || 0) - (b.priority || 0));
|
|
78
145
|
const deduped = domains.reduce((acc, domain) => {
|
|
79
|
-
|
|
146
|
+
const key = domain.domainName ?? domain.domainId;
|
|
147
|
+
if (!acc.find((existing) => (existing.domainName ?? existing.domainId) === key)) {
|
|
80
148
|
acc.push(domain);
|
|
81
149
|
}
|
|
82
150
|
return acc;
|
|
@@ -6,12 +6,13 @@ import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
|
6
6
|
import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
7
7
|
import CapStepsAccordian from '@capillarytech/cap-ui-library/CapStepsAccordian';
|
|
8
8
|
import CapTreeSelect from '@capillarytech/cap-ui-library/CapTreeSelect';
|
|
9
|
+
import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
|
|
9
10
|
import isEmpty from 'lodash/isEmpty';
|
|
10
11
|
import messages from './messages';
|
|
11
12
|
import DeliverySettings from './DeliverySettings';
|
|
12
13
|
import { CHANNELS } from './constants';
|
|
13
14
|
|
|
14
|
-
const CHANNELS_WITH_DELIVERY_SETTINGS = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
|
|
15
|
+
const CHANNELS_WITH_DELIVERY_SETTINGS = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
|
|
15
16
|
|
|
16
17
|
const SendTestMessage = ({
|
|
17
18
|
isFetchingTestCustomers,
|
|
@@ -25,12 +26,13 @@ const SendTestMessage = ({
|
|
|
25
26
|
isSendingTestMessage,
|
|
26
27
|
formatMessage,
|
|
27
28
|
deliverySettings,
|
|
28
|
-
|
|
29
|
+
senderDetailsByChannel = {},
|
|
29
30
|
wecrmAccounts,
|
|
30
31
|
onSaveDeliverySettings,
|
|
31
32
|
isLoadingSenderDetails,
|
|
32
33
|
smsTraiDltEnabled,
|
|
33
34
|
registeredSenderIds,
|
|
35
|
+
isChannelSmsFallbackPreviewEnabled,
|
|
34
36
|
}) => (
|
|
35
37
|
<CapStepsAccordian
|
|
36
38
|
showNumberSteps={false}
|
|
@@ -54,18 +56,20 @@ const SendTestMessage = ({
|
|
|
54
56
|
value={selectedTestEntities}
|
|
55
57
|
multiple
|
|
56
58
|
placeholder={formatMessage(messages.testCustomersPlaceholder)}
|
|
59
|
+
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
|
57
60
|
/>
|
|
58
61
|
{CHANNELS_WITH_DELIVERY_SETTINGS.includes(channel) && (
|
|
59
62
|
<DeliverySettings
|
|
60
63
|
channel={channel}
|
|
61
64
|
deliverySettings={deliverySettings || {}}
|
|
62
|
-
|
|
65
|
+
senderDetailsByChannel={senderDetailsByChannel}
|
|
63
66
|
wecrmAccounts={wecrmAccounts || []}
|
|
64
67
|
onSaveDeliverySettings={onSaveDeliverySettings}
|
|
65
68
|
isLoadingSenderDetails={isLoadingSenderDetails}
|
|
66
69
|
formatMessage={formatMessage}
|
|
67
70
|
smsTraiDltEnabled={smsTraiDltEnabled}
|
|
68
71
|
registeredSenderIds={registeredSenderIds}
|
|
72
|
+
isChannelSmsFallbackPreviewEnabled={isChannelSmsFallbackPreviewEnabled}
|
|
69
73
|
whatsappAccountFromForm={
|
|
70
74
|
channel === CHANNELS.WHATSAPP && formData?.accountName
|
|
71
75
|
? { accountName: formData.accountName }
|
|
@@ -95,24 +99,26 @@ SendTestMessage.propTypes = {
|
|
|
95
99
|
isSendingTestMessage: PropTypes.bool.isRequired,
|
|
96
100
|
formatMessage: PropTypes.func.isRequired,
|
|
97
101
|
deliverySettings: PropTypes.object,
|
|
98
|
-
|
|
102
|
+
senderDetailsByChannel: PropTypes.object,
|
|
99
103
|
wecrmAccounts: PropTypes.array,
|
|
100
104
|
onSaveDeliverySettings: PropTypes.func,
|
|
101
105
|
isLoadingSenderDetails: PropTypes.bool,
|
|
102
106
|
smsTraiDltEnabled: PropTypes.bool,
|
|
103
107
|
registeredSenderIds: PropTypes.array,
|
|
108
|
+
isChannelSmsFallbackPreviewEnabled: PropTypes.bool,
|
|
104
109
|
};
|
|
105
110
|
|
|
106
111
|
SendTestMessage.defaultProps = {
|
|
107
112
|
formData: undefined,
|
|
108
113
|
channel: undefined,
|
|
109
114
|
deliverySettings: {},
|
|
110
|
-
|
|
115
|
+
senderDetailsByChannel: {},
|
|
111
116
|
wecrmAccounts: [],
|
|
112
117
|
onSaveDeliverySettings: undefined,
|
|
113
118
|
isLoadingSenderDetails: false,
|
|
114
119
|
smsTraiDltEnabled: false,
|
|
115
120
|
registeredSenderIds: [],
|
|
121
|
+
isChannelSmsFallbackPreviewEnabled: false,
|
|
116
122
|
};
|
|
117
123
|
|
|
118
124
|
export default SendTestMessage;
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
height: 100%;
|
|
12
12
|
display: flex;
|
|
13
13
|
flex-direction: column;
|
|
14
|
-
background: $CAP_WHITE;
|
|
15
14
|
overflow: hidden;
|
|
16
15
|
|
|
17
16
|
// Preview Chrome wrapper (matches old TestAndPreviewSlidebox design)
|
|
@@ -62,7 +61,27 @@
|
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
}
|
|
64
|
+
|
|
65
|
+
.unified-preview.unified-preview-rcs-tabs {
|
|
66
|
+
.ant-tabs-nav {
|
|
67
|
+
margin: 0;
|
|
68
|
+
padding: 0 $CAP_SPACE_16;
|
|
69
|
+
}
|
|
65
70
|
|
|
71
|
+
// Prevent white hover/active backgrounds from default antd styles
|
|
72
|
+
.ant-tabs-tab,
|
|
73
|
+
.ant-tabs-tab:hover,
|
|
74
|
+
.ant-tabs-tab-active {
|
|
75
|
+
background: transparent;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ant-tabs-tab-btn,
|
|
79
|
+
.ant-tabs-tab-btn:hover,
|
|
80
|
+
.ant-tabs-tab-btn:active {
|
|
81
|
+
background: transparent;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
66
85
|
// Preview Content Container
|
|
67
86
|
.preview-content-container {
|
|
68
87
|
flex: 1;
|
|
@@ -21,8 +21,21 @@ import InAppPreviewContent from './InAppPreviewContent';
|
|
|
21
21
|
import MobilePushPreviewContent from './MobilePushPreviewContent';
|
|
22
22
|
import ViberPreviewContent from './ViberPreviewContent';
|
|
23
23
|
import ZaloPreviewContent from './ZaloPreviewContent';
|
|
24
|
-
import
|
|
24
|
+
import CapTab from '@capillarytech/cap-ui-library/CapTab';
|
|
25
|
+
import {
|
|
26
|
+
CHANNELS,
|
|
27
|
+
DESKTOP,
|
|
28
|
+
TABLET,
|
|
29
|
+
MOBILE,
|
|
30
|
+
ANDROID,
|
|
31
|
+
IOS,
|
|
32
|
+
PREVIEW_TAB_RCS,
|
|
33
|
+
PREVIEW_TAB_SMS_FALLBACK,
|
|
34
|
+
PREVIEW_TAB_KEYS,
|
|
35
|
+
RCS_SMS_FALLBACK_VAR_MAPPED_PROP,
|
|
36
|
+
} from '../constants';
|
|
25
37
|
import messages from '../messages';
|
|
38
|
+
import { getFallbackResolvedContent } from '../../../utils/templateVarUtils';
|
|
26
39
|
import './_unifiedPreview.scss';
|
|
27
40
|
|
|
28
41
|
const UnifiedPreview = ({
|
|
@@ -38,7 +51,24 @@ const UnifiedPreview = ({
|
|
|
38
51
|
lastModified,
|
|
39
52
|
updatedByName,
|
|
40
53
|
showHeader, // Default to true for Test and Preview flow, false for channel component previews
|
|
54
|
+
smsFallbackContent,
|
|
55
|
+
smsFallbackResolvedText,
|
|
56
|
+
activePreviewTab,
|
|
57
|
+
onPreviewTabChange,
|
|
41
58
|
}) => {
|
|
59
|
+
const hasFallbackSmsTemplate = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
60
|
+
const showRcsSmsFallbackTabs = channel === CHANNELS.RCS && hasFallbackSmsTemplate;
|
|
61
|
+
const rawFallbackTemplate = smsFallbackContent?.templateContent ?? smsFallbackContent?.content ?? '';
|
|
62
|
+
const rcsFallbackVarMapped = smsFallbackContent?.[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] ?? {};
|
|
63
|
+
const hasRcsFallbackVarMapped = Object.keys(rcsFallbackVarMapped).length > 0;
|
|
64
|
+
// When no varmap is present, show raw template so {{tags}} remain visible in the preview.
|
|
65
|
+
// Only apply slot substitution when varmap entries exist or a resolved text is available.
|
|
66
|
+
const smsFallbackDisplayContent =
|
|
67
|
+
smsFallbackResolvedText != null && smsFallbackResolvedText !== ''
|
|
68
|
+
? smsFallbackResolvedText
|
|
69
|
+
: hasRcsFallbackVarMapped
|
|
70
|
+
? getFallbackResolvedContent(rawFallbackTemplate, rcsFallbackVarMapped)
|
|
71
|
+
: rawFallbackTemplate;
|
|
42
72
|
/**
|
|
43
73
|
* Render channel-specific preview content
|
|
44
74
|
* For Phase 5, we'll render placeholders
|
|
@@ -124,6 +154,21 @@ const UnifiedPreview = ({
|
|
|
124
154
|
case CHANNELS.RCS: {
|
|
125
155
|
// RCS content is an object with rcsTitle, rcsDesc, rcsImageSrc, suggestions, etc.
|
|
126
156
|
const rcsContent = typeof content === 'object' ? content : {};
|
|
157
|
+
|
|
158
|
+
if (showRcsSmsFallbackTabs && activePreviewTab === PREVIEW_TAB_SMS_FALLBACK) {
|
|
159
|
+
return (
|
|
160
|
+
<SmsPreviewContent
|
|
161
|
+
content={smsFallbackDisplayContent}
|
|
162
|
+
device={ANDROID}
|
|
163
|
+
isUpdating={isUpdating}
|
|
164
|
+
error={error}
|
|
165
|
+
formatMessage={formatMessage}
|
|
166
|
+
senderId={smsFallbackContent?.senderId}
|
|
167
|
+
showHeader={showHeader}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
127
172
|
return (
|
|
128
173
|
<RcsPreviewContent
|
|
129
174
|
content={rcsContent}
|
|
@@ -268,6 +313,79 @@ const UnifiedPreview = ({
|
|
|
268
313
|
* PreviewHeader is shown only when showHeader is true (for Test and Preview flow)
|
|
269
314
|
* Channel-specific content will be in separate files in future phases
|
|
270
315
|
*/
|
|
316
|
+
if (showRcsSmsFallbackTabs) {
|
|
317
|
+
const renderPane = ({ paneChannel, paneDevice, paneShowDeviceToggle, paneContent }) => (
|
|
318
|
+
<CapRow className="unified-preview-tab-pane">
|
|
319
|
+
{showHeader && (
|
|
320
|
+
<PreviewHeader
|
|
321
|
+
selectedCustomer={selectedCustomer}
|
|
322
|
+
device={paneDevice}
|
|
323
|
+
showDeviceToggle={paneShowDeviceToggle}
|
|
324
|
+
onDeviceChange={onDeviceChange}
|
|
325
|
+
channel={paneChannel}
|
|
326
|
+
/>
|
|
327
|
+
)}
|
|
328
|
+
|
|
329
|
+
<CapRow className={`preview-content-container ${!showHeader ? 'preview-content-container-no-header' : ''}`}>
|
|
330
|
+
{paneContent}
|
|
331
|
+
</CapRow>
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
</CapRow>
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<CapRow className="unified-preview unified-preview-rcs-tabs">
|
|
339
|
+
<CapTab
|
|
340
|
+
activeKey={activePreviewTab}
|
|
341
|
+
onChange={onPreviewTabChange}
|
|
342
|
+
panes={[
|
|
343
|
+
{
|
|
344
|
+
tab: formatMessage(messages.rcsTab),
|
|
345
|
+
key: PREVIEW_TAB_RCS,
|
|
346
|
+
content: renderPane({
|
|
347
|
+
paneChannel: CHANNELS.RCS,
|
|
348
|
+
paneDevice: device,
|
|
349
|
+
paneShowDeviceToggle: showDeviceToggle,
|
|
350
|
+
paneContent: (
|
|
351
|
+
<RcsPreviewContent
|
|
352
|
+
content={typeof content === 'object' ? content : {}}
|
|
353
|
+
device={device}
|
|
354
|
+
isUpdating={isUpdating}
|
|
355
|
+
error={error}
|
|
356
|
+
formatMessage={formatMessage}
|
|
357
|
+
senderId={content?.senderId}
|
|
358
|
+
showHeader={showHeader}
|
|
359
|
+
/>
|
|
360
|
+
),
|
|
361
|
+
}),
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
tab: formatMessage(messages.smsFallbackTab),
|
|
365
|
+
key: PREVIEW_TAB_SMS_FALLBACK,
|
|
366
|
+
content: renderPane({
|
|
367
|
+
paneChannel: CHANNELS.SMS,
|
|
368
|
+
paneDevice: device,
|
|
369
|
+
paneShowDeviceToggle: showDeviceToggle,
|
|
370
|
+
paneContent: (
|
|
371
|
+
<SmsPreviewContent
|
|
372
|
+
content={smsFallbackDisplayContent}
|
|
373
|
+
device={device}
|
|
374
|
+
isUpdating={isUpdating}
|
|
375
|
+
error={error}
|
|
376
|
+
formatMessage={formatMessage}
|
|
377
|
+
senderId={smsFallbackContent?.senderId}
|
|
378
|
+
showHeader={showHeader}
|
|
379
|
+
/>
|
|
380
|
+
),
|
|
381
|
+
}),
|
|
382
|
+
},
|
|
383
|
+
]}
|
|
384
|
+
/>
|
|
385
|
+
</CapRow>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
271
389
|
return (
|
|
272
390
|
<CapRow className="unified-preview">
|
|
273
391
|
{/* PreviewHeader - shown only in Test and Preview flow, hidden in channel component previews */}
|
|
@@ -305,9 +423,7 @@ const UnifiedPreview = ({
|
|
|
305
423
|
)}
|
|
306
424
|
{updatedByName && (
|
|
307
425
|
<span className="metadata-item">
|
|
308
|
-
{formatMessage(messages.
|
|
309
|
-
{' '}
|
|
310
|
-
{updatedByName}
|
|
426
|
+
{formatMessage(messages.byAuthor, { name: updatedByName })}
|
|
311
427
|
</span>
|
|
312
428
|
)}
|
|
313
429
|
</CapRow>
|
|
@@ -348,6 +464,15 @@ UnifiedPreview.propTypes = {
|
|
|
348
464
|
|
|
349
465
|
// Header display
|
|
350
466
|
showHeader: PropTypes.bool, // Show PreviewHeader (true for Test and Preview flow, false for channel component previews)
|
|
467
|
+
|
|
468
|
+
// RCS: SMS fallback preview support (same shape as SmsFallback / RCS smsFallbackData)
|
|
469
|
+
smsFallbackContent: PropTypes.shape({
|
|
470
|
+
templateContent: PropTypes.string,
|
|
471
|
+
senderId: PropTypes.string,
|
|
472
|
+
templateName: PropTypes.string,
|
|
473
|
+
}),
|
|
474
|
+
activePreviewTab: PropTypes.oneOf(PREVIEW_TAB_KEYS),
|
|
475
|
+
onPreviewTabChange: PropTypes.func,
|
|
351
476
|
};
|
|
352
477
|
|
|
353
478
|
UnifiedPreview.defaultProps = {
|
|
@@ -360,6 +485,10 @@ UnifiedPreview.defaultProps = {
|
|
|
360
485
|
lastModified: null,
|
|
361
486
|
updatedByName: null,
|
|
362
487
|
showHeader: true, // Default to true for Test and Preview flow
|
|
488
|
+
smsFallbackContent: null,
|
|
489
|
+
smsFallbackResolvedText: undefined,
|
|
490
|
+
activePreviewTab: PREVIEW_TAB_RCS,
|
|
491
|
+
onPreviewTabChange: () => {},
|
|
363
492
|
};
|
|
364
493
|
|
|
365
494
|
export default UnifiedPreview;
|
|
@@ -150,6 +150,11 @@
|
|
|
150
150
|
margin-right: 0.5rem;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/* CapSlideBox defaults to z-index 100; RCS SMS fallback slidebox + tag popovers use 10010–10020 — must stack above or close control is not clickable. */
|
|
154
|
+
.cap-slide-box-v2.common-test-and-preview-slidebox {
|
|
155
|
+
z-index: 10030;
|
|
156
|
+
}
|
|
157
|
+
|
|
153
158
|
.common-test-and-preview-slidebox {
|
|
154
159
|
.common-test-preview-modal-wrap {
|
|
155
160
|
z-index: 10003;
|
|
@@ -419,6 +424,13 @@
|
|
|
419
424
|
}
|
|
420
425
|
}
|
|
421
426
|
|
|
427
|
+
.tags-section-title {
|
|
428
|
+
margin: $CAP_SPACE_12 0;
|
|
429
|
+
display: block;
|
|
430
|
+
color: $CAP_G01;
|
|
431
|
+
font-weight: $FONT_WEIGHT_MEDIUM;
|
|
432
|
+
}
|
|
433
|
+
|
|
422
434
|
.values-table {
|
|
423
435
|
margin-bottom: $CAP_SPACE_16;
|
|
424
436
|
border: $CAP_SPACE_01 solid $CAP_G12;
|
|
@@ -79,6 +79,44 @@ export const CHANNELS = {
|
|
|
79
79
|
ZALO: 'ZALO',
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
/** Unified preview tab keys when RCS shows RCS + SMS fallback panes. */
|
|
83
|
+
export const PREVIEW_TAB_RCS = 'rcs';
|
|
84
|
+
export const PREVIEW_TAB_SMS_FALLBACK = 'smsFallback';
|
|
85
|
+
export const PREVIEW_TAB_KEYS = [PREVIEW_TAB_RCS, PREVIEW_TAB_SMS_FALLBACK];
|
|
86
|
+
|
|
87
|
+
/** Channels whose Test & Preview defaults to Android/iOS device frame (not desktop). */
|
|
88
|
+
export const CHANNELS_USING_ANDROID_PREVIEW_DEVICE = [
|
|
89
|
+
CHANNELS.SMS,
|
|
90
|
+
CHANNELS.WHATSAPP,
|
|
91
|
+
CHANNELS.RCS,
|
|
92
|
+
CHANNELS.INAPP,
|
|
93
|
+
CHANNELS.MOBILEPUSH,
|
|
94
|
+
CHANNELS.VIBER,
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
/** RCS createMessageMeta test payload — rich card envelope literals. */
|
|
98
|
+
export const RCS_TEST_META_CONTENT_TYPE_RICHCARD = 'RICHCARD';
|
|
99
|
+
export const RCS_TEST_META_CARD_TYPE_STANDALONE = 'STANDALONE';
|
|
100
|
+
export const RCS_TEST_META_CARD_ORIENTATION_VERTICAL = 'VERTICAL';
|
|
101
|
+
export const RCS_TEST_META_CARD_WIDTH_SMALL = 'SMALL';
|
|
102
|
+
|
|
103
|
+
/** React key fallback when a custom-values section has no `key` or title id. */
|
|
104
|
+
export const CUSTOM_VALUES_EDITOR_SECTION_FALLBACK_KEY = 'section';
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Matches at least one `{{variableName}}` token in SMS / RCS fallback template text.
|
|
108
|
+
*/
|
|
109
|
+
export const SMS_MUSTACHE_TAG_PATTERN = /\{\{[^}]+\}\}/;
|
|
110
|
+
|
|
111
|
+
/** TRAI DLT `{#…#}` placeholders (same idea as `COMBINED_SMS_TEMPLATE_VAR_REGEX` in unified constants). */
|
|
112
|
+
export const SMS_DLT_HASH_TAG_PATTERN = /\{\#[^#]*\#\}/;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Key on `smsFallbackContent` passed into Test & Preview — RCS slot map from SmsTraiEdit
|
|
116
|
+
* (`onRcsFallbackEditorStateChange`), merged before tag extraction like the embedded SMS editor.
|
|
117
|
+
*/
|
|
118
|
+
export const RCS_SMS_FALLBACK_VAR_MAPPED_PROP = 'rcsSmsFallbackVarMapped';
|
|
119
|
+
|
|
82
120
|
// ============================================
|
|
83
121
|
// DEVICE CONSTANTS
|
|
84
122
|
// ============================================
|