@capillarytech/creatives-library 9.0.44 → 9.0.46

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.
Files changed (33) hide show
  1. package/constants/unified.js +1 -0
  2. package/package.json +1 -1
  3. package/v2Components/CommonTestAndPreview/UnifiedPreview/SmsPreviewContent.js +21 -1
  4. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberCarouselPreviewCards.js +127 -0
  5. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +106 -15
  6. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +144 -1
  7. package/v2Components/CommonTestAndPreview/UnifiedPreview/_viberCarouselPreviewCards.scss +133 -0
  8. package/v2Components/CommonTestAndPreview/constants.js +2 -0
  9. package/v2Components/CommonTestAndPreview/index.js +241 -26
  10. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/SmsPreviewContent.test.js +32 -0
  11. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +364 -0
  12. package/v2Components/CommonTestAndPreview/tests/utils.test.js +49 -0
  13. package/v2Components/CommonTestAndPreview/utils.js +20 -0
  14. package/v2Components/SmsFallback/constants.js +19 -0
  15. package/v2Components/SmsFallback/index.js +0 -3
  16. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  17. package/v2Containers/SmsTrai/Edit/dltVarTypes.js +88 -0
  18. package/v2Containers/SmsTrai/Edit/index.js +206 -96
  19. package/v2Containers/SmsTrai/Edit/index.scss +117 -1
  20. package/v2Containers/SmsTrai/Edit/messages.js +40 -0
  21. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +21164 -4559
  22. package/v2Containers/SmsTrai/Edit/tests/dltVarTypes.test.js +100 -0
  23. package/v2Containers/Templates/_templates.scss +203 -2
  24. package/v2Containers/Templates/index.js +193 -42
  25. package/v2Containers/Templates/messages.js +12 -0
  26. package/v2Containers/Viber/constants.js +21 -0
  27. package/v2Containers/Viber/index.js +719 -49
  28. package/v2Containers/Viber/index.scss +175 -0
  29. package/v2Containers/Viber/messages.js +121 -0
  30. package/v2Containers/Viber/tests/index.test.js +80 -0
  31. package/v2Containers/Whatsapp/index.js +2 -0
  32. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +612 -0
  33. package/v2Containers/Whatsapp/tests/index.test.js +8 -0
