@capillarytech/creatives-library 9.0.64 → 9.0.66
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/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +15 -8
- package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +22 -2
- package/v2Containers/CommunicationFlow/messages.js +10 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +38 -6
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +11 -1
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +94 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +79 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +108 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +59 -5
package/package.json
CHANGED
|
@@ -52,6 +52,17 @@ const formatSafe = (intl, descriptor) => (intl?.formatMessage ? intl.formatMessa
|
|
|
52
52
|
// Classic gates every field-error display on checkValidation (pre-save silence).
|
|
53
53
|
const shouldShowError = (renderContext, error) => Boolean(renderContext?.checkValidation && error);
|
|
54
54
|
|
|
55
|
+
const isEmptyValue = (value) => !value || !/\S/.test(String(value));
|
|
56
|
+
|
|
57
|
+
// Classic (Classic.js:2881-2885) only defers the "empty/required" error until
|
|
58
|
+
// Save/Update/Done (checkValidation); brace/personalization/tag errors show live
|
|
59
|
+
// as soon as they're detected. Title/Message fields need that same two-tier gate.
|
|
60
|
+
const shouldShowFieldError = (renderContext, error, value) => {
|
|
61
|
+
if (!error) return false;
|
|
62
|
+
const isEmptyRequiredError = error === true && isEmptyValue(value);
|
|
63
|
+
return isEmptyRequiredError ? Boolean(renderContext?.checkValidation) : true;
|
|
64
|
+
};
|
|
65
|
+
|
|
55
66
|
/** Classic input-case error message: personalization > brace > schema errorMessage. */
|
|
56
67
|
const resolveInputMessage = (field, value, error, renderContext) => {
|
|
57
68
|
const { intl, restrictPersonalization } = renderContext || {};
|
|
@@ -67,7 +78,7 @@ const resolveInputMessage = (field, value, error, renderContext) => {
|
|
|
67
78
|
export const MpushInputField = ({
|
|
68
79
|
field, value, error, onChange, onBlur, renderContext,
|
|
69
80
|
}) => {
|
|
70
|
-
const showError =
|
|
81
|
+
const showError = shouldShowFieldError(renderContext, error, value);
|
|
71
82
|
const message = showError ? resolveInputMessage(field, value, error, renderContext) : '';
|
|
72
83
|
return (
|
|
73
84
|
<CapColumn key={field.id} span={field.width} offset={field.offset} style={field.style || {}}>
|
|
@@ -95,13 +106,9 @@ export const MpushTextAreaField = ({
|
|
|
95
106
|
}) => {
|
|
96
107
|
let aiDisabled = true;
|
|
97
108
|
try { aiDisabled = isAiContentBotDisabled(); } catch (e) { console.error(e); aiDisabled = true; }
|
|
98
|
-
// Classic
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
// states where Classic can hold a non-false errorType, so gating display on it
|
|
102
|
-
// reproduces Classic's typing silence and also keeps the initial-hydrate
|
|
103
|
-
// errorData (computed for the parent validity emission) from leaking into the UI.
|
|
104
|
-
const showError = shouldShowError(renderContext, error);
|
|
109
|
+
// Classic shows brace/tag/personalization errors live while typing and only
|
|
110
|
+
// defers the empty/required error until Save/Update/Done (Classic.js:2881-2885).
|
|
111
|
+
const showError = shouldShowFieldError(renderContext, error, value);
|
|
105
112
|
// MPUSH keeps the inline message even though the channel is liquid-supported
|
|
106
113
|
// (Classic.js:2908-2915). Personalization gets its own message (2877-2879).
|
|
107
114
|
const inlineMessage = showError ? resolveInputMessage(field, value, error, renderContext) : '';
|
|
@@ -26,7 +26,7 @@ const kids = (el) => {
|
|
|
26
26
|
describe('MpushInputField', () => {
|
|
27
27
|
const field = { id: 'secondary-cta-0-label', width: 18, errorMessage: 'Label required' };
|
|
28
28
|
|
|
29
|
-
it('message resolution: personalization > brace > schema errorMessage; hidden without checkValidation', () => {
|
|
29
|
+
it('message resolution: personalization > brace > schema errorMessage; empty-required error hidden without checkValidation', () => {
|
|
30
30
|
const base = { field, onChange: () => {}, renderContext: { checkValidation: true, intl } };
|
|
31
31
|
expect(kids(MpushInputField({ ...base, value: '', error: true }))[0].props.errorMessage)
|
|
32
32
|
.toBe('Label required');
|
|
@@ -42,12 +42,20 @@ describe('MpushInputField', () => {
|
|
|
42
42
|
...base, value: '', error: true, renderContext: { checkValidation: false, intl },
|
|
43
43
|
}))[0].props.errorMessage).toBe('');
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
it('shows brace/tag errors live, before checkValidation (Save/Update/Done) is set', () => {
|
|
47
|
+
const el = MpushInputField({
|
|
48
|
+
field, value: 'a {{b', error: ERROR_VALUE.BRACKET, onChange: () => {}, renderContext: { checkValidation: false, intl },
|
|
49
|
+
});
|
|
50
|
+
expect(kids(el)[0].props.errorMessage).toContain('curly braces');
|
|
51
|
+
expect(kids(el)[0].props.className).toContain('error');
|
|
52
|
+
});
|
|
45
53
|
});
|
|
46
54
|
|
|
47
55
|
describe('MpushTextAreaField', () => {
|
|
48
56
|
const field = { id: 'message-editor', width: 18, errorMessage: 'Message required' };
|
|
49
57
|
|
|
50
|
-
it('inline message shows only under checkValidation; AskAira renders when AI enabled', () => {
|
|
58
|
+
it('empty-required inline message shows only under checkValidation; AskAira renders when AI enabled', () => {
|
|
51
59
|
const base = { field, onChange: jest.fn(), renderContext: { checkValidation: true, intl } };
|
|
52
60
|
expect(kids(MpushTextAreaField({ ...base, value: '', error: true }))[0].props.errorMessage)
|
|
53
61
|
.toBe('Message required');
|
|
@@ -66,6 +74,18 @@ describe('MpushTextAreaField', () => {
|
|
|
66
74
|
});
|
|
67
75
|
expect(kids(el).length).toBe(1); // textarea only, no bot
|
|
68
76
|
});
|
|
77
|
+
|
|
78
|
+
it('shows brace/tag errors live, before checkValidation (Save/Update/Done) is set', () => {
|
|
79
|
+
const el = MpushTextAreaField({
|
|
80
|
+
field,
|
|
81
|
+
value: 'hi {{name',
|
|
82
|
+
error: ERROR_VALUE.BRACKET,
|
|
83
|
+
onChange: () => {},
|
|
84
|
+
renderContext: { checkValidation: false, intl },
|
|
85
|
+
});
|
|
86
|
+
expect(kids(el)[0].props.errorMessage).toContain('curly braces');
|
|
87
|
+
expect(kids(el)[0].props.className).toContain('error-form-builder');
|
|
88
|
+
});
|
|
69
89
|
});
|
|
70
90
|
|
|
71
91
|
describe('MpushCheckboxField', () => {
|
|
@@ -334,6 +334,16 @@ export default {
|
|
|
334
334
|
id: `${prefix}.rcsAccountLabel`,
|
|
335
335
|
defaultMessage: 'RCS account',
|
|
336
336
|
},
|
|
337
|
+
/** RCS: SMS fallback domain (slidebox) — shown when the RCS content has SMS-fallback content configured */
|
|
338
|
+
fallbackSmsDomainLabel: {
|
|
339
|
+
id: `${prefix}.fallbackSmsDomainLabel`,
|
|
340
|
+
defaultMessage: 'Fallback SMS domain',
|
|
341
|
+
},
|
|
342
|
+
/** RCS: SMS fallback sender ID (slidebox + summary) */
|
|
343
|
+
fallbackSmsSenderIdLabel: {
|
|
344
|
+
id: `${prefix}.fallbackSmsSenderIdLabel`,
|
|
345
|
+
defaultMessage: 'Fallback SMS sender ID',
|
|
346
|
+
},
|
|
337
347
|
// Dynamic Controls toggle labels and descriptions
|
|
338
348
|
sendToControlCustomers: {
|
|
339
349
|
id: `${prefix}.sendToControlCustomers`,
|
package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js
CHANGED
|
@@ -20,13 +20,15 @@ import {
|
|
|
20
20
|
buildChannelSettingFromFieldValues,
|
|
21
21
|
parseChannelSettingForDisplay,
|
|
22
22
|
getWhatsappAccountName,
|
|
23
|
+
hasRcsSmsFallbackContent,
|
|
24
|
+
RCS_SMS_FALLBACK_CHANNEL,
|
|
23
25
|
} from './deliverySettingsConfig';
|
|
24
26
|
import { getDomainProperties, fetchWeCrmAccounts } from '../../../../services/api';
|
|
25
27
|
import { loadItem } from '../../../../services/localStorageApi';
|
|
26
28
|
import { CHANNELS_WITHOUT_DELIVERY } from '../../constants';
|
|
27
29
|
import messages from '../../messages';
|
|
28
30
|
import './DeliverySettingsSection.scss';
|
|
29
|
-
import { WHATSAPP } from "../../../CreativesContainer/constants";
|
|
31
|
+
import { WHATSAPP, SMS, RCS } from "../../../CreativesContainer/constants";
|
|
30
32
|
|
|
31
33
|
const DeliverySettingsSection = ({
|
|
32
34
|
contentItems = [],
|
|
@@ -60,6 +62,21 @@ const DeliverySettingsSection = ({
|
|
|
60
62
|
[contentChannels],
|
|
61
63
|
);
|
|
62
64
|
|
|
65
|
+
// RCS SMS-fallback sender ID uses SMS domain data even when SMS isn't a selected channel.
|
|
66
|
+
const hasRcsSmsFallback = useMemo(() => hasRcsSmsFallbackContent(contentItems), [contentItems]);
|
|
67
|
+
|
|
68
|
+
// Fetch SMS domain properties for fallback sender options without adding SMS as a displayed channel.
|
|
69
|
+
const deliveryFetchChannels = useMemo(() => {
|
|
70
|
+
if (!hasRcsSmsFallback || deliveryChannels.includes(SMS)) return deliveryChannels;
|
|
71
|
+
return [...deliveryChannels, SMS];
|
|
72
|
+
}, [deliveryChannels, hasRcsSmsFallback]);
|
|
73
|
+
|
|
74
|
+
// Include RCS SMS-fallback fields via a pseudo-channel without rendering an SMS section.
|
|
75
|
+
const senderDetailsChannels = useMemo(() => {
|
|
76
|
+
if (!hasRcsSmsFallback) return deliveryChannels;
|
|
77
|
+
return [...deliveryChannels, RCS_SMS_FALLBACK_CHANNEL];
|
|
78
|
+
}, [deliveryChannels, hasRcsSmsFallback]);
|
|
79
|
+
|
|
63
80
|
// Hide section when all configured channels are MPUSH, INAPP, WEBPUSH
|
|
64
81
|
const shouldShow = useMemo(() => {
|
|
65
82
|
if (contentChannels.length === 0) return false;
|
|
@@ -79,11 +96,11 @@ const DeliverySettingsSection = ({
|
|
|
79
96
|
}, [contentItems]);
|
|
80
97
|
|
|
81
98
|
// Stable key for deduplication - avoid re-fetch when array reference changes
|
|
82
|
-
const deliveryChannelKey =
|
|
99
|
+
const deliveryChannelKey = deliveryFetchChannels.slice().sort().join(',');
|
|
83
100
|
const deliveryEnabled = !!deliverySettingsData;
|
|
84
101
|
// Fetch domainProperties when content is configured (single source, deduplicated)
|
|
85
102
|
useEffect(() => {
|
|
86
|
-
if (!deliveryEnabled ||
|
|
103
|
+
if (!deliveryEnabled || deliveryFetchChannels.length === 0) {
|
|
87
104
|
setDomainPropertiesData(null);
|
|
88
105
|
lastChannelKeyRef.current = '';
|
|
89
106
|
return undefined;
|
|
@@ -102,7 +119,7 @@ const DeliverySettingsSection = ({
|
|
|
102
119
|
// uses) so both screens fetch domains for the same org unit; fall back to the logged-in
|
|
103
120
|
// user's own cached OU only when a consumer hasn't supplied one.
|
|
104
121
|
const resolvedOrgUnitId = orgUnitId ?? (loadItem('ouId') || loadItem('orgID'));
|
|
105
|
-
const response = await getDomainProperties(
|
|
122
|
+
const response = await getDomainProperties(deliveryFetchChannels, resolvedOrgUnitId);
|
|
106
123
|
if (!cancelled) {
|
|
107
124
|
const raw = response?.entity || response;
|
|
108
125
|
// Normalize channel keys to uppercase (API may return Viber, viber, etc.)
|
|
@@ -211,7 +228,12 @@ const DeliverySettingsSection = ({
|
|
|
211
228
|
let didChange = false;
|
|
212
229
|
|
|
213
230
|
deliveryChannels.forEach((channel) => {
|
|
214
|
-
|
|
231
|
+
let apiDataForChannel = channelSettingFromAPI[channel];
|
|
232
|
+
// Show RCS SMS-fallback fields only when fallback content is configured, not merely when SMS is selected.
|
|
233
|
+
if (channel === RCS && !hasRcsSmsFallback && apiDataForChannel) {
|
|
234
|
+
const { smsFallbackDomainId, smsFallbackSenderId, ...rcsWithoutFallback } = apiDataForChannel;
|
|
235
|
+
apiDataForChannel = rcsWithoutFallback;
|
|
236
|
+
}
|
|
215
237
|
const userSavedDataForChannel = savedChannelSetting[channel];
|
|
216
238
|
|
|
217
239
|
const apiHasRealSenderId = hasRealData(apiDataForChannel);
|
|
@@ -300,6 +322,16 @@ const DeliverySettingsSection = ({
|
|
|
300
322
|
</CapRow>
|
|
301
323
|
));
|
|
302
324
|
})}
|
|
325
|
+
{hasRcsSmsFallback && parsedSenderDetails?.rcsSmsFallbackSenderId && (
|
|
326
|
+
<CapRow useLegacy key="RCS-rcsSmsFallbackSenderId" type="flex" align="middle" justify="space-between" className="delivery-settings-section__field-row">
|
|
327
|
+
<CapHeading type="label4" className="delivery-settings-section__label">
|
|
328
|
+
{formatMessage(messages.fallbackSmsSenderIdLabel)}
|
|
329
|
+
</CapHeading>
|
|
330
|
+
<CapLabel type="label9" className="delivery-settings-section__value" title={parsedSenderDetails.rcsSmsFallbackSenderId}>
|
|
331
|
+
{parsedSenderDetails.rcsSmsFallbackSenderId}
|
|
332
|
+
</CapLabel>
|
|
333
|
+
</CapRow>
|
|
334
|
+
)}
|
|
303
335
|
</CapRow>
|
|
304
336
|
<CapIcon
|
|
305
337
|
type="chevron-right"
|
|
@@ -313,7 +345,7 @@ const DeliverySettingsSection = ({
|
|
|
313
345
|
<SenderDetails
|
|
314
346
|
show={showSlidebox}
|
|
315
347
|
onClose={() => setShowSlidebox(false)}
|
|
316
|
-
channels={
|
|
348
|
+
channels={senderDetailsChannels}
|
|
317
349
|
preloadedDomainProperties={entityWithWecrmViber}
|
|
318
350
|
isLoadingDomainProperties={isFetchingDomains}
|
|
319
351
|
savedFieldValues={parseChannelSettingForDisplay(deliverySetting?.channelSetting)}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
getSmsDefaultsForDomainId,
|
|
31
31
|
getRcsAccountName,
|
|
32
32
|
getEmailDomainGatewayMapIdByDomainId,
|
|
33
|
+
getRcsSmsFallbackDefaultDomain,
|
|
33
34
|
} from './deliverySettingsConfig';
|
|
34
35
|
import messages from '../../messages';
|
|
35
36
|
import './SenderDetails.scss';
|
|
@@ -114,6 +115,10 @@ const SenderDetails = ({
|
|
|
114
115
|
next.emailSenderName = defs.senderName;
|
|
115
116
|
next.emailReplyToId = defs.replyToId;
|
|
116
117
|
}
|
|
118
|
+
if (fieldKey === 'rcsSmsFallbackDomain' && entity) {
|
|
119
|
+
const defs = getSmsDefaultsForDomainId(entity, value);
|
|
120
|
+
next.rcsSmsFallbackSenderId = defs.senderId;
|
|
121
|
+
}
|
|
117
122
|
return next;
|
|
118
123
|
});
|
|
119
124
|
}, [entity]);
|
|
@@ -133,6 +138,11 @@ const SenderDetails = ({
|
|
|
133
138
|
next.emailSenderId = defs.senderId;
|
|
134
139
|
next.emailSenderName = defs.senderName;
|
|
135
140
|
next.emailReplyToId = defs.replyToId;
|
|
141
|
+
} else if (fieldKey === 'rcsSmsFallbackDomain') {
|
|
142
|
+
const defaultDomain = getRcsSmsFallbackDefaultDomain(entity);
|
|
143
|
+
const defs = getSmsDefaultsForDomainId(entity, defaultDomain);
|
|
144
|
+
next.rcsSmsFallbackDomain = defaultDomain;
|
|
145
|
+
next.rcsSmsFallbackSenderId = defs.senderId;
|
|
136
146
|
} else {
|
|
137
147
|
next[fieldKey] = getDefaultValueForField(fieldKey, entity, { fieldValues: prev });
|
|
138
148
|
}
|
|
@@ -238,7 +248,7 @@ const SenderDetails = ({
|
|
|
238
248
|
|
|
239
249
|
if (type === FIELD_TYPE.SELECT) {
|
|
240
250
|
const isSingleOption = options.length <= 1;
|
|
241
|
-
const isDomainField = fieldKey === 'smsDomain' || fieldKey === 'emailDomain';
|
|
251
|
+
const isDomainField = fieldKey === 'smsDomain' || fieldKey === 'emailDomain' || fieldKey === 'rcsSmsFallbackDomain';
|
|
242
252
|
const showEmptyOptionsError = !isDisabled && entity && options?.length === 0;
|
|
243
253
|
const emptyOptionsErrorMsg = isDomainField
|
|
244
254
|
? formatMessage(messages.domainGatewayError)
|
|
@@ -598,6 +598,100 @@ describe('DeliverySettingsSection — marketer flows', () => {
|
|
|
598
598
|
});
|
|
599
599
|
});
|
|
600
600
|
|
|
601
|
+
describe('RCS SMS-fallback sender ID (normal-flow parity)', () => {
|
|
602
|
+
const rcsWithFallbackContentItem = {
|
|
603
|
+
channel: 'RCS',
|
|
604
|
+
templateData: {
|
|
605
|
+
smsFallBackContent: { message: 'Fallback SMS body' },
|
|
606
|
+
},
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
it('fetches SMS domains too (in addition to RCS) when the RCS content has SMS-fallback content', async () => {
|
|
610
|
+
getDomainProperties.mockResolvedValue({ entity: { ...apiEntity.RCS, ...apiEntity.SMS } });
|
|
611
|
+
|
|
612
|
+
renderSection({ contentItems: [rcsWithFallbackContentItem] });
|
|
613
|
+
|
|
614
|
+
await waitFor(() => expect(getDomainProperties).toHaveBeenCalledWith(['RCS', 'SMS'], 'test-ou'));
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('does not fetch SMS domains when the RCS content has no SMS-fallback content', async () => {
|
|
618
|
+
getDomainProperties.mockResolvedValue({ entity: apiEntity.RCS });
|
|
619
|
+
|
|
620
|
+
renderSection({ contentItems: [{ channel: 'RCS' }] });
|
|
621
|
+
|
|
622
|
+
await waitFor(() => expect(getDomainProperties).toHaveBeenCalledWith(['RCS'], 'test-ou'));
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('shows a "Fallback SMS sender ID" summary row sourced from the SMS domain default sender', async () => {
|
|
626
|
+
getDomainProperties.mockResolvedValue({ entity: { ...apiEntity.RCS, ...apiEntity.SMS } });
|
|
627
|
+
|
|
628
|
+
renderSection({ contentItems: [rcsWithFallbackContentItem] });
|
|
629
|
+
|
|
630
|
+
await waitFor(() => {
|
|
631
|
+
expect(screen.getByText('Fallback SMS sender ID')).toBeInTheDocument();
|
|
632
|
+
expect(screen.getByText('+1002003001')).toBeInTheDocument();
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it('does not show the fallback summary row when the RCS content has no SMS-fallback content', async () => {
|
|
637
|
+
getDomainProperties.mockResolvedValue({ entity: apiEntity.RCS });
|
|
638
|
+
|
|
639
|
+
renderSection({ contentItems: [{ channel: 'RCS' }] });
|
|
640
|
+
|
|
641
|
+
await waitFor(() => expect(screen.getByText('Sender details')).toBeInTheDocument());
|
|
642
|
+
expect(screen.queryByText('Fallback SMS sender ID')).not.toBeInTheDocument();
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('lets the marketer pick a fallback SMS sender ID in the slidebox and persists it under channelSetting.RCS', async () => {
|
|
646
|
+
getDomainProperties.mockResolvedValue({ entity: { ...apiEntity.RCS, ...apiEntity.SMS } });
|
|
647
|
+
const onDeliverySettingChange = jest.fn();
|
|
648
|
+
|
|
649
|
+
renderSection({ contentItems: [rcsWithFallbackContentItem], onDeliverySettingChange });
|
|
650
|
+
|
|
651
|
+
await waitFor(() => expect(screen.getByText('Fallback SMS sender ID')).toBeInTheDocument());
|
|
652
|
+
onDeliverySettingChange.mockClear();
|
|
653
|
+
await openSenderDetailsFromSummary();
|
|
654
|
+
|
|
655
|
+
const slidebox = document.querySelector('.sender-details');
|
|
656
|
+
expect(within(slidebox).getByText('Fallback SMS sender ID')).toBeInTheDocument();
|
|
657
|
+
|
|
658
|
+
const combos = within(slidebox).getAllByRole('combobox');
|
|
659
|
+
// RCS domain, RCS sender ID, Fallback SMS domain, Fallback SMS sender ID
|
|
660
|
+
await userEvent.click(combos[combos.length - 1]);
|
|
661
|
+
await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
|
|
662
|
+
await userEvent.click(await findVisibleSelectOption('+1002003002'));
|
|
663
|
+
|
|
664
|
+
const saveBtn = within(slidebox).getByRole('button', { name: /save changes/i });
|
|
665
|
+
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
|
666
|
+
await userEvent.click(saveBtn);
|
|
667
|
+
|
|
668
|
+
expect(onDeliverySettingChange).toHaveBeenCalled();
|
|
669
|
+
const payload = onDeliverySettingChange.mock.calls[0][0];
|
|
670
|
+
expect(payload.channelSetting.RCS).toMatchObject({
|
|
671
|
+
smsFallbackSenderId: '+1002003002',
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('strips smsFallbackDomainId/smsFallbackSenderId from the auto-saved RCS entry when the RCS content has no SMS-fallback content, even though SMS domain data is present (SMS also selected independently)', async () => {
|
|
676
|
+
// entity.SMS leaks rcsSmsFallbackDomain/rcsSmsFallbackSenderId into channelSettingFromAPI.RCS
|
|
677
|
+
// via parseSenderDetailsFromEntity, regardless of hasRcsSmsFallback — the auto-save effect
|
|
678
|
+
// must strip them back out for RCS when there's no actual fallback content configured.
|
|
679
|
+
getDomainProperties.mockResolvedValue({ entity: { ...apiEntity.RCS, ...apiEntity.SMS } });
|
|
680
|
+
const onDeliverySettingChange = jest.fn();
|
|
681
|
+
|
|
682
|
+
renderSection({
|
|
683
|
+
contentItems: [{ channel: 'RCS' }, { channel: 'SMS' }],
|
|
684
|
+
onDeliverySettingChange,
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
await waitFor(() => expect(onDeliverySettingChange).toHaveBeenCalled());
|
|
688
|
+
const payload = onDeliverySettingChange.mock.calls[0][0];
|
|
689
|
+
expect(payload.channelSetting.RCS).not.toHaveProperty('smsFallbackDomainId');
|
|
690
|
+
expect(payload.channelSetting.RCS).not.toHaveProperty('smsFallbackSenderId');
|
|
691
|
+
expect(payload.channelSetting.RCS).toMatchObject({ senderMobNum: '+12025550123' });
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
|
|
601
695
|
describe('DeduplicationRef — skips re-fetch when data already loaded for the same channels', () => {
|
|
602
696
|
it('does not issue a second network call when the same channels are re-rendered with the same deliverySettingsData', async () => {
|
|
603
697
|
// Covers line 85-86: lastChannelKeyRef === deliveryChannelKey && domainPropertiesData already set
|
package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js
CHANGED
|
@@ -448,6 +448,85 @@ describe('SenderDetails', () => {
|
|
|
448
448
|
});
|
|
449
449
|
});
|
|
450
450
|
|
|
451
|
+
it('renders the RCS SMS-fallback domain + sender ID fields when RCS_SMS_FALLBACK_CHANNEL is included', async () => {
|
|
452
|
+
renderSenderDetails({
|
|
453
|
+
channels: ['RCS', deliverySettingsConfig.RCS_SMS_FALLBACK_CHANNEL],
|
|
454
|
+
preloadedDomainProperties: { ...ENTITIES.rcsBrand, ...ENTITIES.smsTwoSenders },
|
|
455
|
+
});
|
|
456
|
+
const root = document.querySelector('.sender-details');
|
|
457
|
+
await waitFor(() => {
|
|
458
|
+
expect(within(root).getByText('RCS Brand Co')).toBeInTheDocument();
|
|
459
|
+
expect(within(root).getByText('Fallback SMS domain')).toBeInTheDocument();
|
|
460
|
+
expect(within(root).getByText('Fallback SMS sender ID')).toBeInTheDocument();
|
|
461
|
+
expect(within(root).getByText('+1002003001')).toBeInTheDocument();
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('does not render the RCS SMS-fallback fields when the pseudo-channel is not included', async () => {
|
|
466
|
+
renderSenderDetails({
|
|
467
|
+
channels: ['RCS'],
|
|
468
|
+
preloadedDomainProperties: { ...ENTITIES.rcsBrand, ...ENTITIES.smsTwoSenders },
|
|
469
|
+
});
|
|
470
|
+
const root = document.querySelector('.sender-details');
|
|
471
|
+
await waitFor(() => expect(within(root).getByText('RCS Brand Co')).toBeInTheDocument());
|
|
472
|
+
expect(within(root).queryByText('Fallback SMS domain')).not.toBeInTheDocument();
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('changing the fallback SMS domain auto-populates the fallback sender ID', async () => {
|
|
476
|
+
renderSenderDetails({
|
|
477
|
+
channels: ['RCS', deliverySettingsConfig.RCS_SMS_FALLBACK_CHANNEL],
|
|
478
|
+
preloadedDomainProperties: { ...ENTITIES.rcsBrand, ...ENTITIES.smsTwoDomains },
|
|
479
|
+
});
|
|
480
|
+
const root = document.querySelector('.sender-details');
|
|
481
|
+
await waitFor(() => expect(within(root).getByText('Gateway A')).toBeInTheDocument());
|
|
482
|
+
|
|
483
|
+
// Combo order for ['RCS', RCS_SMS_FALLBACK_CHANNEL]: rcsAccount (disabled), fallback domain, fallback sender ID.
|
|
484
|
+
await openSelectAndChoose(root, 1, 'Gateway B');
|
|
485
|
+
await waitFor(() => expect(within(root).getByText('+222')).toBeInTheDocument());
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('resets the fallback SMS domain + sender ID back to the first SMS domain', async () => {
|
|
489
|
+
renderSenderDetails({
|
|
490
|
+
channels: ['RCS', deliverySettingsConfig.RCS_SMS_FALLBACK_CHANNEL],
|
|
491
|
+
preloadedDomainProperties: { ...ENTITIES.rcsBrand, ...ENTITIES.smsTwoDomains },
|
|
492
|
+
});
|
|
493
|
+
const root = document.querySelector('.sender-details');
|
|
494
|
+
await waitFor(() => expect(within(root).getByText('Gateway A')).toBeInTheDocument());
|
|
495
|
+
|
|
496
|
+
await openSelectAndChoose(root, 1, 'Gateway B');
|
|
497
|
+
await waitFor(() => expect(within(root).getByText('+222')).toBeInTheDocument());
|
|
498
|
+
|
|
499
|
+
const resetLinks = within(root).getAllByText('Reset');
|
|
500
|
+
fireEvent.click(resetLinks[resetLinks.length - 2]);
|
|
501
|
+
|
|
502
|
+
await waitFor(() => {
|
|
503
|
+
expect(within(root).getByText('Gateway A')).toBeInTheDocument();
|
|
504
|
+
expect(within(root).getByText('+111')).toBeInTheDocument();
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('persists the fallback SMS sender ID under rcsSmsFallbackSenderId on save', async () => {
|
|
509
|
+
const { onSave } = renderSenderDetails({
|
|
510
|
+
channels: ['RCS', deliverySettingsConfig.RCS_SMS_FALLBACK_CHANNEL],
|
|
511
|
+
preloadedDomainProperties: { ...ENTITIES.rcsBrand, ...ENTITIES.smsTwoSenders },
|
|
512
|
+
});
|
|
513
|
+
const root = document.querySelector('.sender-details');
|
|
514
|
+
await waitFor(() => expect(within(root).getByText('+1002003001')).toBeInTheDocument());
|
|
515
|
+
|
|
516
|
+
const combos = within(root).getAllByRole('combobox');
|
|
517
|
+
fireEvent.mouseDown(combos[combos.length - 1]);
|
|
518
|
+
await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument(), WAIT_OPTIONS);
|
|
519
|
+
fireEvent.click(await findVisibleSelectOption('+1002003002', WAIT_OPTIONS));
|
|
520
|
+
|
|
521
|
+
const saveBtn = within(root).getByRole('button', { name: /save changes/i });
|
|
522
|
+
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
|
523
|
+
fireEvent.click(saveBtn);
|
|
524
|
+
|
|
525
|
+
expect(onSave).toHaveBeenCalledWith(
|
|
526
|
+
expect.objectContaining({ rcsSmsFallbackSenderId: '+1002003002' }),
|
|
527
|
+
);
|
|
528
|
+
});
|
|
529
|
+
|
|
451
530
|
it('exposes parseSenderDetailsFromEntity aligned with parseEntityForDisplay', () => {
|
|
452
531
|
expect(parseSenderDetailsFromEntity(ENTITIES.smsOk)).toEqual(
|
|
453
532
|
parseEntityForDisplay(ENTITIES.smsOk, {}),
|
|
@@ -25,6 +25,10 @@ import {
|
|
|
25
25
|
buildChannelSettingFromFieldValues,
|
|
26
26
|
hasDomainGateway,
|
|
27
27
|
parseChannelSettingForDisplay,
|
|
28
|
+
RCS_SMS_FALLBACK_CHANNEL,
|
|
29
|
+
hasRcsSmsFallbackContent,
|
|
30
|
+
getRcsSmsFallbackDefaultDomain,
|
|
31
|
+
getRcsSmsFallbackBodyText,
|
|
28
32
|
} from '../deliverySettingsConfig';
|
|
29
33
|
|
|
30
34
|
/** Typical campaigns `domainProperties` entity: channel keys → list of gateway rows */
|
|
@@ -1001,6 +1005,110 @@ describe('deliverySettingsConfig — user-facing flows', () => {
|
|
|
1001
1005
|
});
|
|
1002
1006
|
});
|
|
1003
1007
|
|
|
1008
|
+
describe('RCS SMS-fallback sender ID (normal-flow parity)', () => {
|
|
1009
|
+
it('detects meaningful SMS-fallback content on the RCS contentItem (message field)', () => {
|
|
1010
|
+
expect(hasRcsSmsFallbackContent([
|
|
1011
|
+
{ channel: 'RCS', templateData: { smsFallBackContent: { message: 'Hi {{1}}' } } },
|
|
1012
|
+
])).toBe(true);
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
it('recognizes the alternate smsContent/smsTemplateContent/content keys as meaningful fallback content', () => {
|
|
1016
|
+
expect(hasRcsSmsFallbackContent([
|
|
1017
|
+
{ channel: 'RCS', templateData: { smsFallBackContent: { smsContent: 'Hi' } } },
|
|
1018
|
+
])).toBe(true);
|
|
1019
|
+
expect(hasRcsSmsFallbackContent([
|
|
1020
|
+
{ channel: 'RCS', templateData: { smsFallBackContent: { smsTemplateContent: 'Hi' } } },
|
|
1021
|
+
])).toBe(true);
|
|
1022
|
+
expect(hasRcsSmsFallbackContent([
|
|
1023
|
+
{ channel: 'RCS', templateData: { smsFallBackContent: { content: 'Hi' } } },
|
|
1024
|
+
])).toBe(true);
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
it('is false when there is no RCS content item, no smsFallBackContent, or it is only whitespace', () => {
|
|
1028
|
+
expect(hasRcsSmsFallbackContent([])).toBe(false);
|
|
1029
|
+
expect(hasRcsSmsFallbackContent([{ channel: 'SMS', templateData: {} }])).toBe(false);
|
|
1030
|
+
expect(hasRcsSmsFallbackContent([{ channel: 'RCS', templateData: {} }])).toBe(false);
|
|
1031
|
+
expect(hasRcsSmsFallbackContent([
|
|
1032
|
+
{ channel: 'RCS', templateData: { smsFallBackContent: { message: ' ' } } },
|
|
1033
|
+
])).toBe(false);
|
|
1034
|
+
expect(hasRcsSmsFallbackContent([
|
|
1035
|
+
{ channel: 'RCS', templateData: { smsFallBackContent: null } },
|
|
1036
|
+
])).toBe(false);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
it('getRcsSmsFallbackBodyText falls all the way through to "" when smsFallBackContent is an object with none of the recognized keys', () => {
|
|
1040
|
+
expect(getRcsSmsFallbackBodyText({})).toBe('');
|
|
1041
|
+
expect(getRcsSmsFallbackBodyText({ unrelatedField: 'x' })).toBe('');
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
it('defaults contentItems to [] (no args) and tolerates a null/non-array contentItems', () => {
|
|
1045
|
+
expect(hasRcsSmsFallbackContent()).toBe(false);
|
|
1046
|
+
expect(hasRcsSmsFallbackContent(null)).toBe(false);
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
it('is false when the RCS-shaped fallback content sits on an item with no channel key (falsy channel branch)', () => {
|
|
1050
|
+
expect(hasRcsSmsFallbackContent([
|
|
1051
|
+
{ templateData: { smsFallBackContent: { message: 'Hi' } } },
|
|
1052
|
+
])).toBe(false);
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
it('exposes fallback domain + sender ID fields only when RCS_SMS_FALLBACK_CHANNEL is requested', () => {
|
|
1056
|
+
const withoutFallback = getFieldsForChannels(['RCS']).map((f) => f.fieldKey);
|
|
1057
|
+
expect(withoutFallback).not.toContain('rcsSmsFallbackDomain');
|
|
1058
|
+
expect(withoutFallback).not.toContain('rcsSmsFallbackSenderId');
|
|
1059
|
+
|
|
1060
|
+
const withFallback = getFieldsForChannels(['RCS', RCS_SMS_FALLBACK_CHANNEL]).map((f) => f.fieldKey);
|
|
1061
|
+
expect(withFallback).toEqual(expect.arrayContaining(['rcsSmsFallbackDomain', 'rcsSmsFallbackSenderId']));
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
it('sources fallback sender ID options from SMS domain data, filtered by the selected fallback domain', () => {
|
|
1065
|
+
const senderField = getFieldsForChannels([RCS_SMS_FALLBACK_CHANNEL]).find((f) => f.fieldKey === 'rcsSmsFallbackSenderId');
|
|
1066
|
+
const opts = senderField.getOptions(domainPropertiesFromApi, { fieldValues: { rcsSmsFallbackDomain: 's2' } });
|
|
1067
|
+
expect(opts.map((o) => o.value)).toEqual(['+2002']);
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
it('sources fallback sender ID options from the first SMS domain when no context is passed at all (default context)', () => {
|
|
1071
|
+
const senderField = getFieldsForChannels([RCS_SMS_FALLBACK_CHANNEL]).find((f) => f.fieldKey === 'rcsSmsFallbackSenderId');
|
|
1072
|
+
const opts = senderField.getOptions(domainPropertiesFromApi);
|
|
1073
|
+
expect(opts.map((o) => o.value)).toContain('+1001');
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
it('defaults the fallback domain to the first SMS domain (for Reset)', () => {
|
|
1077
|
+
expect(getRcsSmsFallbackDefaultDomain(domainPropertiesFromApi)).toBe('s1');
|
|
1078
|
+
expect(getRcsSmsFallbackDefaultDomain(null)).toBe('');
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
it('parseEntityForDisplay includes the fallback domain/sender ID sourced from SMS domain data', () => {
|
|
1082
|
+
const summary = parseEntityForDisplay(domainPropertiesFromApi);
|
|
1083
|
+
expect(summary.rcsSmsFallbackDomain).toBe('s1');
|
|
1084
|
+
expect(summary.rcsSmsFallbackSenderId).toBe('+1001');
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
it('buildChannelSettingFromFieldValues nests fallback domain/sender ID under RCS alongside the RCS sender number', () => {
|
|
1088
|
+
const saved = buildChannelSettingFromFieldValues({
|
|
1089
|
+
rcsSenderNumber: '+r',
|
|
1090
|
+
rcsSmsFallbackDomain: 's2',
|
|
1091
|
+
rcsSmsFallbackSenderId: '+2002',
|
|
1092
|
+
});
|
|
1093
|
+
expect(saved).toEqual({
|
|
1094
|
+
RCS: {
|
|
1095
|
+
senderMobNum: '+r',
|
|
1096
|
+
rcsSender: '+r',
|
|
1097
|
+
smsFallbackDomainId: 's2',
|
|
1098
|
+
smsFallbackSenderId: '+2002',
|
|
1099
|
+
},
|
|
1100
|
+
});
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
it('parseChannelSettingForDisplay hydrates the fallback domain/sender ID back from saved channelSetting', () => {
|
|
1104
|
+
const flat = parseChannelSettingForDisplay({
|
|
1105
|
+
RCS: { rcsSender: '+r', smsFallbackDomainId: 's2', smsFallbackSenderId: '+2002' },
|
|
1106
|
+
});
|
|
1107
|
+
expect(flat.rcsSmsFallbackDomain).toBe('s2');
|
|
1108
|
+
expect(flat.rcsSmsFallbackSenderId).toBe('+2002');
|
|
1109
|
+
});
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1004
1112
|
describe('getViberAccountOptions — synthetic fallback prepend', () => {
|
|
1005
1113
|
it('prepends a synthetic entry when the saved viberAccount is absent from API options (stale saved domain)', () => {
|
|
1006
1114
|
// Triggers the prepend path: context.fieldValues.viberAccount is not present in API options
|
|
@@ -120,14 +120,16 @@ const getSmsDomainValue = (entity) => {
|
|
|
120
120
|
return smsData.domainProperties?.id ?? smsData.domainId ?? smsData.id ?? '';
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
-
// Sender ID
|
|
124
|
-
const
|
|
125
|
-
const selectedDomainId = context?.fieldValues?.
|
|
123
|
+
// Sender ID options restricted to the selected domain; domainFieldKey supports alternate domain field keys.
|
|
124
|
+
const getSmsSenderIdOptionsForDomainField = (entity, context = {}, domainFieldKey = 'smsDomain') => {
|
|
125
|
+
const selectedDomainId = context?.fieldValues?.[domainFieldKey];
|
|
126
126
|
const domainData = selectedDomainId ? getSmsDomainById(entity, selectedDomainId) : getSmsData(entity);
|
|
127
127
|
if (!domainData) return [];
|
|
128
128
|
return getOptionsFromContactInfo(domainData, 'gsm_sender_id', ['cdma_sender_id']);
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
+
const getSmsSenderIdOptions = (entity, context = {}) => getSmsSenderIdOptionsForDomainField(entity, context, 'smsDomain');
|
|
132
|
+
|
|
131
133
|
const getSmsSenderIdValue = (entity) => {
|
|
132
134
|
const smsData = getSmsData(entity);
|
|
133
135
|
return getValueFromContactInfo(smsData, 'gsm_sender_id', ['cdma_sender_id']);
|
|
@@ -359,6 +361,32 @@ export const getRcsAccountName = (entity) => {
|
|
|
359
361
|
return rcsData.domainProperties?.domainName || rcsData.domainName || rcsData.label || '';
|
|
360
362
|
};
|
|
361
363
|
|
|
364
|
+
// RCS SMS-fallback fields use SMS domain data and a pseudo-channel to include fallback fields in sender details.
|
|
365
|
+
export const RCS_SMS_FALLBACK_CHANNEL = 'RCS_SMS_FALLBACK';
|
|
366
|
+
|
|
367
|
+
/** Extract the SMS-fallback body text from an RCS templateData.smsFallBackContent record (CreativesContainer shape). */
|
|
368
|
+
export const getRcsSmsFallbackBodyText = (smsFallBackContent) => {
|
|
369
|
+
if (!smsFallBackContent || typeof smsFallBackContent !== 'object') return '';
|
|
370
|
+
return String(
|
|
371
|
+
smsFallBackContent.message
|
|
372
|
+
?? smsFallBackContent.smsContent
|
|
373
|
+
?? smsFallBackContent.smsTemplateContent
|
|
374
|
+
?? smsFallBackContent.content
|
|
375
|
+
?? '',
|
|
376
|
+
).trim();
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
/** True when contentItems has an RCS item whose templateData.smsFallBackContent has real body text. */
|
|
380
|
+
export const hasRcsSmsFallbackContent = (contentItems = []) => {
|
|
381
|
+
const rcsItem = (contentItems || []).find((item) => (item?.channel || '')?.toUpperCase() === RCS);
|
|
382
|
+
return getRcsSmsFallbackBodyText(rcsItem?.templateData?.smsFallBackContent) !== '';
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/** Default fallback SMS domain (first SMS domain option, same source as the SMS channel field) - for Reset. */
|
|
386
|
+
export const getRcsSmsFallbackDefaultDomain = (entity) => getSmsDomainOptions(entity)?.[0]?.value ?? getSmsDomainValue(entity) ?? '';
|
|
387
|
+
|
|
388
|
+
const getRcsSmsFallbackSenderIdOptions = (entity, context = {}) => getSmsSenderIdOptionsForDomainField(entity, context, 'rcsSmsFallbackDomain');
|
|
389
|
+
|
|
362
390
|
|
|
363
391
|
// --- VIBER account (disabled) + sender ID ---
|
|
364
392
|
// Aligned with adiona-ui/cap-campaigns-v2: domainId + sender (VIBER_SENDER_ID = 'sender')
|
|
@@ -524,7 +552,24 @@ export const DELIVERY_SETTINGS_FIELDS = [
|
|
|
524
552
|
getValue: () => '',
|
|
525
553
|
getOptions: () => [],
|
|
526
554
|
},
|
|
527
|
-
// RCS sender number
|
|
555
|
+
// RCS sender number is display-only;
|
|
556
|
+
// SMS-fallback domain/sender ID render only when fallback content is configured.
|
|
557
|
+
{
|
|
558
|
+
channel: RCS_SMS_FALLBACK_CHANNEL,
|
|
559
|
+
fieldKey: 'rcsSmsFallbackDomain',
|
|
560
|
+
messageKey: 'fallbackSmsDomainLabel',
|
|
561
|
+
type: FIELD_TYPE.SELECT,
|
|
562
|
+
getValue: getSmsDomainValue,
|
|
563
|
+
getOptions: getSmsDomainOptions,
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
channel: RCS_SMS_FALLBACK_CHANNEL,
|
|
567
|
+
fieldKey: 'rcsSmsFallbackSenderId',
|
|
568
|
+
messageKey: 'fallbackSmsSenderIdLabel',
|
|
569
|
+
type: FIELD_TYPE.SELECT,
|
|
570
|
+
getValue: getSmsSenderIdValue,
|
|
571
|
+
getOptions: (entity, context) => getRcsSmsFallbackSenderIdOptions(entity, context),
|
|
572
|
+
},
|
|
528
573
|
// VIBER - Viber account (disabled) + Sender ID (select with Reset) - per Figma
|
|
529
574
|
{
|
|
530
575
|
channel: 'VIBER',
|
|
@@ -594,6 +639,9 @@ export const parseEntityForDisplay = (entity, context = {}) => {
|
|
|
594
639
|
whatsappAccountName: getWhatsappAccountName(entity, whatsappSourceAccountId),
|
|
595
640
|
rcsSenderNumber: getRcsSenderNumberValue(entity),
|
|
596
641
|
rcsAccountName: getRcsAccountName(entity),
|
|
642
|
+
// RCS SMS-fallback: sourced from the SMS domain data (same as the SMS channel's own fields).
|
|
643
|
+
rcsSmsFallbackDomain: getSmsDomainValue(entity),
|
|
644
|
+
rcsSmsFallbackSenderId: getSmsSenderIdValue(entity),
|
|
597
645
|
senderNumber: getWhatsappSenderNumberValue(entity, { whatsappSourceAccountId }) || getRcsSenderNumberValue(entity),
|
|
598
646
|
};
|
|
599
647
|
};
|
|
@@ -625,10 +673,14 @@ export const buildChannelSettingFromFieldValues = (fieldValues = {}) => {
|
|
|
625
673
|
senderMobNum: fieldValues.whatsappSenderNumber,
|
|
626
674
|
};
|
|
627
675
|
}
|
|
628
|
-
if (fieldValues.rcsSenderNumber != null || fieldValues.rcsAccount != null
|
|
676
|
+
if (fieldValues.rcsSenderNumber != null || fieldValues.rcsAccount != null
|
|
677
|
+
|| fieldValues.rcsSmsFallbackDomain != null || fieldValues.rcsSmsFallbackSenderId != null) {
|
|
629
678
|
channelSetting.RCS = {
|
|
630
679
|
senderMobNum: fieldValues.rcsSenderNumber,
|
|
631
680
|
rcsSender: fieldValues.rcsSenderNumber,
|
|
681
|
+
// SMS-fallback sender ID/domain shown only when RCS SMS-fallback content is configured.
|
|
682
|
+
...(fieldValues.rcsSmsFallbackDomain != null && { smsFallbackDomainId: fieldValues.rcsSmsFallbackDomain }),
|
|
683
|
+
...(fieldValues.rcsSmsFallbackSenderId != null && { smsFallbackSenderId: fieldValues.rcsSmsFallbackSenderId }),
|
|
632
684
|
};
|
|
633
685
|
}
|
|
634
686
|
// VIBER: aligned with adiona-ui (domainId + sender), campaigns-v2 (gsmSenderId/senderViber)
|
|
@@ -697,6 +749,8 @@ export const parseChannelSettingForDisplay = (channelSetting = {}) => {
|
|
|
697
749
|
}
|
|
698
750
|
if (channelSetting.RCS) {
|
|
699
751
|
out.rcsSenderNumber = channelSetting.RCS.senderMobNum || channelSetting.RCS.rcsSender;
|
|
752
|
+
out.rcsSmsFallbackDomain = channelSetting.RCS.smsFallbackDomainId;
|
|
753
|
+
out.rcsSmsFallbackSenderId = channelSetting.RCS.smsFallbackSenderId;
|
|
700
754
|
}
|
|
701
755
|
// VIBER: read sender (adiona) or viberSenderId/gsmSenderId (campaigns compat)
|
|
702
756
|
if (channelSetting.VIBER) {
|