@capillarytech/creatives-library 9.0.54 → 9.0.55-alpha.0
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 +0 -1
- package/package.json +1 -1
- package/services/api.js +20 -0
- package/utils/common.js +0 -4
- package/v2Components/CommonTestAndPreview/index.js +1 -1
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +118 -56
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +1 -1
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +22 -6
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +421 -49
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +18 -2
- package/v2Containers/CommunicationFlow/constants.js +90 -1
- package/v2Containers/CommunicationFlow/index.js +15 -20
- package/v2Containers/CommunicationFlow/messages.js +4 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +6 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +25 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +24 -5
- package/v2Containers/CreativesContainer/index.js +1 -1
package/constants/unified.js
CHANGED
|
@@ -63,7 +63,6 @@ export const ENABLE_AI_SUGGESTIONS = 'ENABLE_AI_SUGGESTIONS';
|
|
|
63
63
|
export const AI_CONTENT_BOT_DISABLED = 'AI_CONTENT_BOT_DISABLED';
|
|
64
64
|
export const AI_DOCUMENTATION_BOT_ENABLED = 'AI_DOCUMENTATION_BOT_ENABLED';
|
|
65
65
|
export const ENABLE_PRODUCT_SUPPORT_VIDEOS = 'ENABLE_PRODUCT_SUPPORT_VIDEOS';
|
|
66
|
-
export const SUPPORT_ENGAGEMENT_MODULE = 'SUPPORT_ENGAGEMENT_MODULE';
|
|
67
66
|
export const EMBEDDED = 'embedded';
|
|
68
67
|
// --- Tag/Validation Constants ---
|
|
69
68
|
export const CARD_RELATED_TAGS = [
|
package/package.json
CHANGED
package/services/api.js
CHANGED
|
@@ -701,6 +701,26 @@ 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 (new Veyron /api/v1/commdefinition surface, proxied
|
|
705
|
+
// through cap-creatives-api's /comm-definitions). Used by CommunicationFlow's
|
|
706
|
+
// own Save action — separate from the legacy messageMeta functions above,
|
|
707
|
+
// which remain in use by CreativesContainer's own save flow (Cap/sagas.js).
|
|
708
|
+
export const createCommDefinition = (payload) => {
|
|
709
|
+
const url = `${API_ENDPOINT}/comm-definitions`;
|
|
710
|
+
return request(url, getAPICallObject('POST', payload, false, false, false, true));
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// Opens a new DRAFT version with the given content on an EXISTING
|
|
714
|
+
// CommDefinition (same id/referenceId) — used by CommunicationFlow's edit-mode
|
|
715
|
+
// save (re-editing a rejected alert) instead of createCommDefinition, so the
|
|
716
|
+
// alert keeps its identity rather than becoming a brand-new CommDefinition.
|
|
717
|
+
// Route verified against a real Postman sample against Veyron's own
|
|
718
|
+
// /commdefinition/{id}/edit endpoint.
|
|
719
|
+
export const editCommDefinition = (commDefinitionId, payload) => {
|
|
720
|
+
const url = `${API_ENDPOINT}/comm-definitions/${commDefinitionId}/edit`;
|
|
721
|
+
return request(url, getAPICallObject('POST', payload, false, false, false, true));
|
|
722
|
+
};
|
|
723
|
+
|
|
704
724
|
export const getCentralCommsMetaIds = (metaIds, metaType = TRANSACTION) => {
|
|
705
725
|
const url = `${API_ENDPOINT}/common/central-comms/meta-id/${metaType}?metaIds=${metaIds}`;
|
|
706
726
|
return request(url, getAPICallObject('GET', null, false, false, false, true));
|
package/utils/common.js
CHANGED
|
@@ -99,10 +99,6 @@ export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
|
|
|
99
99
|
null,
|
|
100
100
|
SUPPORT_CK_EDITOR,
|
|
101
101
|
);
|
|
102
|
-
export const hasSupportEngagementModule = Auth.hasFeatureAccess.bind(
|
|
103
|
-
null,
|
|
104
|
-
SUPPORT_ENGAGEMENT_MODULE,
|
|
105
|
-
);
|
|
106
102
|
|
|
107
103
|
export const hasGiftVoucherFeature = Auth.hasFeatureAccess.bind(
|
|
108
104
|
null,
|
|
@@ -17,10 +17,11 @@ import { createStructuredSelector } from 'reselect';
|
|
|
17
17
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
18
18
|
import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
|
|
19
19
|
import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
20
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
20
21
|
// import injectSaga from '../../utils/injectSaga'; // cap-coupons flows disabled
|
|
21
22
|
// import injectReducer from '../../utils/injectReducer';
|
|
22
23
|
import { makeSelectAuthenticated } from '../Cap/selectors';
|
|
23
|
-
import {
|
|
24
|
+
import { createCommDefinition, editCommDefinition } from '../../services/api';
|
|
24
25
|
import DynamicControlsStep from './steps/DynamicControlsStep';
|
|
25
26
|
import MessageTypeStep from './steps/MessageTypeStep';
|
|
26
27
|
import CommunicationStrategyStep from './steps/CommunicationStrategyStep';
|
|
@@ -35,9 +36,11 @@ import {
|
|
|
35
36
|
CHANNELS,
|
|
36
37
|
INCENTIVE_TYPES,
|
|
37
38
|
DYNAMIC_CONTROLS_CONFIG,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
CCS_STRATEGY_TYPE_SINGLE,
|
|
40
|
+
CCS_CHANNEL_CONTENT_KEY_MAP,
|
|
41
|
+
CCS_CHANNEL_DELIVERY_KEY_MAP,
|
|
42
|
+
CCS_CHANNEL_NAME_MAP,
|
|
43
|
+
CCS_CONTENT_TRANSFORMS,
|
|
41
44
|
} from './constants';
|
|
42
45
|
import { getEnabledSteps } from './utils/getEnabledSteps';
|
|
43
46
|
import messages from './messages';
|
|
@@ -64,6 +67,25 @@ const hasDeliverySettingForChannel = (channelSetting, channel) => {
|
|
|
64
67
|
return !!settings && Object.values(settings).some((v) => v !== null && v !== '' && v !== undefined);
|
|
65
68
|
};
|
|
66
69
|
|
|
70
|
+
// createCommDefinition resolves (rather than rejects) with the CCS error envelope
|
|
71
|
+
// for 4xx/5xx responses — see api.js's request()/checkStatus. A duplicate
|
|
72
|
+
// referenceId within the org comes back as this specific 409.
|
|
73
|
+
const isDuplicateReferenceIdError = (res) => res?.success === false && res?.status?.message === 'REFERENCE_ID_EXISTS';
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* CCS requires referenceId on create; the consumer-supplied Alert/comm name is
|
|
77
|
+
* mandatory but referenceId is optional in every consumer's own form. Generate
|
|
78
|
+
* a stable fallback from the name rather than failing the save.
|
|
79
|
+
*/
|
|
80
|
+
const buildCcsReferenceId = (name) => {
|
|
81
|
+
const slug = (name || 'COMM')
|
|
82
|
+
.trim()
|
|
83
|
+
.toUpperCase()
|
|
84
|
+
.replace(/[^A-Z0-9]+/g, '_')
|
|
85
|
+
.replace(/^_+|_+$/g, '') || 'COMM';
|
|
86
|
+
return `${slug}_${Date.now()}`;
|
|
87
|
+
};
|
|
88
|
+
|
|
67
89
|
const CommunicationFlow = ({
|
|
68
90
|
config,
|
|
69
91
|
initialData,
|
|
@@ -91,6 +113,7 @@ const CommunicationFlow = ({
|
|
|
91
113
|
};
|
|
92
114
|
});
|
|
93
115
|
const [validationErrors, setValidationErrors] = useState({});
|
|
116
|
+
const [saveError, setSaveError] = useState(null);
|
|
94
117
|
|
|
95
118
|
// Memoize enabled steps
|
|
96
119
|
const enabledSteps = useMemo(() => getEnabledSteps(config), [config]);
|
|
@@ -150,74 +173,108 @@ const CommunicationFlow = ({
|
|
|
150
173
|
}, []);
|
|
151
174
|
|
|
152
175
|
const handleSave = useCallback(async () => {
|
|
176
|
+
setSaveError(null);
|
|
153
177
|
const aggregatedData = getAggregatedData();
|
|
154
178
|
const shouldUseCCS = config?.useCCS !== false;
|
|
179
|
+
let ccsCommDefinition = null;
|
|
155
180
|
|
|
156
181
|
if (shouldUseCCS) {
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
182
|
+
const isMultiChannel = [CHANNEL_PRIORITY, AB_TEST].includes(aggregatedData.communicationStrategy);
|
|
183
|
+
const contentItem = (aggregatedData.contentItems || [])[0];
|
|
184
|
+
// Consumer-supplied name (e.g. CapNotify's Alert Name field) — CommunicationFlow
|
|
185
|
+
// has no name input of its own, so this is mandatory input from config.context.
|
|
186
|
+
const name = config?.context?.name;
|
|
160
187
|
|
|
161
|
-
|
|
162
|
-
|
|
188
|
+
// CCS create is SINGLE-strategy only this phase (D1); CHANNEL_PRIORITY/AB_TEST
|
|
189
|
+
// carry multiple content items with no CCS equivalent yet.
|
|
190
|
+
if (!isMultiChannel && contentItem && name) {
|
|
191
|
+
const rawChannel = (contentItem.channel || '').toUpperCase();
|
|
192
|
+
const channel = CCS_CHANNEL_NAME_MAP[rawChannel] || rawChannel;
|
|
193
|
+
const contentKey = CCS_CHANNEL_CONTENT_KEY_MAP[channel];
|
|
194
|
+
const deliveryKey = CCS_CHANNEL_DELIVERY_KEY_MAP[channel];
|
|
195
|
+
const contentTransform = CCS_CONTENT_TRANSFORMS[channel];
|
|
196
|
+
const contentPayload = contentTransform ? contentTransform(contentItem.templateData) : contentItem.templateData;
|
|
197
|
+
const { dynamicControls = {} } = aggregatedData;
|
|
198
|
+
const channelSettings = aggregatedData.deliverySetting?.channelSetting?.[channel] || {};
|
|
199
|
+
const referenceId = config?.context?.referenceId || buildCcsReferenceId(name);
|
|
163
200
|
|
|
164
|
-
|
|
165
|
-
|
|
201
|
+
const payload = {
|
|
202
|
+
referenceId,
|
|
203
|
+
name,
|
|
204
|
+
description: config?.context?.description || undefined,
|
|
205
|
+
strategyType: CCS_STRATEGY_TYPE_SINGLE,
|
|
206
|
+
settings: {
|
|
207
|
+
additionalSettings: {
|
|
208
|
+
useTinyUrl: dynamicControls.useTinyUrl ?? false,
|
|
209
|
+
encryptUrl: dynamicControls.sendToControlCustomers ?? false,
|
|
210
|
+
linkTrackingEnabled: dynamicControls.overrideDailyLimit ?? false,
|
|
211
|
+
userSubscriptionDisabled: dynamicControls.sendToBrandPocs ?? false,
|
|
212
|
+
},
|
|
213
|
+
executionParams: {},
|
|
214
|
+
},
|
|
215
|
+
singleChannelStrategy: {
|
|
216
|
+
variant: {
|
|
217
|
+
channel,
|
|
218
|
+
// `channel` spreads LAST in both objects below — contentItem.templateData
|
|
219
|
+
// (e.g. mobile push) carries the UI's own internal channel identifier
|
|
220
|
+
// (MOBILEPUSH), which must never win over CCS's normalized enum value
|
|
221
|
+
// (PUSH) once spread order puts it after.
|
|
222
|
+
...(contentKey && { [contentKey]: { ...contentPayload, channel } }),
|
|
223
|
+
...(deliveryKey && { [deliveryKey]: { channelSettings: { ...channelSettings, channel } } }),
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
};
|
|
166
227
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
userSubscriptionDisabled: dynamicControls.sendToBrandPocs ?? false,
|
|
172
|
-
};
|
|
228
|
+
// Editing an existing (e.g. rejected) alert: config.context.existingCommDefinitionId
|
|
229
|
+
// opens a new DRAFT version on that SAME CommDefinition instead of creating a
|
|
230
|
+
// brand-new one, so the alert keeps its id/referenceId across the edit.
|
|
231
|
+
const { existingCommDefinitionId } = config?.context || {};
|
|
173
232
|
|
|
174
|
-
if (contentItems.length > 0) {
|
|
175
233
|
try {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
}),
|
|
200
|
-
},
|
|
234
|
+
const res = existingCommDefinitionId
|
|
235
|
+
? await editCommDefinition(existingCommDefinitionId, {
|
|
236
|
+
strategyType: payload.strategyType,
|
|
237
|
+
settings: payload.settings,
|
|
238
|
+
singleChannelStrategy: payload.singleChannelStrategy,
|
|
239
|
+
})
|
|
240
|
+
: await createCommDefinition(payload);
|
|
241
|
+
if (isDuplicateReferenceIdError(res)) {
|
|
242
|
+
// Duplicate referenceId in this org — block the save so the user can
|
|
243
|
+
// change it, rather than silently proceeding without a CCS comm.
|
|
244
|
+
setSaveError(formatMessage(messages.duplicateReferenceIdError));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const data = res?.response?.data;
|
|
248
|
+
if (existingCommDefinitionId) {
|
|
249
|
+
if (data) {
|
|
250
|
+
ccsCommDefinition = {
|
|
251
|
+
id: existingCommDefinitionId,
|
|
252
|
+
referenceId: referenceId || data.referenceId,
|
|
253
|
+
version: data.version?.version ?? data.version ?? 1,
|
|
254
|
+
status: data.status,
|
|
201
255
|
};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (metaIds) {
|
|
212
|
-
const getResponse = await getCentralCommsMetaIds(metaIds);
|
|
256
|
+
}
|
|
257
|
+
} else if (data?.id) {
|
|
258
|
+
ccsCommDefinition = {
|
|
259
|
+
id: data.id,
|
|
260
|
+
referenceId: data.referenceId,
|
|
261
|
+
version: data.version?.version ?? 1,
|
|
262
|
+
status: data.status,
|
|
263
|
+
};
|
|
213
264
|
}
|
|
214
265
|
} catch (error) {
|
|
215
|
-
console.error('[CommunicationFlow] CCS
|
|
266
|
+
console.error('[CommunicationFlow] CCS createCommDefinition error:', error);
|
|
216
267
|
}
|
|
268
|
+
} else if (!name) {
|
|
269
|
+
console.warn('[CommunicationFlow] Skipping CCS createCommDefinition — config.context.name is required');
|
|
270
|
+
} else if (isMultiChannel) {
|
|
271
|
+
console.warn('[CommunicationFlow] Skipping CCS createCommDefinition — CHANNEL_PRIORITY/AB_TEST strategies are not supported by CCS create yet (SINGLE only)');
|
|
272
|
+
} else if (!contentItem) {
|
|
273
|
+
console.warn('[CommunicationFlow] Skipping CCS createCommDefinition — no content item found (contentItems is empty)');
|
|
217
274
|
}
|
|
218
275
|
}
|
|
219
276
|
|
|
220
|
-
onSave(aggregatedData);
|
|
277
|
+
onSave(ccsCommDefinition ? { ...aggregatedData, ccsCommDefinition } : aggregatedData);
|
|
221
278
|
}, [getAggregatedData, config, onSave]);
|
|
222
279
|
|
|
223
280
|
// Call onChange callback when stepData changes
|
|
@@ -317,6 +374,11 @@ const CommunicationFlow = ({
|
|
|
317
374
|
{renderSteps()}
|
|
318
375
|
{onSave && (
|
|
319
376
|
<CapRow useLegacy className="communication-flow-container__footer">
|
|
377
|
+
{saveError && (
|
|
378
|
+
<CapLabel type="label2" className="communication-flow-container__save-error">
|
|
379
|
+
{saveError}
|
|
380
|
+
</CapLabel>
|
|
381
|
+
)}
|
|
320
382
|
<CapButton type="primary" onClick={handleSave} disabled={isSaveDisabled}>
|
|
321
383
|
{formatMessage(messages.save)}
|
|
322
384
|
</CapButton>
|
|
@@ -19,6 +19,7 @@ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
|
19
19
|
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
20
20
|
import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
21
21
|
import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
|
|
22
|
+
import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
|
|
22
23
|
import CommunicationFlow from './index';
|
|
23
24
|
import { CHANNELS, DEFAULT_COMMUNICATION_STRATEGY_OPTIONS, DYNAMIC_CONTROLS_CONFIG } from './constants';
|
|
24
25
|
import {
|
|
@@ -41,7 +42,7 @@ const resolveValue = (val) => {
|
|
|
41
42
|
|
|
42
43
|
const SENDER_ID_RESOLVERS = {
|
|
43
44
|
[SMS]: (setting) => resolveValue(setting?.gsmSenderId),
|
|
44
|
-
[EMAIL]: (setting) => resolveValue(setting?.
|
|
45
|
+
[EMAIL]: (setting) => resolveValue(setting?.senderId),
|
|
45
46
|
[VIBER]: (setting) => resolveValue(setting?.sender) || resolveValue(setting?.gsmSenderId),
|
|
46
47
|
[WHATSAPP]: (setting) => resolveValue(setting?.senderMobNum),
|
|
47
48
|
[RCS]: (setting) => resolveValue(setting?.senderMobNum) || resolveValue(setting?.rcsSender),
|
|
@@ -74,6 +75,8 @@ const CommunicationFlowCard = ({
|
|
|
74
75
|
onChange,
|
|
75
76
|
cap,
|
|
76
77
|
intl,
|
|
78
|
+
disabled,
|
|
79
|
+
disabledTooltip,
|
|
77
80
|
}) => {
|
|
78
81
|
const { formatMessage } = intl || {};
|
|
79
82
|
const [showSlideBox, setShowSlideBox] = useState(false);
|
|
@@ -91,8 +94,9 @@ const CommunicationFlowCard = ({
|
|
|
91
94
|
}, [onCancel]);
|
|
92
95
|
|
|
93
96
|
const handleOpen = useCallback(() => {
|
|
97
|
+
if (disabled) return;
|
|
94
98
|
setShowSlideBox(true);
|
|
95
|
-
}, []);
|
|
99
|
+
}, [disabled]);
|
|
96
100
|
|
|
97
101
|
const firstItem = savedData?.contentItems?.[0];
|
|
98
102
|
const channelConfig = firstItem
|
|
@@ -182,9 +186,17 @@ const CommunicationFlowCard = ({
|
|
|
182
186
|
<CapImage src={addCreativesIllustration} />
|
|
183
187
|
</CapColumn>
|
|
184
188
|
<CapColumn span={14} className="empty-card-action-col">
|
|
185
|
-
|
|
186
|
-
{
|
|
187
|
-
|
|
189
|
+
{disabled && disabledTooltip ? (
|
|
190
|
+
<CapTooltip title={disabledTooltip}>
|
|
191
|
+
<CapButton type="secondary" disabled>
|
|
192
|
+
{formatMessage(messages.addCreatives)}
|
|
193
|
+
</CapButton>
|
|
194
|
+
</CapTooltip>
|
|
195
|
+
) : (
|
|
196
|
+
<CapButton type="secondary" onClick={handleOpen} disabled={disabled}>
|
|
197
|
+
{formatMessage(messages.addCreatives)}
|
|
198
|
+
</CapButton>
|
|
199
|
+
)}
|
|
188
200
|
</CapColumn>
|
|
189
201
|
</CapRow>
|
|
190
202
|
</CapCard>
|
|
@@ -196,7 +208,7 @@ const CommunicationFlowCard = ({
|
|
|
196
208
|
<CapSlideBox
|
|
197
209
|
show={showSlideBox}
|
|
198
210
|
handleClose={handleClose}
|
|
199
|
-
size="size-
|
|
211
|
+
size="size-l"
|
|
200
212
|
header={(
|
|
201
213
|
<CapHeader
|
|
202
214
|
title={formatMessage(messages.addMessage)}
|
|
@@ -227,6 +239,8 @@ CommunicationFlowCard.propTypes = {
|
|
|
227
239
|
onChange: PropTypes.func,
|
|
228
240
|
cap: PropTypes.object,
|
|
229
241
|
intl: PropTypes.object.isRequired,
|
|
242
|
+
disabled: PropTypes.bool, // Disables the "Add creatives" trigger (e.g. until a consumer-required field is filled)
|
|
243
|
+
disabledTooltip: PropTypes.node, // Shown on hover when disabled is true
|
|
230
244
|
};
|
|
231
245
|
|
|
232
246
|
CommunicationFlowCard.defaultProps = {
|
|
@@ -235,6 +249,8 @@ CommunicationFlowCard.defaultProps = {
|
|
|
235
249
|
onCancel: null,
|
|
236
250
|
onChange: null,
|
|
237
251
|
cap: null,
|
|
252
|
+
disabled: false,
|
|
253
|
+
disabledTooltip: null,
|
|
238
254
|
};
|
|
239
255
|
|
|
240
256
|
export default injectIntl(CommunicationFlowCard);
|