@capillarytech/creatives-library 8.0.329 → 8.0.330-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 +4 -0
- package/package.json +1 -1
- package/utils/commonUtils.js +19 -1
- package/utils/templateVarUtils.js +35 -6
- package/utils/tests/templateVarUtils.test.js +44 -0
- package/v2Components/CommonTestAndPreview/index.js +49 -57
- package/v2Components/SmsFallback/smsFallbackUtils.js +14 -3
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +16 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +5 -0
- package/v2Containers/CreativesContainer/index.js +9 -3
- package/v2Containers/Rcs/constants.js +6 -2
- package/v2Containers/Rcs/index.js +218 -83
- package/v2Containers/Rcs/messages.js +2 -1
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +20 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +484 -4
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +67 -0
- package/v2Containers/Rcs/tests/utils.test.js +56 -0
- package/v2Containers/Rcs/utils.js +53 -6
- package/v2Containers/SmsTrai/Edit/index.js +27 -0
- package/v2Containers/SmsTrai/Edit/messages.js +5 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
package/constants/unified.js
CHANGED
|
@@ -168,6 +168,10 @@ export const COMBINED_SMS_TEMPLATE_VAR_REGEX = /\{\{[^}]+\}\}|\{\#[^#]*\#\}/g;
|
|
|
168
168
|
export const MUSTACHE_VAR_TOKEN_FULL_STRING_REGEX = /^\{\{[^}]+\}\}$/;
|
|
169
169
|
/** Full-string check: one DLT hash token. */
|
|
170
170
|
export const DLT_HASH_VAR_TOKEN_FULL_STRING_REGEX = /^\{\#[^#]*\#\}$/;
|
|
171
|
+
/** Full-string with capture group: inner name for `{{ name }}`-style tokens (whitespace-tolerant). */
|
|
172
|
+
export const MUSTACHE_TOKEN_INNER_NAME_REGEX = /^\{\{\s*([^}]+?)\s*\}\}$/;
|
|
173
|
+
/** Full-string with capture group: inner name/body for `{# name #}` DLT tokens (whitespace-tolerant). */
|
|
174
|
+
export const DLT_HASH_TOKEN_INNER_NAME_REGEX = /^\{#\s*(.*?)\s*#\}$/;
|
|
171
175
|
/** Global with capture group: inner name inside `{{name}}`. */
|
|
172
176
|
export const MUSTACHE_VAR_NAME_CAPTURE_REGEX = /\{\{([^}]+)\}\}/g;
|
|
173
177
|
/** Global with capture group: inner body inside `{#body#}`. */
|
package/package.json
CHANGED
package/utils/commonUtils.js
CHANGED
|
@@ -539,4 +539,22 @@ export const isValidMobile = (mobile) => PHONE_REGEX.test(mobile);
|
|
|
539
539
|
export const formatPhoneNumber = (phone) => {
|
|
540
540
|
if (!phone) return '';
|
|
541
541
|
return String(phone).replace(/[^\d]/g, '');
|
|
542
|
-
};
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* TRAI sender IDs on persisted RCS SMS fallback: may live on the merged object, under
|
|
546
|
+
* `templateConfigs`, or (legacy) `templateConfigs.header`. Same resolution order as
|
|
547
|
+
* `createPayload` in `Rcs/index.js`.
|
|
548
|
+
*/
|
|
549
|
+
export function extractRegisteredSenderIdsFromSmsFallbackRecord(record) {
|
|
550
|
+
if (!record || typeof record !== 'object') return null;
|
|
551
|
+
const tc = record.templateConfigs && typeof record.templateConfigs === 'object'
|
|
552
|
+
? record.templateConfigs
|
|
553
|
+
: {};
|
|
554
|
+
const candidates = [record.registeredSenderIds, tc.registeredSenderIds, tc.header];
|
|
555
|
+
for (let i = 0; i < candidates.length; i += 1) {
|
|
556
|
+
const a = candidates[i];
|
|
557
|
+
if (Array.isArray(a) && a.length > 0) return a;
|
|
558
|
+
}
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
COMBINED_SMS_TEMPLATE_VAR_REGEX,
|
|
8
8
|
DEFAULT_MUSTACHE_VAR_REGEX,
|
|
9
9
|
DLT_HASH_VAR_TOKEN_FULL_STRING_REGEX,
|
|
10
|
+
DLT_HASH_TOKEN_INNER_NAME_REGEX,
|
|
10
11
|
DLT_VAR_BODY_CAPTURE_REGEX,
|
|
12
|
+
MUSTACHE_TOKEN_INNER_NAME_REGEX,
|
|
11
13
|
MUSTACHE_VAR_NAME_CAPTURE_REGEX,
|
|
12
14
|
MUSTACHE_VAR_TOKEN_FULL_STRING_REGEX,
|
|
13
15
|
} from '../constants/unified';
|
|
@@ -115,13 +117,36 @@ export const extractTemplateVariables = (templateStr = '', captureRegex) => {
|
|
|
115
117
|
return variables;
|
|
116
118
|
};
|
|
117
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Looks up the inner name of a `{{name}}` or `{#name#}` token in a flat key→value map.
|
|
122
|
+
* Handles both exact matches and dot-path suffixes (e.g. `tag.FORMAT_1` → name `FORMAT_1`).
|
|
123
|
+
* Returns the resolved string value, or `undefined` if not found / blank.
|
|
124
|
+
*/
|
|
125
|
+
function resolveUserVarForToken(segment, userVarMap) {
|
|
126
|
+
if (!userVarMap || typeof userVarMap !== 'object') return undefined;
|
|
127
|
+
const mMustache = segment.match(MUSTACHE_TOKEN_INNER_NAME_REGEX);
|
|
128
|
+
const mDlt = segment.match(DLT_HASH_TOKEN_INNER_NAME_REGEX);
|
|
129
|
+
const innerName = ((mMustache ? mMustache[1] : (mDlt ? mDlt[1] : '')) ?? '').trim();
|
|
130
|
+
if (!innerName) return undefined;
|
|
131
|
+
const direct = userVarMap[innerName];
|
|
132
|
+
if (direct != null && String(direct).trim() !== '') return String(direct);
|
|
133
|
+
const hit = Object.keys(userVarMap).find((k) => k === innerName || k.endsWith(`.${innerName}`));
|
|
134
|
+
if (hit != null && userVarMap[hit] != null && String(userVarMap[hit]).trim() !== '') return String(userVarMap[hit]);
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
/**
|
|
119
139
|
* SMS / DLT template preview: replace `{{…}}` / `{#…#}` tokens using `varMapData` keys `${token}_${index}`.
|
|
120
140
|
* Used by SmsTraiEdit (RCS SMS fallback) and UnifiedPreview fallback SMS bubble.
|
|
121
141
|
* DLT `{#…#}`: empty / unset slot values show the raw token (matches DLT preview UX); mustache `{{…}}` still
|
|
122
142
|
* resolves to empty when the slot is cleared.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} templateStr
|
|
145
|
+
* @param {Object} varMapData - Slot map (`${token}_${index}` → value)
|
|
146
|
+
* @param {Object|null} [userVarMap] - Optional Test & Preview custom values; used as fallback when a slot
|
|
147
|
+
* is absent or blank. When omitted the function behaves identically to its original two-argument form.
|
|
123
148
|
*/
|
|
124
|
-
export const getFallbackResolvedContent = (templateStr = '', varMapData = {}) => {
|
|
149
|
+
export const getFallbackResolvedContent = (templateStr = '', varMapData = {}, userVarMap = null) => {
|
|
125
150
|
const fallbackVarSlotMap = varMapData ?? {};
|
|
126
151
|
const templateSegments = splitTemplateVarString(templateStr, COMBINED_SMS_TEMPLATE_VAR_REGEX);
|
|
127
152
|
return templateSegments
|
|
@@ -132,13 +157,17 @@ export const getFallbackResolvedContent = (templateStr = '', varMapData = {}) =>
|
|
|
132
157
|
if (Object.prototype.hasOwnProperty.call(fallbackVarSlotMap, slotKey)) {
|
|
133
158
|
const slotValue = fallbackVarSlotMap[slotKey];
|
|
134
159
|
if (isDltHashVarToken(segment)) {
|
|
135
|
-
if (slotValue == null)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
160
|
+
if (slotValue == null || String(slotValue).trim() === '') {
|
|
161
|
+
if (userVarMap) { const uv = resolveUserVarForToken(segment, userVarMap); if (uv !== undefined) return uv; }
|
|
162
|
+
return segment;
|
|
163
|
+
}
|
|
164
|
+
return String(slotValue);
|
|
139
165
|
}
|
|
140
|
-
|
|
166
|
+
if (slotValue != null && String(slotValue).trim() !== '') return String(slotValue);
|
|
167
|
+
if (userVarMap) { const uv = resolveUserVarForToken(segment, userVarMap); if (uv !== undefined) return uv; }
|
|
168
|
+
return '';
|
|
141
169
|
}
|
|
170
|
+
if (userVarMap) { const uv = resolveUserVarForToken(segment, userVarMap); if (uv !== undefined) return uv; }
|
|
142
171
|
if (isDltHashVarToken(segment)) return segment;
|
|
143
172
|
return '';
|
|
144
173
|
})
|
|
@@ -157,4 +157,48 @@ describe('templateVarUtils', () => {
|
|
|
157
157
|
expect(extractTemplateVariables('{{x}} {{y}}', globalRe)).toEqual(['x', 'y']);
|
|
158
158
|
});
|
|
159
159
|
});
|
|
160
|
+
|
|
161
|
+
describe('getFallbackResolvedContent — userVarMap (third param)', () => {
|
|
162
|
+
it('falls back to userVarMap for mustache slot absent from varMapData', () => {
|
|
163
|
+
expect(getFallbackResolvedContent('Hi {{name}}', {}, { name: 'Alice' })).toBe('Hi Alice');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('falls back to userVarMap for DLT slot absent from varMapData', () => {
|
|
167
|
+
expect(getFallbackResolvedContent('{#city#}', {}, { city: 'Mumbai' })).toBe('Mumbai');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('falls back to userVarMap when mustache slot is present but blank', () => {
|
|
171
|
+
expect(getFallbackResolvedContent('{{x}}', { '{{x}}_0': '' }, { x: 'val' })).toBe('val');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('falls back to userVarMap when DLT slot is present but blank', () => {
|
|
175
|
+
expect(getFallbackResolvedContent('{#x#}', { '{#x#}_0': '' }, { x: 'val' })).toBe('val');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('slot wins over userVarMap when mustache slot is non-empty', () => {
|
|
179
|
+
expect(getFallbackResolvedContent('{{x}}', { '{{x}}_0': 'slot-val' }, { x: 'user-val' })).toBe('slot-val');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('slot wins over userVarMap when DLT slot is non-empty', () => {
|
|
183
|
+
expect(getFallbackResolvedContent('{#x#}', { '{#x#}_0': 'slot' }, { x: 'user' })).toBe('slot');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('resolves dot-path suffix key from userVarMap', () => {
|
|
187
|
+
expect(
|
|
188
|
+
getFallbackResolvedContent('{{FORMAT_1}}', {}, { 'expiry.FORMAT_1': 'Dec 2025' }),
|
|
189
|
+
).toBe('Dec 2025');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('userVarMap null behaves identically to the two-argument form', () => {
|
|
193
|
+
expect(getFallbackResolvedContent('{{a}}', { '{{a}}_0': 'x' }, null)).toBe('x');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('blank userVarMap value leaves mustache token as empty string', () => {
|
|
197
|
+
expect(getFallbackResolvedContent('{{a}}', {}, { a: '' })).toBe('');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('blank userVarMap value leaves DLT token as raw token', () => {
|
|
201
|
+
expect(getFallbackResolvedContent('{#a#}', {}, { a: '' })).toBe('{#a#}');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
160
204
|
});
|
|
@@ -97,6 +97,7 @@ import {
|
|
|
97
97
|
extractPreviewFromLiquidResponse,
|
|
98
98
|
getSmsFallbackTextForTagExtraction,
|
|
99
99
|
} from './previewApiUtils';
|
|
100
|
+
import { pickFirstSmsFallbackTemplateString } from '../../v2Containers/Rcs/rcsLibraryHydrationUtils';
|
|
100
101
|
|
|
101
102
|
import { isValidEmail, isValidMobile, formatPhoneNumber } from '../../utils/commonUtils';
|
|
102
103
|
import { getMembersLookup } from '../../services/api';
|
|
@@ -1039,78 +1040,77 @@ const CommonTestAndPreview = (props) => {
|
|
|
1039
1040
|
* rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
|
|
1040
1041
|
* Then rcsDeliverySettings, executionParams, clientName last.
|
|
1041
1042
|
*/
|
|
1042
|
-
const buildRcsTestMessagePayload = (
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1043
|
+
const buildRcsTestMessagePayload = (
|
|
1044
|
+
creativeFormData,
|
|
1045
|
+
_unusedEditorContentString,
|
|
1046
|
+
_customValuesObj,
|
|
1047
|
+
deliverySettingsOverride,
|
|
1048
|
+
basePayload,
|
|
1049
|
+
_rcsTestMetaExtras = {},
|
|
1050
|
+
) => {
|
|
1051
|
+
const rcsSectionFromForm =
|
|
1052
|
+
creativeFormData?.versions?.base?.content?.RCS ?? creativeFormData?.content?.RCS ?? {};
|
|
1053
|
+
const rcsContentFromForm = rcsSectionFromForm?.rcsContent || {};
|
|
1054
|
+
const smsFallbackFromCreativeForm = rcsSectionFromForm?.smsFallBackContent || {};
|
|
1055
|
+
let rcsCardPayloadList = [];
|
|
1056
|
+
if (Array.isArray(rcsContentFromForm?.cardContent)) {
|
|
1057
|
+
rcsCardPayloadList = rcsContentFromForm.cardContent;
|
|
1058
|
+
} else if (rcsContentFromForm?.cardContent) {
|
|
1059
|
+
rcsCardPayloadList = [rcsContentFromForm.cardContent];
|
|
1058
1060
|
}
|
|
1059
|
-
//
|
|
1060
|
-
const
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1061
|
+
// Raw title/description with template tags; SMS fallback uses tagged template fields (pickFirst…).
|
|
1062
|
+
const cardContentForTestMetaApi = rcsCardPayloadList.map((singleRcsCardPayload) => {
|
|
1063
|
+
const normalizedCardMediaForTestApi = singleRcsCardPayload?.media
|
|
1064
|
+
? normalizeRcsTestCardMedia(singleRcsCardPayload.media)
|
|
1065
|
+
: undefined;
|
|
1066
|
+
const suggestionsFromCard = Array.isArray(singleRcsCardPayload?.suggestions)
|
|
1067
|
+
? singleRcsCardPayload.suggestions
|
|
1068
|
+
: [];
|
|
1069
|
+
const suggestionsFormattedForTestMeta = suggestionsFromCard.map((suggestionItem, index) =>
|
|
1070
|
+
mapRcsSuggestionForTestMeta(suggestionItem, index));
|
|
1068
1071
|
return {
|
|
1069
|
-
title:
|
|
1070
|
-
description:
|
|
1071
|
-
mediaType:
|
|
1072
|
-
...(
|
|
1073
|
-
...(
|
|
1074
|
-
|
|
1072
|
+
title: singleRcsCardPayload?.title ?? '',
|
|
1073
|
+
description: singleRcsCardPayload?.description ?? '',
|
|
1074
|
+
mediaType: singleRcsCardPayload?.mediaType ?? MEDIA_TYPE_TEXT,
|
|
1075
|
+
...(normalizedCardMediaForTestApi && { media: normalizedCardMediaForTestApi }),
|
|
1076
|
+
...(suggestionsFormattedForTestMeta.length > 0 && {
|
|
1077
|
+
suggestions: suggestionsFormattedForTestMeta,
|
|
1078
|
+
}),
|
|
1075
1079
|
};
|
|
1076
1080
|
});
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const hasResolvedFallbackBodyFromRcsExtra =
|
|
1081
|
-
rcsExtraFallbackTemplate != null
|
|
1082
|
-
&& String(rcsExtraFallbackTemplate).trim() !== '';
|
|
1083
|
-
const smsMessageRaw = hasResolvedFallbackBodyFromRcsExtra
|
|
1084
|
-
? String(rcsExtraFallbackTemplate)
|
|
1085
|
-
: (smsFallback?.smsContent ?? smsFallback?.message ?? '');
|
|
1081
|
+
const smsFallbackTaggedTemplateBody = pickFirstSmsFallbackTemplateString(
|
|
1082
|
+
smsFallbackFromCreativeForm,
|
|
1083
|
+
) || '';
|
|
1086
1084
|
const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
|
|
1087
1085
|
? deliverySettingsOverride.cdmaSenderId.split('|')[1]
|
|
1088
1086
|
: deliverySettingsOverride?.cdmaSenderId;
|
|
1089
1087
|
const deliveryFallbackSmsId =
|
|
1090
1088
|
typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
|
|
1091
1089
|
const creativeFallbackSmsId =
|
|
1092
|
-
|
|
1090
|
+
smsFallbackFromCreativeForm?.senderId != null
|
|
1091
|
+
? String(smsFallbackFromCreativeForm.senderId).trim()
|
|
1092
|
+
: '';
|
|
1093
1093
|
const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
|
|
1094
1094
|
|
|
1095
1095
|
const smsFallBackContent =
|
|
1096
|
-
|
|
1097
|
-
? { message:
|
|
1096
|
+
smsFallbackTaggedTemplateBody.trim() !== ''
|
|
1097
|
+
? { message: smsFallbackTaggedTemplateBody }
|
|
1098
1098
|
: undefined;
|
|
1099
1099
|
|
|
1100
1100
|
// accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
|
|
1101
1101
|
const accountIdForMeta =
|
|
1102
|
-
|
|
1103
|
-
? String(
|
|
1102
|
+
rcsContentFromForm?.accountId != null && String(rcsContentFromForm.accountId).trim() !== ''
|
|
1103
|
+
? String(rcsContentFromForm.accountId)
|
|
1104
1104
|
: undefined;
|
|
1105
1105
|
|
|
1106
1106
|
const rcsRichCardContent = {
|
|
1107
1107
|
contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
1108
|
-
cardType:
|
|
1109
|
-
cardSettings:
|
|
1108
|
+
cardType: rcsContentFromForm?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
1109
|
+
cardSettings: rcsContentFromForm?.cardSettings ?? {
|
|
1110
1110
|
cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
1111
1111
|
cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
1112
1112
|
},
|
|
1113
|
-
...(
|
|
1113
|
+
...(cardContentForTestMetaApi.length > 0 && { cardContent: cardContentForTestMetaApi }),
|
|
1114
1114
|
};
|
|
1115
1115
|
|
|
1116
1116
|
const rcsMessageContent = {
|
|
@@ -3299,15 +3299,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
3299
3299
|
uniqueUserIds,
|
|
3300
3300
|
previewData,
|
|
3301
3301
|
deliveryOverride,
|
|
3302
|
-
|
|
3303
|
-
? {
|
|
3304
|
-
smsFallbackTemplateContent:
|
|
3305
|
-
smsFallbackTextForTagExtraction
|
|
3306
|
-
|| smsFallbackContent?.templateContent
|
|
3307
|
-
|| smsFallbackContent?.content
|
|
3308
|
-
|| '',
|
|
3309
|
-
}
|
|
3310
|
-
: {},
|
|
3302
|
+
{},
|
|
3311
3303
|
);
|
|
3312
3304
|
|
|
3313
3305
|
actions.createMessageMetaRequested(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import { getFallbackResolvedContentForCardDisplay } from '../../utils/templateVarUtils';
|
|
3
|
+
import { extractRegisteredSenderIdsFromSmsFallbackRecord } from '../../utils/commonUtils';
|
|
3
4
|
import { SMS_CATEGORY_FILTERS } from './constants';
|
|
4
5
|
|
|
5
6
|
export function buildFallbackDataFromTemplate(template) {
|
|
@@ -16,9 +17,14 @@ export function buildFallbackDataFromTemplate(template) {
|
|
|
16
17
|
const content = smsEditor;
|
|
17
18
|
const headerList = Array.isArray(base.header) ? base.header : [];
|
|
18
19
|
const senderId = (headerList[0]) || base.senderId || '';
|
|
20
|
+
const templateName =
|
|
21
|
+
template.name
|
|
22
|
+
|| base.template_name
|
|
23
|
+
|| base['template-name']
|
|
24
|
+
|| '';
|
|
19
25
|
return {
|
|
20
26
|
smsTemplateId: template._id || '',
|
|
21
|
-
templateName
|
|
27
|
+
templateName,
|
|
22
28
|
content,
|
|
23
29
|
templateContent,
|
|
24
30
|
senderId,
|
|
@@ -35,14 +41,19 @@ export function mapFallbackValueToEditTemplateData(source) {
|
|
|
35
41
|
rcsSmsFallbackVarMappedFromSource != null
|
|
36
42
|
&& typeof rcsSmsFallbackVarMappedFromSource === 'object'
|
|
37
43
|
&& Object.keys(rcsSmsFallbackVarMappedFromSource).length > 0;
|
|
44
|
+
const registeredSenderIdsForHeader =
|
|
45
|
+
extractRegisteredSenderIdsFromSmsFallbackRecord(source);
|
|
38
46
|
return {
|
|
39
47
|
_id: source?.smsTemplateId,
|
|
40
48
|
name: source?.templateName,
|
|
41
49
|
versions: {
|
|
42
50
|
base: {
|
|
43
51
|
'sms-editor': source?.templateContent || source?.content,
|
|
44
|
-
...(
|
|
45
|
-
? {
|
|
52
|
+
...(source?.templateName != null && String(source.templateName).trim() !== ''
|
|
53
|
+
? { template_name: String(source.templateName).trim() }
|
|
54
|
+
: {}),
|
|
55
|
+
...(Array.isArray(registeredSenderIdsForHeader) && registeredSenderIdsForHeader.length
|
|
56
|
+
? { header: registeredSenderIdsForHeader }
|
|
46
57
|
: {}),
|
|
47
58
|
...(typeof source?.unicodeValidity === 'boolean'
|
|
48
59
|
? { 'unicode-validity': source.unicodeValidity }
|
|
@@ -50,6 +50,21 @@ describe('smsFallbackUtils', () => {
|
|
|
50
50
|
};
|
|
51
51
|
expect(buildFallbackDataFromTemplate(template).unicodeValidity).toBe(true);
|
|
52
52
|
});
|
|
53
|
+
|
|
54
|
+
it('uses versions.base.template_name when root name is empty (DLT detail shape)', () => {
|
|
55
|
+
const template = {
|
|
56
|
+
_id: 'dlt1',
|
|
57
|
+
name: '',
|
|
58
|
+
versions: {
|
|
59
|
+
base: {
|
|
60
|
+
template_name: 'DLT Registered Name',
|
|
61
|
+
'sms-editor': 'Hi',
|
|
62
|
+
header: ['H1'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
expect(buildFallbackDataFromTemplate(template).templateName).toBe('DLT Registered Name');
|
|
67
|
+
});
|
|
53
68
|
});
|
|
54
69
|
|
|
55
70
|
describe('getSmsFallbackCardDisplayContent', () => {
|
|
@@ -101,6 +116,7 @@ describe('smsFallbackUtils', () => {
|
|
|
101
116
|
versions: {
|
|
102
117
|
base: {
|
|
103
118
|
'sms-editor': 'body',
|
|
119
|
+
template_name: 'N',
|
|
104
120
|
header: ['h1'],
|
|
105
121
|
'unicode-validity': false,
|
|
106
122
|
},
|
|
@@ -84,6 +84,10 @@ TestAndPreviewSlidebox.propTypes = {
|
|
|
84
84
|
templateName: PropTypes.string,
|
|
85
85
|
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: PropTypes.object,
|
|
86
86
|
}),
|
|
87
|
+
/** Passed to CommonTestAndPreview for RCS test-meta resolution (slot semantics vs full-mode). */
|
|
88
|
+
rcsTestPreviewOptions: PropTypes.shape({
|
|
89
|
+
isLibraryMode: PropTypes.bool,
|
|
90
|
+
}),
|
|
87
91
|
// Redux props are passed through
|
|
88
92
|
actions: PropTypes.object.isRequired,
|
|
89
93
|
extractedTags: PropTypes.array.isRequired,
|
|
@@ -115,6 +119,7 @@ TestAndPreviewSlidebox.defaultProps = {
|
|
|
115
119
|
currentTab: 1,
|
|
116
120
|
messageMetaConfigId: null,
|
|
117
121
|
prefilledValues: {},
|
|
122
|
+
rcsTestPreviewOptions: undefined,
|
|
118
123
|
senderDetailsByChannel: {},
|
|
119
124
|
wecrmAccounts: [],
|
|
120
125
|
isLoadingSenderDetails: false,
|
|
@@ -43,6 +43,7 @@ import {EXTERNAL_URL, SITE_URL, WEBPUSH_MEDIA_TYPES} from '../WebPush/constants'
|
|
|
43
43
|
import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
|
|
44
44
|
import { RCS_STATUSES } from '../Rcs/constants';
|
|
45
45
|
import { mapRcsCardContentForConsumerWithResolvedTags } from '../Rcs/utils';
|
|
46
|
+
import { pickRcsCardVarMappedEntries } from '../Rcs/rcsLibraryHydrationUtils';
|
|
46
47
|
import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
47
48
|
import { CREATIVE } from '../Facebook/constants';
|
|
48
49
|
import { LOYALTY } from '../App/constants';
|
|
@@ -764,8 +765,13 @@ export class Creatives extends React.Component {
|
|
|
764
765
|
rootMirrorCardVarMapped != null && typeof rootMirrorCardVarMapped === 'object'
|
|
765
766
|
? rootMirrorCardVarMapped
|
|
766
767
|
: {};
|
|
767
|
-
const mergedFromRootAndNested = {
|
|
768
|
-
|
|
768
|
+
const mergedFromRootAndNested = {
|
|
769
|
+
...pickRcsCardVarMappedEntries(rootRecord),
|
|
770
|
+
...pickRcsCardVarMappedEntries(nestedRecord),
|
|
771
|
+
};
|
|
772
|
+
return Object.keys(mergedFromRootAndNested).length > 0
|
|
773
|
+
? mergedFromRootAndNested
|
|
774
|
+
: null;
|
|
769
775
|
})();
|
|
770
776
|
// Campaigns (embedded): do not duplicate `cardVarMapped` as root `rcsCardVarMapped` on send —
|
|
771
777
|
// slot map stays on `versions…cardContent[0].cardVarMapped` only. Library full mode keeps root mirror.
|
|
@@ -1348,7 +1354,7 @@ export class Creatives extends React.Component {
|
|
|
1348
1354
|
const cardVarMappedFromFirstRcsCard =
|
|
1349
1355
|
firstCardFromSubmit?.cardVarMapped != null
|
|
1350
1356
|
&& typeof firstCardFromSubmit.cardVarMapped === 'object'
|
|
1351
|
-
? firstCardFromSubmit.cardVarMapped
|
|
1357
|
+
? pickRcsCardVarMappedEntries(firstCardFromSubmit.cardVarMapped)
|
|
1352
1358
|
: null;
|
|
1353
1359
|
const includeRootRcsCardVarMappedOnConsumerPayload =
|
|
1354
1360
|
cardVarMappedFromFirstRcsCard
|
|
@@ -90,13 +90,17 @@ export const RCS_MEDIA_TYPES = {
|
|
|
90
90
|
VIDEO: 'VIDEO',
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
export const
|
|
93
|
+
/** Match `{{…}}` placeholders including Liquid-style names (e.g. `tag.FORMAT_1`). Must align with split + `isAnyTemplateVarToken` / SMS fallback. */
|
|
94
|
+
export const rcsVarRegex = /\{\{[^}]+\}\}/g;
|
|
95
|
+
/** True when the whole string is a single RCS/mustache token (used when testing segment tokens). */
|
|
96
|
+
export const rcsVarTestRegex = /^\{\{[^}]+\}\}$/;
|
|
95
97
|
|
|
96
98
|
/** Matches all `{{N}}` numeric-index variable tokens in a template string (global). */
|
|
97
99
|
export const RCS_NUMERIC_VAR_TOKEN_REGEX = /\{\{(\d+)\}\}/g;
|
|
98
100
|
/** `cardVarMapped` slot keys that are numeric only (legacy ordering). */
|
|
99
101
|
export const RCS_NUMERIC_VAR_NAME_REGEX = /^\d+$/;
|
|
102
|
+
/** Semantic Liquid-style keys on RCS `cardVarMapped` (same class as `{{…}}` inner names in the editor). */
|
|
103
|
+
export const RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX = /^[\w.]+$/;
|
|
100
104
|
/** Escape `RegExp` metacharacters when building a pattern from user/tag text. */
|
|
101
105
|
export const RCS_REGEX_META_CHARS_PATTERN = /[.*+?^${}()|[\]\\]/g;
|
|
102
106
|
/** Entire string is a single `{{tag}}` token (value still a placeholder). */
|