@@ -0,0 +1,100 @@
1
+ import {
2
+ DLT_VAR_TYPE_KEYS,
3
+ DLT_VAR_TYPE_META,
4
+ DLT_LEGACY_VAR_TOKEN,
5
+ DLT_LEGACY_VAR_REGEX,
6
+ ANY_DLT_HASH_TOKEN_PROBE_REGEX,
7
+ isAnyDltVarToken,
8
+ getDltVarTypeKey,
9
+ getDltVarTypeMeta,
10
+ getDltVarCharLimit,
11
+ } from '../dltVarTypes';
12
+
13
+ describe('dltVarTypes', () => {
14
+ it('exposes the six DLT types with short + long syntax + char limit', () => {
15
+ expect(DLT_VAR_TYPE_KEYS).toEqual([
16
+ 'numeric', 'alphanumeric', 'url', 'urlott', 'cbn', 'email',
17
+ ]);
18
+ DLT_VAR_TYPE_KEYS.forEach((key) => {
19
+ const meta = DLT_VAR_TYPE_META[key];
20
+ expect(meta.short).toMatch(/^\{#[^#]+#\}$/);
21
+ expect(meta.long).toMatch(/^\{#[^#]+#\}$/);
22
+ expect(typeof meta.charLimit).toBe('number');
23
+ });
24
+ });
25
+
26
+ describe('isAnyDltVarToken', () => {
27
+ it('accepts short and long DLT token forms', () => {
28
+ expect(isAnyDltVarToken('{#num#}')).toBe(true);
29
+ expect(isAnyDltVarToken('{#numeric#}')).toBe(true);
30
+ expect(isAnyDltVarToken('{#var#}')).toBe(true);
31
+ expect(isAnyDltVarToken('{# alp #}')).toBe(true);
32
+ });
33
+
34
+ it('rejects non-DLT strings and non-strings', () => {
35
+ expect(isAnyDltVarToken('{{mustache}}')).toBe(false);
36
+ expect(isAnyDltVarToken('plain text')).toBe(false);
37
+ expect(isAnyDltVarToken('')).toBe(false);
38
+ expect(isAnyDltVarToken(null)).toBe(false);
39
+ expect(isAnyDltVarToken(undefined)).toBe(false);
40
+ expect(isAnyDltVarToken(42)).toBe(false);
41
+ });
42
+ });
43
+
44
+ describe('getDltVarTypeKey', () => {
45
+ it('resolves short and long spellings + legacy {#var#} to a type key', () => {
46
+ expect(getDltVarTypeKey('{#num#}')).toBe('numeric');
47
+ expect(getDltVarTypeKey('{#numeric#}')).toBe('numeric');
48
+ expect(getDltVarTypeKey('{#ALP#}')).toBe('alphanumeric');
49
+ expect(getDltVarTypeKey('{# url #}')).toBe('url');
50
+ expect(getDltVarTypeKey(DLT_LEGACY_VAR_TOKEN)).toBe('alphanumeric');
51
+ });
52
+
53
+ it('returns null for unknown / non-string tokens', () => {
54
+ expect(getDltVarTypeKey('{#foobar#}')).toBe(null);
55
+ expect(getDltVarTypeKey('')).toBe(null);
56
+ expect(getDltVarTypeKey(null)).toBe(null);
57
+ expect(getDltVarTypeKey(undefined)).toBe(null);
58
+ });
59
+ });
60
+
61
+ describe('getDltVarTypeMeta', () => {
62
+ it('returns the full meta object for a valid token', () => {
63
+ const meta = getDltVarTypeMeta('{#url#}');
64
+ expect(meta.charLimit).toBe(120);
65
+ expect(meta.placeholderKey).toBe('dltVarPlaceholderUrl');
66
+ });
67
+
68
+ it('returns null for an unknown token', () => {
69
+ expect(getDltVarTypeMeta('{#nope#}')).toBe(null);
70
+ });
71
+ });
72
+
73
+ describe('getDltVarCharLimit', () => {
74
+ it('returns the type-specific char limit for known tokens', () => {
75
+ expect(getDltVarCharLimit('{#num#}')).toBe(40);
76
+ expect(getDltVarCharLimit('{#url#}')).toBe(120);
77
+ expect(getDltVarCharLimit('{#cbn#}')).toBe(14);
78
+ });
79
+
80
+ it('falls back to the supplied default when the token is unknown', () => {
81
+ expect(getDltVarCharLimit('{#nope#}', 40)).toBe(40);
82
+ expect(getDltVarCharLimit('{#nope#}', 99)).toBe(99);
83
+ expect(getDltVarCharLimit(null)).toBe(40);
84
+ });
85
+ });
86
+
87
+ describe('exported regexes', () => {
88
+ it('DLT_LEGACY_VAR_REGEX matches only the legacy generic token', () => {
89
+ expect(DLT_LEGACY_VAR_REGEX.test('body {#var#} tail')).toBe(true);
90
+ expect(DLT_LEGACY_VAR_REGEX.test('body {#VAR#} tail')).toBe(true);
91
+ expect(DLT_LEGACY_VAR_REGEX.test('body {#num#} tail')).toBe(false);
92
+ });
93
+
94
+ it('ANY_DLT_HASH_TOKEN_PROBE_REGEX detects any hash token safely across calls', () => {
95
+ expect(ANY_DLT_HASH_TOKEN_PROBE_REGEX.test('hi {#alp#} there')).toBe(true);
96
+ expect(ANY_DLT_HASH_TOKEN_PROBE_REGEX.test('hi {#url#} again')).toBe(true);
97
+ expect(ANY_DLT_HASH_TOKEN_PROBE_REGEX.test('plain')).toBe(false);
98
+ });
99
+ });
100
+ });
@@ -160,7 +160,6 @@
160
160
  line-clamp: 3;
161
161
  }
162
162
  }
163
-
164
163
  }
165
164
 
166
165
  &.no-image .ant-card-meta {
@@ -168,6 +167,44 @@
168
167
  word-wrap: break-word;
169
168
  }
170
169
  }
170
+
171
+ &.viber-carousel-static {
172
+ height: auto;
173
+ min-height: 21.125rem;
174
+ overflow: visible;
175
+
176
+ .ant-card-body {
177
+ height: auto;
178
+ overflow: visible;
179
+ padding-bottom: $CAP_SPACE_12;
180
+
181
+ .ant-card-meta,
182
+ .ant-card-meta-detail,
183
+ .ant-card-meta-description {
184
+ height: auto;
185
+ max-height: none;
186
+ overflow: visible;
187
+ }
188
+
189
+ .truncate-text-viber {
190
+ .ant-card-meta-description {
191
+ display: block;
192
+ overflow: visible;
193
+ max-height: none;
194
+ -webkit-box-orient: unset;
195
+ -webkit-line-clamp: unset;
196
+ line-clamp: unset;
197
+ }
198
+ }
199
+
200
+ .content,
201
+ .cards,
202
+ .card {
203
+ overflow: visible;
204
+ height: auto;
205
+ }
206
+ }
207
+ }
171
208
 
