@capillarytech/creatives-library 9.0.56-alpha.4 → 9.0.56-alpha.5
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/package.json +1 -1
- package/services/api.js +8 -6
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +2 -1
- package/v2Components/CommonTestAndPreview/index.js +1 -1
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +23 -25
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +2 -4
- package/v2Containers/CommunicationFlow/constants.js +10 -8
- package/v2Containers/CommunicationFlow/index.js +5 -7
package/package.json
CHANGED
package/services/api.js
CHANGED
|
@@ -701,21 +701,23 @@ export const createCentralCommsMetaId = (payload, metaType = TRANSACTION) => {
|
|
|
701
701
|
return request(url, getAPICallObject('POST', payload, false, false, false, true));
|
|
702
702
|
};
|
|
703
703
|
|
|
704
|
-
// CCS CommDefinition API
|
|
704
|
+
// CCS CommDefinition API, proxied through cap-creatives-api; used by CommunicationFlow's Save, while legacy messageMeta functions remain for CreativesContainer's save flow.
|
|
705
|
+
const COMM_DEFINITIONS_PATH = `${API_ENDPOINT}/comm-definitions`;
|
|
706
|
+
|
|
705
707
|
export const createCommDefinition = (payload) => {
|
|
706
|
-
const url =
|
|
708
|
+
const url = COMM_DEFINITIONS_PATH;
|
|
707
709
|
return request(url, getAPICallObject('POST', payload, false, false, false, true));
|
|
708
710
|
};
|
|
709
711
|
|
|
710
|
-
// Opens a new DRAFT version
|
|
712
|
+
// Opens a new DRAFT version on an existing CommDefinition, preserving its id/referenceId.
|
|
711
713
|
export const editCommDefinition = (commDefinitionId, payload) => {
|
|
712
|
-
const url = `${
|
|
714
|
+
const url = `${COMM_DEFINITIONS_PATH}/${commDefinitionId}/edit`;
|
|
713
715
|
return request(url, getAPICallObject('POST', payload, false, false, false, true));
|
|
714
716
|
};
|
|
715
717
|
|
|
716
|
-
//
|
|
718
|
+
// notify/ui's "Send test" uses this instead of the legacy createMessageMeta/sendTestMessage flow.
|
|
717
719
|
export const sendTransactionalComm = (payload) => {
|
|
718
|
-
const url = `${
|
|
720
|
+
const url = `${COMM_DEFINITIONS_PATH}/send/transaction`;
|
|
719
721
|
return request(url, getAPICallObject('POST', payload, false, false, false, true));
|
|
720
722
|
};
|
|
721
723
|
|
|
@@ -140,7 +140,8 @@ const UnifiedPreview = ({
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
case CHANNELS.LINE:
|
|
143
|
-
// LINE
|
|
143
|
+
// LINE carries a single plain-text message today, so it reuses the SMS chat-bubble
|
|
144
|
+
// preview. Rich types (image/video/sticker/imageMap/carousel/flex) aren't rendered yet.
|
|
144
145
|
return (
|
|
145
146
|
<SmsPreviewContent
|
|
146
147
|
content={typeof content === 'string' ? content : (content?.resolvedBody || '')}
|
|
@@ -209,7 +209,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
209
209
|
wecrmAccounts = [],
|
|
210
210
|
isLoadingSenderDetails = false,
|
|
211
211
|
orgUnitId = -1,
|
|
212
|
-
// notify/ui only: { commDefinitionId, referenceId } —
|
|
212
|
+
// notify/ui only: { commDefinitionId, referenceId } — routes "Send test" to CCS's send/transaction endpoint.
|
|
213
213
|
ccsSendTransaction,
|
|
214
214
|
// Email-specific props
|
|
215
215
|
beeInstance,
|
|
@@ -46,11 +46,8 @@ import { getEnabledSteps } from './utils/getEnabledSteps';
|
|
|
46
46
|
import messages from './messages';
|
|
47
47
|
import './CommunicationFlow.scss';
|
|
48
48
|
|
|
49
|
-
// Self-injects the same 'cap' reducer CreativesContainer already injects under this key
|
|
50
|
-
//
|
|
51
|
-
// consumer's own store already provides it. Guarantees makeSelectAuthenticated() below never
|
|
52
|
-
// reads an undefined domain, regardless of whether a given consumer (e.g. coupons,
|
|
53
|
-
// cartPromotions) wires up its own 'cap' slice.
|
|
49
|
+
// Self-injects the same 'cap' reducer CreativesContainer already injects under this key;
|
|
50
|
+
// idempotent, so it's a no-op if the consumer's own store already provides it.
|
|
54
51
|
const withCapReducer = injectReducer({ key: 'cap', reducer: capReducer });
|
|
55
52
|
|
|
56
53
|
const getDeliveryChannels = (contentItems) => {
|
|
@@ -64,7 +61,7 @@ const hasDeliverySettingForChannel = (channelSetting, channel) => {
|
|
|
64
61
|
return !!settings && Object.values(settings).some((v) => v !== null && v !== '' && v !== undefined);
|
|
65
62
|
};
|
|
66
63
|
|
|
67
|
-
//
|
|
64
|
+
// Duplicate referenceId within the org returns a 409 CCS error envelope.
|
|
68
65
|
const isDuplicateReferenceIdError = (res) => res?.success === false && res?.status?.message === REFERENCE_ID_EXISTS;
|
|
69
66
|
|
|
70
67
|
const CommunicationFlow = ({
|
|
@@ -162,12 +159,10 @@ const CommunicationFlow = ({
|
|
|
162
159
|
if (shouldUseCCS) {
|
|
163
160
|
const isMultiChannel = [CHANNEL_PRIORITY, AB_TEST].includes(aggregatedData.communicationStrategy);
|
|
164
161
|
const contentItem = (aggregatedData.contentItems || [])[0];
|
|
165
|
-
// Consumer-supplied
|
|
166
|
-
// has no name input of its own, so this is mandatory input from config.context.
|
|
162
|
+
// Consumer-supplied (e.g. CapNotify's Alert Name field) — CommunicationFlow has no name input of its own.
|
|
167
163
|
const name = config?.context?.name;
|
|
168
164
|
|
|
169
|
-
// CCS create
|
|
170
|
-
// carry multiple content items with no CCS equivalent yet.
|
|
165
|
+
// CCS create only supports SINGLE strategy; CHANNEL_PRIORITY/AB_TEST have no CCS equivalent yet.
|
|
171
166
|
if (!isMultiChannel && contentItem && name) {
|
|
172
167
|
const rawChannel = (contentItem.channel || '').toUpperCase();
|
|
173
168
|
const channel = CCS_CHANNEL_NAME_MAP[rawChannel] || rawChannel;
|
|
@@ -177,8 +172,7 @@ const CommunicationFlow = ({
|
|
|
177
172
|
const contentPayload = contentTransform ? contentTransform(contentItem.templateData) : contentItem.templateData;
|
|
178
173
|
const { dynamicControls = {} } = aggregatedData;
|
|
179
174
|
const channelSettings = aggregatedData.deliverySetting?.channelSetting?.[channel] || {};
|
|
180
|
-
//
|
|
181
|
-
// approval) — sent as '' rather than auto-generated/omitted when left blank.
|
|
175
|
+
// Optional fields — sent as '' rather than omitted when left blank.
|
|
182
176
|
const referenceId = config?.context?.referenceId || '';
|
|
183
177
|
const description = config?.context?.description || '';
|
|
184
178
|
|
|
@@ -199,29 +193,35 @@ const CommunicationFlow = ({
|
|
|
199
193
|
singleChannelStrategy: {
|
|
200
194
|
variant: {
|
|
201
195
|
channel,
|
|
202
|
-
// `channel`
|
|
196
|
+
// `channel` spread last so CCS's normalized enum overrides the UI's internal identifier.
|
|
203
197
|
...(contentKey && { [contentKey]: { ...contentPayload, channel } }),
|
|
204
198
|
...(deliveryKey && { [deliveryKey]: { channelSettings: { ...channelSettings, channel } } }),
|
|
205
199
|
},
|
|
206
200
|
},
|
|
207
201
|
};
|
|
208
202
|
|
|
209
|
-
//
|
|
203
|
+
// existingCommDefinitionId (set when editing, e.g. a rejected alert) creates a new
|
|
204
|
+
// DRAFT version on the same CommDefinition instead of a new one.
|
|
210
205
|
const { existingCommDefinitionId } = config?.context || {};
|
|
206
|
+
const {
|
|
207
|
+
referenceId: payloadReferenceId,
|
|
208
|
+
description: payloadDescription,
|
|
209
|
+
strategyType,
|
|
210
|
+
settings,
|
|
211
|
+
singleChannelStrategy,
|
|
212
|
+
} = payload;
|
|
211
213
|
|
|
212
214
|
try {
|
|
213
215
|
const res = existingCommDefinitionId
|
|
214
216
|
? await editCommDefinition(existingCommDefinitionId, {
|
|
215
|
-
referenceId:
|
|
216
|
-
description:
|
|
217
|
-
strategyType
|
|
218
|
-
settings
|
|
219
|
-
singleChannelStrategy
|
|
217
|
+
referenceId: payloadReferenceId,
|
|
218
|
+
description: payloadDescription,
|
|
219
|
+
strategyType,
|
|
220
|
+
settings,
|
|
221
|
+
singleChannelStrategy,
|
|
220
222
|
})
|
|
221
223
|
: await createCommDefinition(payload);
|
|
222
224
|
if (isDuplicateReferenceIdError(res)) {
|
|
223
|
-
// Duplicate referenceId in this org — block the save so the user can
|
|
224
|
-
// change it, rather than silently proceeding without a CCS comm.
|
|
225
225
|
setSaveError(formatMessage(messages.duplicateReferenceIdError));
|
|
226
226
|
return;
|
|
227
227
|
}
|
|
@@ -291,10 +291,8 @@ const CommunicationFlow = ({
|
|
|
291
291
|
</CapRow>
|
|
292
292
|
);
|
|
293
293
|
case STEPS.COMMUNICATION_STRATEGY: {
|
|
294
|
-
// No
|
|
295
|
-
//
|
|
296
|
-
// see the `.communication-strategy-row` spacing rule in CommunicationFlow.scss,
|
|
297
|
-
// which is not tied to whether the divider itself renders.
|
|
294
|
+
// No divider when there's no content yet, but the spacing above the Channel
|
|
295
|
+
// card must stay either way — see .communication-strategy-row in the scss.
|
|
298
296
|
const templateStepFollows = enabledSteps.includes(STEPS.CHANNEL_SELECTION);
|
|
299
297
|
const hasContent = stepData.contentItems?.length > 0;
|
|
300
298
|
return (
|
|
@@ -111,10 +111,8 @@ const CommunicationFlowCard = ({
|
|
|
111
111
|
const senderIdValue = getSenderIdValue(firstItem, savedData);
|
|
112
112
|
const controls = config?.features?.dynamicControlsData?.controls || DYNAMIC_CONTROLS_CONFIG;
|
|
113
113
|
const dynamicControlKeys = Object.keys(savedData?.dynamicControls || {});
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
// false render their own "Advanced controls" section elsewhere, so this summary
|
|
117
|
-
// card's right column would otherwise show a redundant, always-empty control list.
|
|
114
|
+
// Consumers that set showDynamicControls: false render their own "Advanced
|
|
115
|
+
// controls" section elsewhere, so skip this card's redundant control list.
|
|
118
116
|
const showDynamicControls = config?.features?.showDynamicControls !== false;
|
|
119
117
|
const channel = firstItem.channel?.toUpperCase();
|
|
120
118
|
const senderLabel = [WHATSAPP, RCS].includes(channel)
|
|
@@ -38,15 +38,16 @@ export const CHANNEL_PRIORITY = 'CHANNEL_PRIORITY';
|
|
|
38
38
|
export const AB_TEST = 'AB_TEST';
|
|
39
39
|
export const SINGLE_TEMPLATE = 'SINGLE_TEMPLATE';
|
|
40
40
|
|
|
41
|
-
// CCS
|
|
41
|
+
// CCS create is only supported for SINGLE (CHANNEL_PRIORITY/AB_TEST skip CCS entirely).
|
|
42
42
|
export const CCS_STRATEGY_TYPE_SINGLE = 'SINGLE';
|
|
43
43
|
|
|
44
|
-
// Mirrors CCS SingleChannelStrategy field names
|
|
44
|
+
// Mirrors CCS SingleChannelStrategy field names; kept separate from the legacy
|
|
45
|
+
// messageMeta CHANNEL_CONTENT_KEY_MAP/CHANNEL_DELIVERY_KEY_MAP.
|
|
45
46
|
export const CCS_CHANNEL_CONTENT_KEY_MAP = {
|
|
46
47
|
SMS: 'smsMessageContent',
|
|
47
48
|
EMAIL: 'emailMessageContent',
|
|
48
49
|
WHATSAPP: 'whatsappMessageContent',
|
|
49
|
-
// CCS's channel enum
|
|
50
|
+
// CCS's channel enum for mobile push is 'PUSH', but content/delivery keys stay 'mpush*'.
|
|
50
51
|
PUSH: 'mpushMessageContent',
|
|
51
52
|
INAPP: 'inAppMessageContent',
|
|
52
53
|
ANDROID: 'androidMessageContent',
|
|
@@ -58,17 +59,16 @@ export const CCS_CHANNEL_CONTENT_KEY_MAP = {
|
|
|
58
59
|
RCS: 'rcsMessageContent',
|
|
59
60
|
};
|
|
60
61
|
|
|
61
|
-
//
|
|
62
|
+
// Transforms legacy messageMeta field names to CCS's before sending, for channels
|
|
63
|
+
// where the two shapes don't already match.
|
|
62
64
|
export const CCS_CONTENT_TRANSFORMS = {
|
|
63
65
|
EMAIL: (templateData = {}) => ({
|
|
64
66
|
messageSubject: templateData.emailSubject,
|
|
65
67
|
messageBody: templateData.emailBody || templateData.emailHtml,
|
|
66
68
|
}),
|
|
67
|
-
// SMS: legacy stores text as `messageBody`, while CCS expects `message`; map the field explicitly to avoid CCS returning smsMessageContent.message as null.
|
|
68
69
|
SMS: (templateData = {}) => ({
|
|
69
70
|
message: templateData.messageBody || '',
|
|
70
71
|
}),
|
|
71
|
-
// WEBPUSH: the legacy editor nests data under messageContent.content; flatten it to CCS's { messageSubject, accountId, content } shape and drop the legacy-only `offers` field.
|
|
72
72
|
WEBPUSH: (templateData = {}) => {
|
|
73
73
|
const inner = templateData.messageContent?.content || templateData;
|
|
74
74
|
return {
|
|
@@ -94,12 +94,14 @@ export const CCS_CHANNEL_DELIVERY_KEY_MAP = {
|
|
|
94
94
|
RCS: 'rcsDeliverySettings',
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
-
//
|
|
97
|
+
// UI uses `MOBILEPUSH`; CCS uses `PUSH` for the channel enum. Other channels pass through.
|
|
98
98
|
export const CCS_CHANNEL_NAME_MAP = {
|
|
99
99
|
MOBILEPUSH: 'PUSH',
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
-
// Channel config — single source of truth for CreativesContainer and TemplatesV2
|
|
102
|
+
// Channel config — single source of truth for CreativesContainer and TemplatesV2.
|
|
103
|
+
// paneKey: TemplatesV2 defaultPanes key used by channelsToHide.
|
|
104
|
+
// channelProp: CreativesContainer channel prop that must match pane.key for the tab to be active.
|
|
103
105
|
export const CHANNELS = [
|
|
104
106
|
{
|
|
105
107
|
value: 'SMS',
|
|
@@ -80,15 +80,13 @@ CommunicationFlowContainer.propTypes = {
|
|
|
80
80
|
controls: PropTypes.array,
|
|
81
81
|
}),
|
|
82
82
|
}),
|
|
83
|
-
context: PropTypes.object, // ouId, campaignId, programId
|
|
84
|
-
//
|
|
85
|
-
useCCS: PropTypes.bool, // If false, skips the CCS createCommDefinition call on save. Defaults to true.
|
|
83
|
+
context: PropTypes.object, // ouId, campaignId, programId, name, referenceId, description
|
|
84
|
+
useCCS: PropTypes.bool, // skips the CCS createCommDefinition call on save when false; defaults to true
|
|
86
85
|
}).isRequired,
|
|
87
86
|
initialData: PropTypes.object, // for edit/preview mode
|
|
88
|
-
onSave: PropTypes.func.isRequired, // (data) => void
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
onChange: PropTypes.func, // (data) => void - optional, called on data changes
|
|
87
|
+
onSave: PropTypes.func.isRequired, // (data) => void, data.ccsCommDefinition set when CCS create succeeded
|
|
88
|
+
onCancel: PropTypes.func.isRequired, // () => void
|
|
89
|
+
onChange: PropTypes.func, // (data) => void
|
|
92
90
|
};
|
|
93
91
|
|
|
94
92
|
CommunicationFlowContainer.defaultProps = {
|