@capillarytech/creatives-library 9.0.56-betaa.0 → 9.0.57-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 +7 -1
- package/package.json +1 -1
- package/services/api.js +25 -4
- package/utils/common.js +0 -4
- package/utils/templateVarUtils.js +16 -0
- package/utils/tests/templateVarUtils.test.js +44 -0
- package/v2Components/CapActionButton/index.js +73 -60
- package/v2Components/CapActionButton/index.scss +44 -28
- package/v2Components/CapActionButton/messages.js +7 -3
- package/v2Components/CapActionButton/tests/index.test.js +17 -1
- package/v2Components/CapWhatsappCTA/messages.js +4 -0
- package/v2Components/CapWhatsappCarouselButton/index.js +42 -33
- package/v2Components/CapWhatsappCarouselButton/index.scss +44 -2
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
- package/v2Components/CommonTestAndPreview/actions.js +13 -0
- package/v2Components/CommonTestAndPreview/constants.js +8 -0
- package/v2Components/CommonTestAndPreview/index.js +55 -3
- package/v2Components/CommonTestAndPreview/reducer.js +20 -0
- package/v2Components/CommonTestAndPreview/sagas.js +28 -0
- package/v2Components/CommonTestAndPreview/selectors.js +6 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
- package/v2Components/CommonTestAndPreview/tests/actions.test.js +15 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +101 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +41 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +84 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +12 -0
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +36 -22
- package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +5 -13
- package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +6 -4
- package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +13 -14
- package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +15 -8
- package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +31 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +13 -0
- package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +26 -0
- package/v2Components/FormBuilder/_formBuilder.scss +8 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +9 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +140 -75
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +18 -2
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +67 -28
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +503 -51
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +98 -4
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
- package/v2Containers/CommunicationFlow/constants.js +65 -4
- package/v2Containers/CommunicationFlow/index.js +16 -23
- package/v2Containers/CommunicationFlow/messages.js +11 -3
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +27 -11
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +4 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +176 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +22 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +14 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +11 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +51 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +19 -5
- package/v2Containers/CreativesContainer/SlideBoxContent.js +15 -0
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
- package/v2Containers/CreativesContainer/index.js +29 -3
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +29 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +18 -5
- package/v2Containers/CreativesContainer/tests/index.test.js +12 -0
- package/v2Containers/MobilePush/Create/index.js +18 -1
- package/v2Containers/MobilePush/Create/test/contentValidity.test.js +96 -0
- package/v2Containers/MobilePush/Edit/index.js +18 -1
- package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +116 -0
- package/v2Containers/MobilePush/commonMethods.js +49 -1
- package/v2Containers/MobilePushNew/index.js +29 -4
- package/v2Containers/MobilePushNew/tests/index.test.js +119 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +82 -0
- package/v2Containers/MobilePushNew/utils.js +34 -1
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +34 -0
- package/v2Containers/Rcs/index.scss +15 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +115 -19
- package/v2Containers/Rcs/tests/index.test.js +67 -0
- package/v2Containers/Sms/Create/index.js +3 -1
- package/v2Containers/Sms/Edit/index.js +25 -0
- package/v2Containers/Sms/Edit/tests/index.test.js +85 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +27 -3
- package/v2Containers/Viber/index.js +24 -1
- package/v2Containers/Viber/tests/index.test.js +103 -0
- package/v2Containers/WebPush/Create/index.js +19 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +17 -12
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +5 -0
- package/v2Containers/WebPush/Create/tests/contentValidity.test.js +294 -0
- package/v2Containers/Whatsapp/index.js +25 -4
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +306 -34
- package/v2Containers/Whatsapp/tests/index.test.js +115 -0
- package/v2Containers/Zalo/index.js +28 -1
- package/v2Containers/Zalo/tests/index.test.js +99 -0
- package/v2Containers/mockdata.js +25 -0
package/constants/unified.js
CHANGED
|
@@ -64,7 +64,6 @@ export const ENABLE_AI_SUGGESTIONS = 'ENABLE_AI_SUGGESTIONS';
|
|
|
64
64
|
export const AI_CONTENT_BOT_DISABLED = 'AI_CONTENT_BOT_DISABLED';
|
|
65
65
|
export const AI_DOCUMENTATION_BOT_ENABLED = 'AI_DOCUMENTATION_BOT_ENABLED';
|
|
66
66
|
export const ENABLE_PRODUCT_SUPPORT_VIDEOS = 'ENABLE_PRODUCT_SUPPORT_VIDEOS';
|
|
67
|
-
export const SUPPORT_ENGAGEMENT_MODULE = 'SUPPORT_ENGAGEMENT_MODULE';
|
|
68
67
|
export const EMBEDDED = 'embedded';
|
|
69
68
|
// --- Tag/Validation Constants ---
|
|
70
69
|
export const CARD_RELATED_TAGS = [
|
|
@@ -230,3 +229,10 @@ export const RCS_API_SMS_FALLBACK_KEYS = Object.freeze({
|
|
|
230
229
|
RCS_SMS_FALLBACK_VAR_MAPPED: 'rcsSmsFallbackVarMapped',
|
|
231
230
|
});
|
|
232
231
|
|
|
232
|
+
export const HTTP_METHODS = Object.freeze({
|
|
233
|
+
GET: 'GET',
|
|
234
|
+
POST: 'POST',
|
|
235
|
+
PUT: 'PUT',
|
|
236
|
+
PATCH: 'PATCH',
|
|
237
|
+
DELETE: 'DELETE',
|
|
238
|
+
});
|
package/package.json
CHANGED
package/services/api.js
CHANGED
|
@@ -12,6 +12,7 @@ import getSchema from './getSchema';
|
|
|
12
12
|
import * as API from '../utils/ApiCaller';
|
|
13
13
|
import { addBaseToTemplate, transformCustomFieldsData } from '../utils/commonUtils';
|
|
14
14
|
import { EMAIL, SMS, TRANSACTION } from '../v2Containers/CreativesContainer/constants';
|
|
15
|
+
import { HTTP_METHODS } from '../constants/unified';
|
|
15
16
|
let API_ENDPOINT = config.development.api_endpoint;
|
|
16
17
|
let EXPORT_API_ENDPOINT = config.development.exports_api_endpoint;
|
|
17
18
|
let CAMPAIGNS_API_ENDPOINT = config.development.campaigns_api_endpoint;
|
|
@@ -698,22 +699,42 @@ export const getLiquidTags = (content) => {
|
|
|
698
699
|
|
|
699
700
|
export const createCentralCommsMetaId = (payload, metaType = TRANSACTION) => {
|
|
700
701
|
const url = `${API_ENDPOINT}/common/central-comms/meta-id/${metaType}`;
|
|
701
|
-
return request(url, getAPICallObject(
|
|
702
|
+
return request(url, getAPICallObject(HTTP_METHODS.POST, payload, false, false, false, true));
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
// Use the CCS CommDefinition API for CommunicationFlow Save; keep legacy messageMeta APIs for CreativesContainer.
|
|
706
|
+
const COMM_DEFINITIONS_PATH = `${API_ENDPOINT}/comm-definitions`;
|
|
707
|
+
|
|
708
|
+
export const createCommDefinition = (payload) => {
|
|
709
|
+
const url = COMM_DEFINITIONS_PATH;
|
|
710
|
+
return request(url, getAPICallObject(HTTP_METHODS.POST, payload, false, false, false, true));
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// Opens a new DRAFT version on an existing CommDefinition, preserving its id/referenceId.
|
|
714
|
+
export const editCommDefinition = (commDefinitionId, payload) => {
|
|
715
|
+
const url = `${COMM_DEFINITIONS_PATH}/${commDefinitionId}/edit`;
|
|
716
|
+
return request(url, getAPICallObject(HTTP_METHODS.POST, payload, false, false, false, true));
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// notify/ui's "Send test" uses this instead of the legacy createMessageMeta/sendTestMessage flow.
|
|
720
|
+
export const sendTransactionalComm = (payload) => {
|
|
721
|
+
const url = `${COMM_DEFINITIONS_PATH}/send/transaction`;
|
|
722
|
+
return request(url, getAPICallObject(HTTP_METHODS.POST, payload, false, false, false, true));
|
|
702
723
|
};
|
|
703
724
|
|
|
704
725
|
export const getCentralCommsMetaIds = (metaIds, metaType = TRANSACTION) => {
|
|
705
726
|
const url = `${API_ENDPOINT}/common/central-comms/meta-id/${metaType}?metaIds=${metaIds}`;
|
|
706
|
-
return request(url, getAPICallObject(
|
|
727
|
+
return request(url, getAPICallObject(HTTP_METHODS.GET, null, false, false, false, true));
|
|
707
728
|
};
|
|
708
729
|
|
|
709
730
|
export const bulkClaimAndApprove = (payload, metaType = TRANSACTION) => {
|
|
710
731
|
const url = `${API_ENDPOINT}/common/central-comms/bulk-claim-approve/${metaType}`;
|
|
711
|
-
return request(url, getAPICallObject(
|
|
732
|
+
return request(url, getAPICallObject(HTTP_METHODS.POST, payload, false, false, false, true));
|
|
712
733
|
};
|
|
713
734
|
|
|
714
735
|
export const updateMetaConfig = (payload, metaType = TRANSACTION, metaId) => {
|
|
715
736
|
const url = `${API_ENDPOINT}/common/central-comms/meta-id/${metaType}/${metaId}`;
|
|
716
|
-
return request(url, getAPICallObject(
|
|
737
|
+
return request(url, getAPICallObject(HTTP_METHODS.PATCH, payload, false, false, false, true));
|
|
717
738
|
};
|
|
718
739
|
|
|
719
740
|
// Customer Search APIs for Email Test & Preview Feature
|
package/utils/common.js
CHANGED
|
@@ -100,10 +100,6 @@ export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
|
|
|
100
100
|
null,
|
|
101
101
|
SUPPORT_CK_EDITOR,
|
|
102
102
|
);
|
|
103
|
-
export const hasSupportEngagementModule = Auth.hasFeatureAccess.bind(
|
|
104
|
-
null,
|
|
105
|
-
SUPPORT_ENGAGEMENT_MODULE,
|
|
106
|
-
);
|
|
107
103
|
|
|
108
104
|
export const hasGiftVoucherFeature = Auth.hasFeatureAccess.bind(
|
|
109
105
|
null,
|
|
@@ -115,6 +115,22 @@ export const extractTemplateVariables = (templateStr = '', captureRegex) => {
|
|
|
115
115
|
return variables;
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
// Reconcile slot-format (`${token}_${index}`) and CCS-format (`"0"`, `"1"`, ...) var maps into the editor's `${token}_${segmentIndex}` slot-key format.
|
|
119
|
+
export const reconcileVarMapToSlotFormat = (rawVarMap = {}, segments = [], regex) => {
|
|
120
|
+
if (Object.keys(rawVarMap ?? {}).length === 0) return {};
|
|
121
|
+
const isSlotFormat = Object.keys(rawVarMap).some((key) => key.includes('_'));
|
|
122
|
+
if (isSlotFormat) return { ...rawVarMap };
|
|
123
|
+
const slotMap = {};
|
|
124
|
+
let occurrenceIndex = 0;
|
|
125
|
+
(segments ?? []).forEach((segment, segmentIndex) => {
|
|
126
|
+
if (typeof segment === 'string' && (segment.match(regex) || []).length > 0) {
|
|
127
|
+
slotMap[`${segment}_${segmentIndex}`] = rawVarMap[occurrenceIndex] ?? '';
|
|
128
|
+
occurrenceIndex += 1;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return slotMap;
|
|
132
|
+
};
|
|
133
|
+
|
|
118
134
|
/**
|
|
119
135
|
* Looks up the inner name of a `{{name}}` or `{#name#}` token in a flat key→value map.
|
|
120
136
|
* Handles both exact matches and dot-path suffixes (e.g. `tag.FORMAT_1` → name `FORMAT_1`).
|
|
@@ -6,8 +6,11 @@ import {
|
|
|
6
6
|
getFallbackResolvedContentForCardDisplay,
|
|
7
7
|
isDltHashVarToken,
|
|
8
8
|
isAnyTemplateVarToken,
|
|
9
|
+
reconcileVarMapToSlotFormat,
|
|
9
10
|
} from '../templateVarUtils';
|
|
10
11
|
|
|
12
|
+
const MUSTACHE_VAR_REGEX = /\{\{\d+\}\}/g;
|
|
13
|
+
|
|
11
14
|
describe('templateVarUtils', () => {
|
|
12
15
|
describe('splitContentByOrderedVarTokens', () => {
|
|
13
16
|
it('pushes remainder when next token is not found in string', () => {
|
|
@@ -201,4 +204,45 @@ describe('templateVarUtils', () => {
|
|
|
201
204
|
expect(getFallbackResolvedContent('{#a#}', {}, { a: '' })).toBe('{#a#}');
|
|
202
205
|
});
|
|
203
206
|
});
|
|
207
|
+
|
|
208
|
+
describe('reconcileVarMapToSlotFormat', () => {
|
|
209
|
+
it('returns an empty object for an empty/absent rawVarMap', () => {
|
|
210
|
+
expect(reconcileVarMapToSlotFormat({}, ['{{1}}'], MUSTACHE_VAR_REGEX)).toEqual({});
|
|
211
|
+
expect(reconcileVarMapToSlotFormat(undefined, ['{{1}}'], MUSTACHE_VAR_REGEX)).toEqual({});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('uses slot-format keys (containing an underscore) as-is', () => {
|
|
215
|
+
const rawVarMap = { '{{1}}_1': 'test', '{{2}}_3': 'test2' };
|
|
216
|
+
expect(
|
|
217
|
+
reconcileVarMapToSlotFormat(rawVarMap, ['x', '{{1}}', 'y', '{{2}}'], MUSTACHE_VAR_REGEX),
|
|
218
|
+
).toEqual(rawVarMap);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('remaps CCS-format sequential-occurrence-index keys ("0","1",...) onto this UI\'s own slot keys (regression: CCS sends plain occurrence indices, not this UI\'s `${token}_${arrayIndex}` format, which silently blanked WhatsApp edit fields and desynced the segment array)', () => {
|
|
222
|
+
// Mirrors the real CCS response: "Hi! Here is your latest tier progress summary with us.\n\nTo
|
|
223
|
+
// move up to the next tier, you still need to spend {{1}} more,\nearn {{2}} more points, ..."
|
|
224
|
+
// — varMapped keys "0".."10" map to {{1}}.."{{11}}" in template order.
|
|
225
|
+
const segments = [
|
|
226
|
+
'spend ',
|
|
227
|
+
'{{1}}',
|
|
228
|
+
' more,\nearn ',
|
|
229
|
+
'{{2}}',
|
|
230
|
+
' more points',
|
|
231
|
+
];
|
|
232
|
+
const rawVarMap = { 0: 'dasda', 1: 'dasd' };
|
|
233
|
+
expect(reconcileVarMapToSlotFormat(rawVarMap, segments, MUSTACHE_VAR_REGEX)).toEqual({
|
|
234
|
+
'{{1}}_1': 'dasda',
|
|
235
|
+
'{{2}}_3': 'dasd',
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('defaults a missing occurrence-index entry to an empty string rather than dropping the slot', () => {
|
|
240
|
+
const segments = ['a ', '{{1}}', ' b ', '{{2}}'];
|
|
241
|
+
const rawVarMap = { 0: 'filled' }; // no entry for occurrence index 1
|
|
242
|
+
expect(reconcileVarMapToSlotFormat(rawVarMap, segments, MUSTACHE_VAR_REGEX)).toEqual({
|
|
243
|
+
'{{1}}_1': 'filled',
|
|
244
|
+
'{{2}}_3': '',
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
204
248
|
});
|
|
@@ -101,10 +101,12 @@ export const CapActionButton = (props) => {
|
|
|
101
101
|
const onButtonTextChange = ({target}) => {
|
|
102
102
|
const { value, id } = target;
|
|
103
103
|
let errorMessage = '';
|
|
104
|
-
if (value.
|
|
104
|
+
if (!value.trim()) {
|
|
105
|
+
errorMessage = formatMessage(messages.ctaButtonTextEmptyMessage);
|
|
106
|
+
} else if (!isValidText(value)) {
|
|
107
|
+
errorMessage = formatMessage(messages.ctaButtonTextErrorMessage);
|
|
108
|
+
} else if (value.length > BTN_MAX_LENGTH) {
|
|
105
109
|
errorMessage = formatMessage(messages.ctaButtonTextLengthError, { maxLength: BTN_MAX_LENGTH });
|
|
106
|
-
} if (!isValidText(value)) {
|
|
107
|
-
errorMessage = formatMessage(messages.ctaButtonErrorMessage);
|
|
108
110
|
}
|
|
109
111
|
setButtonError(errorMessage);
|
|
110
112
|
updateDisplayAndPostback(value, id);
|
|
@@ -446,12 +448,11 @@ export const CapActionButton = (props) => {
|
|
|
446
448
|
gutter={9}
|
|
447
449
|
>
|
|
448
450
|
<CapColumn
|
|
449
|
-
|
|
450
|
-
className={`${ctaIsPhone ? 'whatsapp-saved-cta-phone-icon' : ''}`}
|
|
451
|
+
className={`rcs-saved-cta-cell ${ctaIsPhone ? 'whatsapp-saved-cta-phone-icon' : ''}`}
|
|
451
452
|
>
|
|
452
453
|
<CapIcon size="s" type={ctaIsPhone ? 'call' : (ctaIsReply ? 'small-link' : 'open-in-new')} />
|
|
453
454
|
</CapColumn>
|
|
454
|
-
<CapColumn
|
|
455
|
+
<CapColumn className="rcs-saved-cta-cell rcs-saved-cta-text-cell">
|
|
455
456
|
<CapLabel
|
|
456
457
|
type="label2"
|
|
457
458
|
className="rcs-saved-cta-button-text"
|
|
@@ -459,64 +460,76 @@ export const CapActionButton = (props) => {
|
|
|
459
460
|
{text}
|
|
460
461
|
</CapLabel>
|
|
461
462
|
</CapColumn>
|
|
462
|
-
<CapColumn
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
463
|
+
<CapColumn className="rcs-saved-cta-cell rcs-saved-cta-type-cell">
|
|
464
|
+
{!ctaIsPhone && !ctaIsReply && (
|
|
465
|
+
<CapLabel className="url-type" type="label2">
|
|
466
|
+
{(cta.urlType || RCS_CTA_URL_TYPE.STATIC) === RCS_CTA_URL_TYPE.DYNAMIC
|
|
467
|
+
? formatMessage(messages.ctaWebsiteTypeDynamic)
|
|
468
|
+
: formatMessage(messages.ctaWebsiteTypeStatic)}
|
|
469
|
+
</CapLabel>
|
|
470
|
+
)}
|
|
471
|
+
</CapColumn>
|
|
472
|
+
<CapColumn className="rcs-saved-cta-cell rcs-saved-cta-url-cell">
|
|
473
|
+
{ctaIsPhone && (
|
|
474
|
+
<CapLabel className="phone">+{phoneNumber}</CapLabel>
|
|
475
|
+
)}
|
|
476
|
+
{!ctaIsPhone && !ctaIsReply && (
|
|
477
|
+
<CapTooltip
|
|
478
|
+
title={url}
|
|
479
|
+
placement="top"
|
|
480
|
+
wrapperClassName="rcs-cta-url-tooltip-wrapper"
|
|
481
|
+
triggerClassName="rcs-cta-url-tooltip-trigger"
|
|
482
|
+
>
|
|
483
|
+
<CapLabel className="url">{url}</CapLabel>
|
|
484
|
+
</CapTooltip>
|
|
485
|
+
)}
|
|
486
|
+
</CapColumn>
|
|
487
|
+
<CapColumn className="rcs-saved-cta-cell rcs-saved-cta-actions-cell">
|
|
488
|
+
{isDynamicUrlSubtype && (url || '').includes(DYNAMIC_URL_SUFFIX) && (
|
|
489
|
+
<TagList
|
|
490
|
+
className="rcs-cta-taglist"
|
|
491
|
+
label={formatMessage(messages.ctaTagListLabel)}
|
|
492
|
+
onTagSelect={(data) => onTagSelect(data, index, url)}
|
|
493
|
+
location={location}
|
|
494
|
+
tags={tags}
|
|
495
|
+
injectedTags={injectedTags}
|
|
496
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
497
|
+
onContextChange={onContextChange}
|
|
498
|
+
/>
|
|
499
|
+
)}
|
|
500
|
+
{isDynamicUrlSubtype && !(url || '').includes(DYNAMIC_URL_SUFFIX) && (
|
|
501
|
+
<CapTooltip title={formatMessage(messages.ctaTagListRevert)} placement="top">
|
|
502
|
+
<CapIcon
|
|
503
|
+
size="s"
|
|
504
|
+
type="return"
|
|
505
|
+
className="rcs-cta-tag-revert"
|
|
506
|
+
onClick={() => revertTagSelect(index, url)}
|
|
489
507
|
/>
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
508
|
+
</CapTooltip>
|
|
509
|
+
)}
|
|
510
|
+
{(isFullMode && !isEditFlow) && (
|
|
511
|
+
<CapRow
|
|
512
|
+
className="rcs-saved-cta-action-icons"
|
|
513
|
+
type="flex"
|
|
514
|
+
align="middle"
|
|
515
|
+
noWrap
|
|
516
|
+
>
|
|
517
|
+
<CapIcon
|
|
518
|
+
size="s"
|
|
519
|
+
type="edit"
|
|
520
|
+
className="rcs-saved-cta-edit-icon"
|
|
521
|
+
onClick={() => editCta(index)}
|
|
522
|
+
/>
|
|
523
|
+
{!(isHostIcs && index === 0) && !hideDeleteForSuggestionIndex(index) && !cannotDeleteSavedMandatory(index) && (
|
|
503
524
|
<CapIcon
|
|
504
525
|
size="s"
|
|
505
|
-
type="
|
|
506
|
-
className="rcs-saved-cta-
|
|
507
|
-
onClick={() =>
|
|
526
|
+
type="delete"
|
|
527
|
+
className="rcs-saved-cta-delete-icon"
|
|
528
|
+
onClick={() => deleteButtonHandler(index)}
|
|
508
529
|
/>
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
type="delete"
|
|
513
|
-
className="rcs-saved-cta-delete-icon"
|
|
514
|
-
onClick={() => deleteButtonHandler(index)}
|
|
515
|
-
/>
|
|
516
|
-
)}
|
|
517
|
-
</div>
|
|
518
|
-
)}
|
|
519
|
-
</div>
|
|
530
|
+
)}
|
|
531
|
+
</CapRow>
|
|
532
|
+
)}
|
|
520
533
|
</CapColumn>
|
|
521
534
|
</CapRow>
|
|
522
535
|
);
|
|
@@ -25,37 +25,51 @@
|
|
|
25
25
|
margin-bottom: $CAP_SPACE_12;
|
|
26
26
|
flex-wrap: nowrap;
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// fixed span, the gutter/margin between columns had nowhere to come from
|
|
32
|
-
// and pushed this column's content past the card's right border.
|
|
33
|
-
.rcs-saved-cta-value-column {
|
|
28
|
+
.rcs-saved-cta-cell {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
34
31
|
min-width: 0;
|
|
32
|
+
|
|
33
|
+
// Cell children are flex items too — without this their own min-content
|
|
34
|
+
// (the full text/URL) makes the cell unshrinkable and pushes the row.
|
|
35
|
+
> * {
|
|
36
|
+
min-width: 0;
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
// Fixed basis for the 20-char button text cap.
|
|
41
|
+
.rcs-saved-cta-text-cell {
|
|
42
|
+
flex: 0 1 10rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Fixed, not auto: "Static" is narrower than "Dynamic", which left the URL
|
|
46
|
+
// starting at a different x per row.
|
|
47
|
+
.rcs-saved-cta-type-cell {
|
|
48
|
+
flex: 0 1 4rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.rcs-saved-cta-url-cell {
|
|
52
|
+
flex: 1 1 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.rcs-saved-cta-actions-cell {
|
|
56
|
+
flex: 0 0 auto;
|
|
44
57
|
justify-content: flex-end;
|
|
45
58
|
gap: $CAP_SPACE_08;
|
|
46
|
-
|
|
47
|
-
min-width: 0;
|
|
59
|
+
}
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
// CapTooltip nests `.url` two levels down, so these — not `.url` — are the
|
|
62
|
+
// boxes that must be allowed to shrink for the ellipsis to apply.
|
|
63
|
+
.rcs-cta-url-tooltip-wrapper {
|
|
64
|
+
flex: 1 1 0;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
}
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
68
|
+
.rcs-cta-url-tooltip-trigger {
|
|
69
|
+
min-width: 0;
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
.url {
|
|
72
|
+
.url, .rcs-saved-cta-button-text, .url-type {
|
|
59
73
|
white-space: nowrap;
|
|
60
74
|
overflow: hidden;
|
|
61
75
|
text-overflow: ellipsis;
|
|
@@ -69,10 +83,6 @@
|
|
|
69
83
|
word-break: break-word;
|
|
70
84
|
}
|
|
71
85
|
|
|
72
|
-
.saved-button-text {
|
|
73
|
-
font-weight: 500;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
86
|
.rcs-saved-cta-action-icons {
|
|
77
87
|
display: flex;
|
|
78
88
|
align-items: center;
|
|
@@ -85,8 +95,6 @@
|
|
|
85
95
|
cursor: pointer;
|
|
86
96
|
}
|
|
87
97
|
|
|
88
|
-
// Dynamic-URL tag controls: keep them from stretching the flex row's
|
|
89
|
-
// height/width — same treatment as the edit/delete icons above.
|
|
90
98
|
.rcs-cta-tag-revert {
|
|
91
99
|
display: flex;
|
|
92
100
|
align-items: center;
|
|
@@ -95,7 +103,15 @@
|
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
.rcs-cta-taglist {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
98
108
|
flex-shrink: 0;
|
|
109
|
+
|
|
110
|
+
// CapTagList floats this trigger right, taking it out of flow, which
|
|
111
|
+
// let it paint over the edit icon instead of occupying real width.
|
|
112
|
+
.tooltip-add-label-container {
|
|
113
|
+
float: none;
|
|
114
|
+
}
|
|
99
115
|
}
|
|
100
116
|
}
|
|
101
117
|
|
|
@@ -83,9 +83,13 @@ export default defineMessages({
|
|
|
83
83
|
id: `${prefix}.comingSoonTooltip`,
|
|
84
84
|
defaultMessage: 'Right now only one Call to action button is enabled, in future you would be able to add upto 3 buttons.',
|
|
85
85
|
},
|
|
86
|
-
|
|
87
|
-
id: `${prefix}.
|
|
88
|
-
defaultMessage: 'Button text cannot be empty',
|
|
86
|
+
ctaButtonTextEmptyMessage: {
|
|
87
|
+
id: `${prefix}.ctaButtonTextEmptyMessage`,
|
|
88
|
+
defaultMessage: 'Button text cannot be empty or contain only whitespace',
|
|
89
|
+
},
|
|
90
|
+
ctaButtonTextErrorMessage: {
|
|
91
|
+
id: `${prefix}.ctaButtonTextErrorMessage`,
|
|
92
|
+
defaultMessage: 'Newlines, Emojis, or Special characters are not allowed',
|
|
89
93
|
},
|
|
90
94
|
ctaButtonTextLengthError: {
|
|
91
95
|
id: `${prefix}.ctaButtonTextLengthError`,
|
|
@@ -521,7 +521,23 @@ describe('CapActionButton', () => {
|
|
|
521
521
|
initializeComponent([initial]);
|
|
522
522
|
const buttontextInput = screen.getByPlaceholderText('Enter button text');
|
|
523
523
|
fireEvent.change(buttontextInput, { target: { value: '&*^', id: 0 } });
|
|
524
|
-
expect(screen.getByText(/
|
|
524
|
+
expect(screen.getByText(/special characters are not allowed/i)).toBeInTheDocument();
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('should show empty error for whitespace-only button text (onButtonTextChange)', () => {
|
|
528
|
+
const initial = {
|
|
529
|
+
index: 0,
|
|
530
|
+
type: RCS_BUTTON_TYPES.QUICK_REPLY,
|
|
531
|
+
text: 'Shop',
|
|
532
|
+
phoneNumber: '',
|
|
533
|
+
url: '',
|
|
534
|
+
postback: 'Shop',
|
|
535
|
+
isSaved: false,
|
|
536
|
+
};
|
|
537
|
+
initializeComponent([initial]);
|
|
538
|
+
const buttontextInput = screen.getByPlaceholderText('Enter button text');
|
|
539
|
+
fireEvent.change(buttontextInput, { target: { value: ' ', id: 0 } });
|
|
540
|
+
expect(screen.getByText(/cannot be empty or contain only whitespace/i)).toBeInTheDocument();
|
|
525
541
|
});
|
|
526
542
|
|
|
527
543
|
it('should show error for invalid url (onUrlChange)', () => {
|
|
@@ -57,6 +57,10 @@ export default defineMessages({
|
|
|
57
57
|
id: `${prefix}.ctaButtonErrorMessage`,
|
|
58
58
|
defaultMessage: "Newlines, Emojis, or Special characters are not allowed",
|
|
59
59
|
},
|
|
60
|
+
ctaButtonTextEmptyMessage: {
|
|
61
|
+
id: `${prefix}.ctaButtonTextEmptyMessage`,
|
|
62
|
+
defaultMessage: 'Button text cannot be empty or contain only whitespace',
|
|
63
|
+
},
|
|
60
64
|
addButton: {
|
|
61
65
|
id: `${prefix}.addButton`,
|
|
62
66
|
defaultMessage: 'Add button',
|
|
@@ -126,7 +126,9 @@ export const CapWhatsappCarouselButton = (props) => {
|
|
|
126
126
|
|
|
127
127
|
const onButtonTextChange = (value, buttonIndex) => {
|
|
128
128
|
let errorMessage = '';
|
|
129
|
-
if (!
|
|
129
|
+
if (!value.trim()) {
|
|
130
|
+
errorMessage = formatMessage(capWhatsappCTAMsg.ctaButtonTextEmptyMessage);
|
|
131
|
+
} else if (!isValidWhatsappCtaText(value)) {
|
|
130
132
|
errorMessage = formatMessage(capWhatsappCTAMsg.ctaButtonErrorMessage);
|
|
131
133
|
}
|
|
132
134
|
onValueChange(buttonIndex, [{fieldName: 'text', value}, {fieldName: 'textError', value: errorMessage}]);
|
|
@@ -333,12 +335,11 @@ export const CapWhatsappCarouselButton = (props) => {
|
|
|
333
335
|
noWrap
|
|
334
336
|
>
|
|
335
337
|
<CapColumn
|
|
336
|
-
|
|
337
|
-
className={`${isPhoneNumberType ? 'whatsapp-saved-cta-phone-icon' : ''}`}
|
|
338
|
+
className={`whatsapp-saved-cta-cell ${isPhoneNumberType ? 'whatsapp-saved-cta-phone-icon' : ''}`}
|
|
338
339
|
>
|
|
339
340
|
<CapIcon size="s" type={isPhoneNumberType ? 'call' : isURLtype ? 'launch' : 'six-dots'} />
|
|
340
341
|
</CapColumn>
|
|
341
|
-
<CapColumn
|
|
342
|
+
<CapColumn className="whatsapp-saved-cta-cell whatsapp-saved-cta-text-cell">
|
|
342
343
|
<CapLabel
|
|
343
344
|
type="label2"
|
|
344
345
|
className="whatsapp-saved-cta-button-text"
|
|
@@ -346,39 +347,47 @@ export const CapWhatsappCarouselButton = (props) => {
|
|
|
346
347
|
{button?.text}
|
|
347
348
|
</CapLabel>
|
|
348
349
|
</CapColumn>
|
|
349
|
-
|
|
350
|
-
|
|
350
|
+
<CapColumn className="whatsapp-saved-cta-cell whatsapp-saved-cta-type-cell">
|
|
351
|
+
{isURLtype && (
|
|
352
|
+
<CapLabel className="url-type" type="label2">
|
|
353
|
+
{button?.urlType === STATIC_URL
|
|
354
|
+
? formatMessage(capWhatsappCTAMsg.ctaWebsiteTypeStatic)
|
|
355
|
+
: formatMessage(capWhatsappCTAMsg.ctaWebsiteTypeDynamic)}
|
|
356
|
+
</CapLabel>
|
|
357
|
+
)}
|
|
358
|
+
</CapColumn>
|
|
359
|
+
<CapColumn className="whatsapp-saved-cta-cell whatsapp-saved-cta-url-cell whatsapp-url-preview-wrapper">
|
|
360
|
+
{isPhoneNumberType && (
|
|
351
361
|
<CapLabel className="phone">
|
|
352
362
|
{button?.phone_number}
|
|
353
363
|
</CapLabel>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
364
|
+
)}
|
|
365
|
+
{isURLtype && (
|
|
366
|
+
<CapTooltip
|
|
367
|
+
title={button?.url}
|
|
368
|
+
placement="top"
|
|
369
|
+
wrapperClassName="whatsapp-cta-url-tooltip-wrapper"
|
|
370
|
+
triggerClassName="whatsapp-cta-url-tooltip-trigger"
|
|
371
|
+
>
|
|
372
|
+
<CapLabel className="url">{button?.url}</CapLabel>
|
|
373
|
+
</CapTooltip>
|
|
374
|
+
)}
|
|
375
|
+
</CapColumn>
|
|
376
|
+
<CapColumn className="whatsapp-saved-cta-cell whatsapp-saved-cta-actions-cell">
|
|
377
|
+
{!isEditFlow && (
|
|
378
|
+
<CapRow
|
|
379
|
+
className="whatsapp-saved-cta-action-icons"
|
|
380
|
+
type="flex"
|
|
381
|
+
align="middle"
|
|
382
|
+
noWrap
|
|
383
|
+
>
|
|
384
|
+
<CapIcon size="s" type="edit" onClick={() => handleEditButton(index)}/>
|
|
385
|
+
{parseInt(carouselIndex, 10) === 0 && (
|
|
386
|
+
<CapIcon size="s" type="delete" onClick={() => handleDeleteButton(index)} disabled={buttonData?.length === 1}/>
|
|
387
|
+
)}
|
|
371
388
|
</CapRow>
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
{!isEditFlow && (
|
|
375
|
-
<CapRow type='flex' align='middle' justify="end" gap={12} fillSpace>
|
|
376
|
-
<CapIcon size="s" type="edit" onClick={() => handleEditButton(index)}/>
|
|
377
|
-
{parseInt(carouselIndex, 10) === 0 && (
|
|
378
|
-
<CapIcon size="s" type="delete" onClick={() => handleDeleteButton(index)} disabled={buttonData?.length === 1}/>
|
|
379
|
-
)}
|
|
380
|
-
</CapRow>
|
|
381
|
-
)}
|
|
389
|
+
)}
|
|
390
|
+
</CapColumn>
|
|
382
391
|
{isEditFlow && button?.urlType === DYNAMIC_URL && button?.url.includes('{{1}}') && (
|
|
383
392
|
<CapColumn>
|
|
384
393
|
<TagList
|