172
209
 
173
210
  &.carousel-img-section {
@@ -1222,6 +1259,147 @@
1222
1259
  max-height: 11rem;
1223
1260
  margin-bottom: $CAP_SPACE_04;
1224
1261
  }
1262
+
1263
+ /* Listing carousel preview: always grow with content so title + buttons stay visible for any image */
1264
+ .cap-custom-card.ant-card.VIBER.viber-carousel-static {
1265
+ height: auto;
1266
+ min-height: 21.125rem;
1267
+ overflow: visible;
1268
+
1269
+ > .ant-card-body {
1270
+ height: auto;
1271
+ max-height: none;
1272
+ overflow: visible;
1273
+ padding-bottom: $CAP_SPACE_12;
1274
+ }
1275
+
1276
+ .ant-card-meta,
1277
+ .ant-card-meta-detail,
1278
+ .ant-card-meta-description,
1279
+ .truncate-text-viber,
1280
+ .truncate-text-viber .ant-card-meta-description {
1281
+ height: auto;
1282
+ max-height: none;
1283
+ overflow: visible;
1284
+ display: block;
1285
+ -webkit-line-clamp: unset;
1286
+ line-clamp: unset;
1287
+ -webkit-box-orient: unset;
1288
+ }
1289
+ }
1290
+
1291
+ .viber-carousel-static {
1292
+ .content {
1293
+ width: 100%;
1294
+ overflow: visible;
1295
+ display: flex;
1296
+ flex-direction: column;
1297
+ height: auto;
1298
+
1299
+ .message-box {
1300
+ width: 100%;
1301
+ height: auto;
1302
+ border-radius: $CAP_SPACE_04;
1303
+ background: $CAP_WHITE;
1304
+ margin-bottom: $CAP_SPACE_08;
1305
+ display: flex;
1306
+ align-items: flex-start;
1307
+
1308
+ .message {
1309
+ display: -webkit-box;
1310
+ width: 100%;
1311
+ color: $CAP_G01;
1312
+ line-height: 1.3;
1313
+ overflow: hidden;
1314
+ text-overflow: ellipsis;
1315
+ -webkit-line-clamp: 2;
1316
+ line-clamp: 2;
1317
+ -webkit-box-orient: vertical;
1318
+ white-space: normal;
1319
+ word-break: break-word;
1320
+ }
1321
+ }
1322
+
1323
+ .cards {
1324
+ display: flex;
1325
+ gap: $CAP_SPACE_08;
1326
+ overflow: visible;
1327
+ width: 100%;
1328
+ height: auto;
1329
+
1330
+ .card {
1331
+ flex: 1;
1332
+ min-width: 0;
1333
+ width: 100%;
1334
+ background: $CAP_WHITE;
1335
+ border-radius: $CAP_SPACE_04;
1336
+ overflow: visible;
1337
+ display: flex;
1338
+ flex-direction: column;
1339
+ height: auto;
1340
+
1341
+ .image {
1342
+ width: 100%;
1343
+ max-height: 11rem;
1344
+ margin-bottom: $CAP_SPACE_04;
1345
+ border-radius: $CAP_SPACE_04;
1346
+ display: block;
1347
+ flex-shrink: 0;
1348
+ }
1349
+
1350
+ .image-placeholder {
1351
+ width: 100%;
1352
+ height: 7.143rem;
1353
+ border-radius: $CAP_SPACE_04;
1354
+ background: $CAP_G07;
1355
+ }
1356
+
1357
+ .text {
1358
+ display: -webkit-box;
1359
+ color: $CAP_G01;
1360
+ margin: $CAP_SPACE_04 0;
1361
+ line-height: 1.3;
1362
+ overflow: hidden;
1363
+ text-overflow: ellipsis;
1364
+ white-space: normal;
1365
+ -webkit-line-clamp: 2;
1366
+ line-clamp: 2;
1367
+ -webkit-box-orient: vertical;
1368
+ flex-shrink: 0;
1369
+ width: 100%;
1370
+ visibility: visible;
1371
+ }
1372
+
1373
+ .buttons {
1374
+ display: flex;
1375
+ flex-direction: column;
1376
+ gap: $CAP_SPACE_04;
1377
+ flex-shrink: 0;
1378
+ width: 100%;
1379
+ visibility: visible;
1380
+
1381
+ .button {
1382
+ display: block;
1383
+ min-height: 1.5rem;
1384
+ border-radius: $CAP_SPACE_12;
1385
+ background: $CAP_PURPLE01;
1386
+ color: $CAP_WHITE;
1387
+ text-align: center;
1388
+ padding: 0.179rem $CAP_SPACE_08;
1389
+ white-space: normal;
1390
+ }
1391
+
1392
+ .button-secondary {
1393
+ color: $CAP_PURPLE01;
1394
+ background: transparent;
1395
+ border: none;
1396
+ box-shadow: none;
1397
+ }
1398
+ }
1399
+ }
1400
+ }
1401
+ }
1402
+ }
1225
1403
  .whatsapp-doc {
1226
1404
  width: 100%;
1227
1405
  max-height: 123px;
@@ -1480,12 +1658,35 @@
1480
1658
  cursor: not-allowed;
1481
1659
  }
1482
1660
 
1661
+ .sms-service-explicit-retired-banner.ant-alert {
1662
+ margin: 0 $CAP_SPACE_16 $CAP_SPACE_16 $CAP_SPACE_16;
1663
+ padding: $CAP_SPACE_08 $CAP_SPACE_12;
1664
+ align-items: center;
1665
+ }
1666
+
1667
+ .sms-template-category-tag {
1668
+ margin-bottom: $CAP_SPACE_12;
1669
+ padding: 0 $CAP_SPACE_12;
1670
+ border-radius: 62.5rem;
1671
+ }
1672
+
1673
+
1674
+ .sms-template-legacy-warning.ant-alert {
1675
+ margin-top: $CAP_SPACE_12;
1676
+
1677
+ .ant-alert-title,
1678
+ .ant-alert-title span {
1679
+ font-weight: 400;
1680
+ white-space: normal;
1681
+ }
1682
+ }
1683
+
1483
1684
  .template-action-dropdown {
1484
1685
  min-width: 14.286rem !important;
1485
1686
  .template-action-menu {
1486
1687
  padding: 4px 0px;
1487
1688
  .template-action-menu-item {
1488
- padding: 12px 24px;
1689
+ padding: $CAP_SPACE_12 $CAP_SPACE_24;
1489
1690
  }
1490
1691
  }
1491
1692
  }
@@ -73,6 +73,7 @@ import * as ebillActions from '../Ebill/actions';
73
73
  import * as emailActions from '../Email/actions';
74
74
  import * as lineActions from '../Line/Container/actions';
75
75
  import * as viberActions from '../Viber/actions';
76
+ import { VIBER_MEDIA_TYPES } from '../Viber/constants';
76
77
  import * as facebookActions from '../Facebook/actions';
77
78
  import * as whatsappActions from '../Whatsapp/actions';
78
79
  import * as rcsActions from '../Rcs/actions';
@@ -142,7 +143,10 @@ import {CREATIVE} from '../Facebook/constants';
142
143
  import videoPlay from '../../assets/videoPlay.svg';
143
144
  import whatsappImageEmptyPreview from '../../v2Components/TemplatePreview/assets/images/empty_image_preview.svg';
144
145
  import whatsappVideoEmptyPreview from '../../v2Components/TemplatePreview/assets/images/empty_video_preview.svg';
145
- import { CAP_SPACE_16, CAP_G08, CAP_G05, CAP_SPACE_08, CAP_SPACE_12 } from '@capillarytech/cap-ui-library/styled/variables';
146
+ import {
147
+ CAP_SPACE_16, CAP_G08, CAP_G05, CAP_SPACE_08, CAP_SPACE_12, CAP_YELLOW01,
148
+ } from '@capillarytech/cap-ui-library/styled/variables';
149
+ import { SMS_CATEGORY_TAG_STYLES, SMS_CATEGORY_LABELS } from '../../v2Components/SmsFallback/constants';
146
150
  import { GA } from '@capillarytech/cap-ui-utils';
147
151
  import { MAPP_SDK } from '../InApp/constants';
148
152
  import injectReducer from '../../utils/injectReducer';
@@ -158,6 +162,7 @@ import { v2MobilePushSagas } from '../MobilePushNew/sagas';
158
162
  import { AUTO_CAROUSEL, BIG_PICTURE, FILMSTRIP_CAROUSEL, MANUAL_CAROUSEL } from '../MobilePushNew/constants';
159
163
  import CapPageSpinner from '../../v2Components/CapPageSpinner';
160
164
  import webPushSagas from '../WebPush/sagas';
165
+ import { DLT_LEGACY_VAR_REGEX } from '../SmsTrai/Edit/dltVarTypes';
161
166
  const withMobilePushNewSaga = injectSaga({ key: 'mobilePushNew', saga: v2MobilePushSagas, mode: DAEMON });
162
167
  const withWebPushSaga = injectSaga({ key: 'webPush', saga: webPushSagas, mode: DAEMON });
163
168
 
@@ -454,9 +459,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
454
459
  }
455
460
 
456
461
  checkDLTfeatureEnable() {
457
- const {smsRegister, isFullMode} = this.props;
458
- const isTraiDlt = commonUtil.isTraiDLTEnable(isFullMode, smsRegister);
459
- return isTraiDlt;
462
+ const { smsRegister, isFullMode, localTemplatesConfig } = this.props;
463
+ if (commonUtil.isTraiDLTEnable(isFullMode, smsRegister)) return true;
464
+ // Local-templates picker (RCS SMS fallback, campaigns): apply DLT UI whenever the org has
465
+ // the feature, even if the parent context wasn't marked `smsRegister === 'DLT'`.
466
+ return !!localTemplatesConfig?.useLocalTemplates && commonUtil.hasTraiDltFeature();
460
467
  }
461
468
 
462
469
  componentDidMount() {
@@ -1399,6 +1406,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1399
1406
  const image = viberContent.image || {};
1400
1407
  const video = viberContent.video || {};
1401
1408
  const button = viberContent.button || {};
1409
+ const messageType = viberContent.type || '';
1410
+ const cardsRaw = viberContent.cards;
1411
+ const cards = Array.isArray(cardsRaw) ? cardsRaw : [];
1412
+ const isCarousel =
1413
+ messageType === VIBER_MEDIA_TYPES.CAROUSEL || cards.length > 0;
1402
1414
 
1403
1415
  const viberPreview = {
1404
1416
  messageContent: text,
@@ -1416,14 +1428,39 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1416
1428
  };
1417
1429
  }
1418
1430
 
1431
+ if (isCarousel) {
1432
+ viberPreview.type = VIBER_MEDIA_TYPES.CAROUSEL;
1433
+ viberPreview.cards = cards.map((card) => ({
1434
+ text: card?.text || '',
1435
+ mediaUrl: card?.mediaUrl || '',
1436
+ buttons: (card?.buttons || []).map((btn) => ({
1437
+ title: btn?.title || '',
1438
+ action: btn?.action || '',
1439
+ })),
1440
+ }));
1441
+ viberPreview.showCarouselEditorPreview = true;
1442
+ }
1443
+
1419
1444
  // Extract account name
1420
1445
  const accountName = get(template, 'definition.accountName', '');
1421
1446
 
1422
1447
  // Return Viber content object (same as Viber/index.js getTemplateContent)
1423
1448
  return {
1424
1449
  viberPreviewContent: viberPreview,
1425
- accountName: accountName ? [accountName] : [],
1426
- brandName: accountName ? [accountName] : [],
1450
+ accountName: accountName || '',
1451
+ brandName: accountName || '',
1452
+ messageContent: text,
1453
+ ...(isCarousel && {
1454
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1455
+ cards: viberPreview.cards,
1456
+ }),
1457
+ ...(!isCarousel && !isEmpty(button) && button.text && (button.url || viberContent.buttonURL) && {
1458
+ button: {
1459
+ text: button.text,
1460
+ url: button.url || viberContent.buttonURL || '',
1461
+ },
1462
+ }),
1463
+ buttonURL: button.url || viberContent.buttonURL || '',
1427
1464
  };
1428
1465
  }
1429
1466
 
@@ -2045,11 +2082,20 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2045
2082
  const isCardArchiveEligible = isArchivalEnabled && this.isChannelArchiveEligible(currentChannel, cardWhatsappStatus, cardRcsStatus);
2046
2083
  const isArchivedMode = isArchivalEnabled && get(this.props, 'Templates.isArchivedMode', false);
2047
2084
  const isAnyArchiveInProgress = isArchivalEnabled && !!(get(this.props, 'Templates.archiveInProgress') || get(this.props, 'Templates.unarchiveInProgress') || get(this.props, 'Templates.bulkArchiveInProgress') || get(this.props, 'Templates.bulkUnarchiveInProgress'));
2085
+ const smsBaseForLegacyCheck = template?.versions?.base || {};
2086
+ const updatedSmsEditorForLegacy = smsBaseForLegacyCheck['updated-sms-editor'];
2087
+ const smsBodyForLegacyCheck = Array.isArray(updatedSmsEditorForLegacy)
2088
+ ? updatedSmsEditorForLegacy.join('')
2089
+ : updatedSmsEditorForLegacy ?? smsBaseForLegacyCheck['sms-editor'] ?? '';
2090
+ const isDltLegacyTemplate = currentChannel === SMS
2091
+ && isTraiDltFeature
2092
+ && !this.props.isFullMode
2093
+ && DLT_LEGACY_VAR_REGEX.test(smsBodyForLegacyCheck);
2048
2094
  const templateData = {
2049
2095
  key: `${currentChannel}-card-${template?.name}`,
2050
2096
  title: (
2051
2097
  <span className="template-card-title" title={template?.name}>
2052
- {isCardArchiveEligible && this.renderCardSelectionCheckbox({ templateId: template._id, selectedIds: selectedIdsArrayForCard, isDisabled: isAnyArchiveInProgress })}
2098
+ {isCardArchiveEligible && this.renderCardSelectionCheckbox({ templateId: template._id, selectedIds: selectedIdsArrayForCard, isDisabled: isAnyArchiveInProgress || isDltLegacyTemplate })}
2053
2099
  <CapLabel.CapLabelInline type="label1" title={template?.name} className="template-card-name">
2054
2100
  {template?.name}
2055
2101
  {currentChannel === INAPP && (
@@ -2109,20 +2155,39 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2109
2155
  })()
2110
2156
  ],
2111
2157
  hoverOption: isArchivedMode || !this.canPerform(PERMISSIONS.CREATIVE_EDIT) ? null : (
2112
- <CapButton
2113
- className={
2114
- this.props.isFullMode
2115
- ? `edit-${channelLowerCase}`
2116
- : `select-${channelLowerCase}`
2117
- }
2118
- onClick={e =>
2119
- handlers.handleEditClick(e, template, undefined, undefined, {
2120
- account: this.state.selectedAccount
2121
- })
2122
- }
2123
- >
2124
- {hoverButtonText}
2125
- </CapButton>
2158
+ isDltLegacyTemplate ? (
2159
+ <CapTooltip
2160
+ title={this.props.intl.formatMessage(messages.smsLegacyBlockedTooltip)}
2161
+ >
2162
+ <CapLabel.CapLabelInline>
2163
+ <CapButton
2164
+ className={
2165
+ this.props.isFullMode
2166
+ ? `edit-${channelLowerCase}`
2167
+ : `select-${channelLowerCase}`
2168
+ }
2169
+ disabled
2170
+ >
2171
+ {hoverButtonText}
2172
+ </CapButton>
2173
+ </CapLabel.CapLabelInline>
2174
+ </CapTooltip>
2175
+ ) : (
2176
+ <CapButton
2177
+ className={
2178
+ this.props.isFullMode
2179
+ ? `edit-${channelLowerCase}`
2180
+ : `select-${channelLowerCase}`
2181
+ }
2182
+ onClick={e =>
2183
+ handlers.handleEditClick(e, template, undefined, undefined, {
2184
+ account: this.state.selectedAccount
2185
+ })
2186
+ }
2187
+ >
2188
+ {hoverButtonText}
2189
+ </CapButton>
2190
+ )
2126
2191
  )
2127
2192
  };
2128
2193
  const {
@@ -2222,22 +2287,49 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2222
2287
  );
2223
2288
  }
2224
2289
  switch (currentChannel) {
2225
- case SMS:
2290
+ case SMS: {
2291
+ const smsBaseForCard = template.versions.base || {};
2292
+ const updatedSmsEditor = smsBaseForCard['updated-sms-editor'];
2293
+ const normalizedUpdatedSmsEditor = Array.isArray(updatedSmsEditor)
2294
+ ? updatedSmsEditor.join('')
2295
+ : updatedSmsEditor;
2296
+ const smsBodyForCard = normalizedUpdatedSmsEditor || smsBaseForCard['sms-editor'] || '';
2297
+ const hasLegacyVarToken = DLT_LEGACY_VAR_REGEX.test(smsBodyForCard);
2298
+ const rawCategoryLabel = smsBaseForCard.type || '';
2299
+ const categoryLabel = rawCategoryLabel.toLowerCase() === SMS_CATEGORY_LABELS.SERVICE_EXPLICIT
2300
+ ? this.props.intl.formatMessage(messages.promotional)
2301
+ : rawCategoryLabel;
2302
+ const categoryTagStyle = SMS_CATEGORY_TAG_STYLES[categoryLabel.toLowerCase()] || {};
2226
2303
  templateData.content = isTraiDltFeature ? (
2227
2304
  <>
2228
- <CapLabel type="label3" style={{ marginBottom: CAP_SPACE_16 }}>
2229
- {template.versions.base.type}
2230
- </CapLabel>
2231
- <CapLabel type="label1">
2232
- {template.versions.base['updated-sms-editor'] === ''
2233
- ? template.versions.base['sms-editor']
2234
- : template.versions.base['updated-sms-editor']?.join('')}
2235
- </CapLabel>
2305
+ {hasLegacyVarToken ? (
2306
+ <CapColoredTag
2307
+ tagColor="rgba(254, 197, 46, 0.15)"
2308
+ tagTextColor={CAP_YELLOW01}
2309
+ tagHeight="1.25rem"
2310
+ tagFontSize="0.75rem"
2311
+ className="sms-template-category-tag"
2312
+ >
2313
+ {this.props.intl.formatMessage(messages.smsLegacyFormatBadge)}
2314
+ </CapColoredTag>
2315
+ ) : categoryLabel ? (
2316
+ <CapColoredTag
2317
+ tagColor={categoryTagStyle.tagColor}
2318
+ tagTextColor={categoryTagStyle.tagTextColor}
2319
+ tagHeight="1.25rem"
2320
+ tagFontSize="0.75rem"
2321
+ className="sms-template-category-tag"
2322
+ >
2323
+ {categoryLabel}
2324
+ </CapColoredTag>
2325
+ ) : null}
2326
+ <CapLabel type="label1">{smsBodyForCard}</CapLabel>
2236
2327
  </>
2237
2328
  ) : (
2238
2329
  template.versions.base['sms-editor']
2239
2330
  );
2240
2331
  break;
2332
+ }
2241
2333
  case EMAIL: {
2242
2334
  const url = template.versions.base.preview_http_url;
2243
2335
  if (url) {
@@ -2436,10 +2528,59 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2436
2528
  image = {},
2437
2529
  button = {},
2438
2530
  video = {},
2531
+ type = '',
2532
+ cards = [],
2439
2533
  } = {},
2440
2534
  } = {},
2441
2535
  } = template.versions.base;
2536
+ const isViberCarousel = type === VIBER_MEDIA_TYPES.CAROUSEL;
2442
2537
  templateData.content = text;
2538
+ if (isViberCarousel) {
2539
+ const previewCards = Array.isArray(cards) ? cards.slice(0, 1) : [];
2540
+ // Bypass CapCustomCard Viber Meta + line-clamp (clips title/buttons for tall images).
2541
+ // Parent class `viber-carousel-static` scopes listing styles in _templates.scss.
2542
+ // cardType hits the default content path; keep VIBER for channel card styles.
2543
+ templateData.cardType = 'VIBER_CAROUSEL_PREVIEW';
2544
+ templateData.className = 'viber-carousel-static VIBER';
2545
+ templateData.content = (
2546
+ <CapRow vertical className="content">
2547
+ <CapRow className="message-box">
2548
+ <CapLabel type="label1" className="message">
2549
+ {text}
2550
+ </CapLabel>
2551
+ </CapRow>
2552
+ <CapRow className="cards">
2553
+ {previewCards?.map((card, cardIdx) => (
2554
+ <CapRow vertical className="card" key={`viber-carousel-static-card-${cardIdx}`}>
2555
+ {card?.mediaUrl ? (
2556
+ <CapImage src={card.mediaUrl} className="image" />
2557
+ ) : (
2558
+ <CapRow className="image-placeholder" />
2559
+ )}
2560
+ <CapLabel type="label1" className="text">
2561
+ {card?.text}
2562
+ </CapLabel>
2563
+ <CapRow vertical className="buttons">
2564
+ {(Array.isArray(card?.buttons) && card.buttons.length
2565
+ ? card.buttons
2566
+ : [{}, {}]
2567
+ ).slice(0, 2).map((carouselButton, btnIdx) => (
2568
+ <CapLabel
2569
+ key={`viber-carousel-static-btn-${cardIdx}-${btnIdx}`}
2570
+ type="label1"
2571
+ className={`button${btnIdx === 1 ? ' button-secondary' : ''}`}
2572
+ >
2573
+ {(carouselButton?.title || '').trim()}
2574
+ </CapLabel>
2575
+ ))}
2576
+ </CapRow>
2577
+ </CapRow>
2578
+ ))}
2579
+ </CapRow>
2580
+ </CapRow>
2581
+ );
2582
+ break;
2583
+ }
2443
2584
  if (!isEmpty(image)) {
2444
2585
  const { url = '' } = image;
2445
2586
  templateData.url = url;
@@ -2761,6 +2902,12 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2761
2902
  )}
2762
2903
  </CapRow>
2763
2904
  {[WHATSAPP, ZALO, INAPP,RCS].includes(currentChannel) && this.selectedFilters()}
2905
+ {currentChannel === SMS && this.checkDLTfeatureEnable() && (
2906
+ <CapInfoNote
2907
+ className="sms-service-explicit-retired-banner"
2908
+ message={this.props.intl.formatMessage(messages.smsServiceExplicitRetiredBanner)}
2909
+ />
2910
+ )}
2764
2911
  {<div>
2765
2912
  {!isEmpty(filteredTemplates) || !isEmpty(this.state.searchText) || !isEmpty(this.props.Templates.templateError) ? (
2766
2913
  <div className={!isEmpty(this.state.searchText) && isEmpty(cardDataList) ? '' : this.isFullMode() ? "v2-pagination-container" : "v2-pagination-container-half"}>
@@ -4788,7 +4935,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
4788
4935
  <CapRadio.CapRadioGroup className="line-filters" defaultValue={smsFilter} onChange={this.setSMSFilter}>
4789
4936
  {
4790
4937
  (() => {
4791
- const { ALL, SERVICE_IMPLICIT, SERVICE_EXPLICIT, PROMOTIONAL } = SMS_FILTERS;
4938
+ const { ALL, SERVICE_IMPLICIT, PROMOTIONAL } = SMS_FILTERS;
4792
4939
  return (
4793
4940
  <>
4794
4941
  <CapRadio.Button value={ALL}><CapLabel type="label2">
@@ -4797,9 +4944,6 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
4797
4944
  <CapRadio.Button value={PROMOTIONAL}><CapLabel type="label2">
4798
4945
  <FormattedMessage {...messages.promotional} />
4799
4946
  </CapLabel></CapRadio.Button>
4800
- <CapRadio.Button value={SERVICE_EXPLICIT}><CapLabel type="label2">
4801
- <FormattedMessage {...messages.serviceExplicit} />
4802
- </CapLabel></CapRadio.Button>
4803
4947
  <CapRadio.Button value={SERVICE_IMPLICIT}><CapLabel type="label2">
4804
4948
  <FormattedMessage {...messages.serviceImplicit} />
4805
4949
  </CapLabel></CapRadio.Button>
@@ -5107,15 +5251,22 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
5107
5251
  ? this.props.Templates.selectedWhatsappAccount.name
5108
5252
  : null
5109
5253
  }
5110
- // Pass formData for EMAIL and SMS channels for tag extraction
5254
+ // Pass formData for tag extraction (EMAIL/SMS use nested formData; VIBER uses content object)
5111
5255
  formData={
5112
- this.state.testAndPreviewContent &&
5113
- (this.state.channel?.toUpperCase() === EMAIL ||
5114
- this.state.channel?.toUpperCase() === SMS) &&
5115
- typeof this.state.testAndPreviewContent === 'object' &&
5116
- this.state.testAndPreviewContent?.formData
5117
- ? this.state.testAndPreviewContent.formData
5118
- : null
5256
+ (() => {
5257
+ const previewContent = this.state.testAndPreviewContent;
5258
+ const channelUpper = this.state.channel?.toUpperCase();
5259
+ if (!previewContent || typeof previewContent !== 'object') {
5260
+ return null;
5261
+ }
5262
+ if ([EMAIL, SMS].includes(channelUpper)) {
5263
+ return previewContent.formData || null;
5264
+ }
5265
+ if (channelUpper === VIBER) {
5266
+ return previewContent;
5267
+ }
5268
+ return null;
5269
+ })()
5119
5270
  }
5120
5271
  />
5121
5272
  )}