@capillarytech/creatives-library 9.0.45 → 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.
@@ -19,11 +19,14 @@ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
19
19
  import CapError from '@capillarytech/cap-ui-library/CapError';
20
20
  import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
21
21
  import CapInfoNote from '@capillarytech/cap-ui-library/CapInfoNote';
22
+ import CapAlert from '@capillarytech/cap-ui-library/CapAlert';
23
+ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
22
24
  import injectSaga from '../../../utils/injectSaga';
23
25
  import injectReducer from '../../../utils/injectReducer';
24
26
  import {
25
27
  CAP_SPACE_16,
26
28
  CAP_SPACE_12,
29
+ CAP_SPACE_08,
27
30
  CAP_SPACE_24,
28
31
  CAP_SPACE_32,
29
32
  CAP_SPACE_04,
@@ -51,6 +54,7 @@ import {
51
54
  FULL,
52
55
  ALL,
53
56
  LIBRARY,
57
+ HORIZONTAL_WHITESPACE_ONLY_REGEX,
54
58
  } from './constants';
55
59
  import v2EditSmsReducer from '../../Sms/Edit/reducer';
56
60
  import { v2SmsEditSagas } from '../../Sms/Edit/sagas';
@@ -68,6 +72,15 @@ import {
68
72
  } from '../../../utils/templateVarUtils';
69
73
  import VarSegmentMessageEditor from '../../../v2Components/VarSegmentMessageEditor';
70
74
  import rcsMessages from '../../Rcs/messages';
75
+ import {
76
+ ANY_DLT_HASH_TOKEN_GLOBAL_REGEX,
77
+ ANY_DLT_HASH_TOKEN_PROBE_REGEX,
78
+ DLT_LEGACY_VAR_REGEX,
79
+ DLT_LEGACY_VAR_TOKEN,
80
+ isAnyDltVarToken,
81
+ getDltVarTypeMeta,
82
+ getDltVarCharLimit,
83
+ } from './dltVarTypes';
71
84
 
72
85
  import './index.scss';
73
86
 
@@ -257,8 +270,8 @@ export const SmsTraiEdit = (props) => {
257
270
  <span className="sms-trai-rcs-fallback-var-hint" style={dltVarSlotHintStyle}>
258
271
  {formatMessage(messages.textAreaCounts, {
259
272
  varCounts,
260
- var: SMS_TRAI_VAR,
261
- charCounts: varCounts * CHARLIMIT,
273
+ var: dltSegmentToken,
274
+ charCounts: varCounts * getDltVarCharLimit(dltSegmentToken, CHARLIMIT),
262
275
  })}
263
276
  </span>
264
277
  );
@@ -288,9 +301,13 @@ export const SmsTraiEdit = (props) => {
288
301
  }
289
302
  `;
290
303
  const TraiEditTemplateDetails = styled.div`
304
+ display: flex;
305
+ flex-wrap: wrap;
306
+ align-items: center;
307
+ gap: ${CAP_SPACE_08};
291
308
  margin-bottom: ${CAP_SPACE_16};
292
309
  .cap-label-span {
293
- margin-right: ${CAP_SPACE_12};
310
+ margin-right: 0;
294
311
  }
295
312
  `;
296
313
 
@@ -415,18 +432,21 @@ export const SmsTraiEdit = (props) => {
415
432
  if (isRcsSmsFallback) return;
416
433
  traiDataRef.current = isFullMode ? templateDetails : templateData;
417
434
  if (traiDataRef.current && !isEmpty(traiDataRef.current)) {
418
- let msg = get(traiDataRef.current, `versions.base.sms-editor`, '');
435
+ const msg = get(traiDataRef.current, `versions.base.sms-editor`, '') || '';
419
436
  const templateMessageArray = [];
420
- while (msg.length !== 0) {
421
- const index = msg.search(SMS_TRAI_VAR);
422
- if (index !== -1) {
423
- templateMessageArray.push(msg.substring(0, index));
424
- templateMessageArray.push(SMS_TRAI_VAR);
425
- msg = msg.substring(index + 7, msg.length);
426
- } else {
427
- templateMessageArray.push(msg);
428
- break;
437
+ let cursor = 0;
438
+ ANY_DLT_HASH_TOKEN_GLOBAL_REGEX.lastIndex = 0;
439
+ let match = ANY_DLT_HASH_TOKEN_GLOBAL_REGEX.exec(msg);
440
+ while (match) {
441
+ if (match.index > cursor) {
442
+ templateMessageArray.push(msg.substring(cursor, match.index));
429
443
  }
444
+ templateMessageArray.push(match[0]);
445
+ cursor = match.index + match[0].length;
446
+ match = ANY_DLT_HASH_TOKEN_GLOBAL_REGEX.exec(msg);
447
+ }
448
+ if (cursor < msg.length) {
449
+ templateMessageArray.push(msg.substring(cursor));
430
450
  }
431
451
  const filteredTemplateMessageArray = templateMessageArray.filter((i) => i === 0 || i);
432
452
  updateTempMsgArray(filteredTemplateMessageArray);
@@ -488,16 +508,18 @@ export const SmsTraiEdit = (props) => {
488
508
  let varCount = 1;
489
509
  let horizontalSpaceCount = 0;
490
510
  for (let i = 0; i < tempMsgArray.length; i += 1) {
491
- if (tempMsgArray[i] === SMS_TRAI_VAR) {
511
+ if (isAnyDltVarToken(tempMsgArray[i])) {
492
512
  const nextElem =
493
513
  tempMsgArray[i === tempMsgArray.length - 1 ? -1 : i + 1];
494
- if (nextElem?.replace(/[^\S\r\n]/gm, '') === '') {
514
+ if (nextElem?.replace(HORIZONTAL_WHITESPACE_ONLY_REGEX, '') === '') {
495
515
  horizontalSpaceCount += 1;
496
516
  }
497
- if (tempMsgArray[i] !== nextElem && nextElem?.replace(/[^\S\r\n]/gm, '') !== '') {
498
- varMapRef.current[`${tempMsgArray[i]}_${(i - varCount - horizontalSpaceCount) + 1}`] = {
517
+ if (tempMsgArray[i] !== nextElem && nextElem?.replace(HORIZONTAL_WHITESPACE_ONLY_REGEX, '') !== '') {
518
+ const slotStartIndex = (i - varCount - horizontalSpaceCount) + 1;
519
+ varMapRef.current[`${SMS_TRAI_VAR}_${slotStartIndex}`] = {
499
520
  data: '',
500
521
  count: varCount,
522
+ token: tempMsgArray[slotStartIndex] || tempMsgArray[i],
501
523
  };
502
524
  varCount = 1;
503
525
  horizontalSpaceCount = 0;
@@ -567,7 +589,7 @@ export const SmsTraiEdit = (props) => {
567
589
  if (
568
590
  !isFullMode &&
569
591
  updatedSmsEditor?.length > 0 &&
570
- !updatedSmsEditor.includes(SMS_TRAI_VAR)
592
+ !updatedSmsEditor.some(isAnyDltVarToken)
571
593
  ) {
572
594
  tagValidationResponseRef.current = runValidateTags(updatedSmsEditor.join(''));
573
595
  const missing = (tagValidationResponseRef.current.missingTags || []).length > 0;
@@ -600,7 +622,7 @@ export const SmsTraiEdit = (props) => {
600
622
  for (let i = _id; i < loopIndex; i += 1) {
601
623
  if (i === _id) {
602
624
  arr[i] = varMapRef.current[key].data; //data for first #var# of the textbox
603
- } else if (arr[i] === SMS_TRAI_VAR) {
625
+ } else if (isAnyDltVarToken(arr[i])) {
604
626
  arr[i] = ''; //'' for remaining #var# of a textbox
605
627
  }
606
628
  }
@@ -698,7 +720,7 @@ export const SmsTraiEdit = (props) => {
698
720
  const editorJoined = Array.isArray(updatedSmsEditor)
699
721
  ? updatedSmsEditor.join('')
700
722
  : String(updatedSmsEditor || '');
701
- if (!isRcsSmsFallback && editorJoined.includes(SMS_TRAI_VAR) && !isFullMode) {
723
+ if (!isRcsSmsFallback && ANY_DLT_HASH_TOKEN_PROBE_REGEX.test(editorJoined) && !isFullMode) {
702
724
  updateIsValidationError(true);
703
725
  } else {
704
726
  // Only show full-screen spinner when waiting on full-mode SMS edit API — not for RCS SMS fallback
@@ -807,7 +829,7 @@ export const SmsTraiEdit = (props) => {
807
829
  const varMapKeys = Object.keys(varMapRef.current)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
808
830
  const loopIndex = varMapKeys[varMapKeys?.indexOf(textAreaId) + 1] || arr.length;
809
831
  for (let i = textAreaId; i < loopIndex; i += 1) {
810
- if (arr[i] === SMS_TRAI_VAR) {
832
+ if (isAnyDltVarToken(arr[i])) {
811
833
  arr[i] = '';
812
834
  }
813
835
  }
@@ -830,17 +852,18 @@ export const SmsTraiEdit = (props) => {
830
852
  const loopIndex = varMapKeys[varMapKeys?.indexOf(_id) + 1] || arr.length;
831
853
 
832
854
  varMapRef.current[`${SMS_TRAI_VAR}_${_id}`].data = value;
855
+ const slotToken = varMapRef.current[`${SMS_TRAI_VAR}_${_id}`]?.token || SMS_TRAI_VAR;
833
856
  if (value === '') {
834
857
  for (let i = _id; i < loopIndex; i += 1) {
835
858
  if (i === _id || arr[i] === '') {
836
- arr[i] = SMS_TRAI_VAR;
859
+ arr[i] = slotToken;
837
860
  }
838
861
  }
839
862
  } else {
840
863
  for (let i = _id; i < loopIndex; i += 1) {
841
864
  if (i === _id) {
842
865
  arr[i] = varMapRef.current[`${SMS_TRAI_VAR}_${_id}`].data;
843
- } else if (arr[i] === SMS_TRAI_VAR) {
866
+ } else if (isAnyDltVarToken(arr[i])) {
844
867
  arr[i] = '';
845
868
  }
846
869
  }
@@ -853,7 +876,7 @@ export const SmsTraiEdit = (props) => {
853
876
  if (idValue >= 0 && updatedSmsEditor) {
854
877
  if (
855
878
  updatedSmsEditor[idValue] &&
856
- updatedSmsEditor[idValue] !== SMS_TRAI_VAR
879
+ !isAnyDltVarToken(updatedSmsEditor[idValue])
857
880
  ) {
858
881
  return updatedSmsEditor[idValue];
859
882
  }
@@ -862,62 +885,86 @@ export const SmsTraiEdit = (props) => {
862
885
  return '';
863
886
  };
864
887
 
865
- const capHeading = (index, varCount) => {
866
- if ((index, varCount)) {
867
- return (
868
- <CapHeading
869
- key={`label${index}`}
870
- type="h6"
871
- style={{ margin: `${CAP_SPACE_04} 0`, float: 'right' }}
872
- >
873
- {formatMessage(messages.textAreaCounts, {
874
- varCounts: varCount,
875
- var: SMS_TRAI_VAR,
876
- charCounts: varCount * CHARLIMIT,
877
- })}
878
- </CapHeading>
879
- );
888
+ const getSlotDisplayMeta = (token) => {
889
+ const meta = getDltVarTypeMeta(token);
890
+ if (!meta) {
891
+ return {
892
+ placeholder: props.intl.formatMessage(messages.inputplaceHolderText),
893
+ charLimit: CHARLIMIT,
894
+ };
880
895
  }
881
- return <></>;
896
+ return {
897
+ placeholder: formatMessage(messages[meta.placeholderKey]),
898
+ charLimit: meta.charLimit,
899
+ };
882
900
  };
883
901
 
902
+ const renderSlotCounter = (currentValue, charLimit) => (
903
+ <CapLabelInline type="label4" className="sms-trai-slot-counter">
904
+ {formatMessage(messages.slotCharCounter, {
905
+ current: (currentValue || '').length,
906
+ max: charLimit,
907
+ })}
908
+ </CapLabelInline>
909
+ );
910
+
884
911
  const renderedContent = () => {
885
912
  const renderArray = [];
886
913
  if (tempMsgArray?.length !== 0) {
887
914
  let varCount = 0;
915
+ let slotStartIndex = -1;
916
+ let slotToken = SMS_TRAI_VAR;
888
917
  tempMsgArray.forEach((elem, index) => {
889
918
  let prevElem = '';
890
919
  prevElem =
891
920
  tempMsgArray[[index === 0 ? -1 : index - 1]];
892
- if (elem.includes(SMS_TRAI_VAR)) {
921
+ if (isAnyDltVarToken(elem)) {
922
+ if (varCount === 0) {
923
+ slotStartIndex = index;
924
+ slotToken = elem;
925
+ }
893
926
  varCount += 1;
894
- if (prevElem !== SMS_TRAI_VAR && prevElem?.replace(/[^\S\r\n]/gm, '') !== '') {
927
+ if (!isAnyDltVarToken(prevElem) && prevElem?.replace(HORIZONTAL_WHITESPACE_ONLY_REGEX, '') !== '') {
928
+ const currentSlotToken = slotToken;
929
+ const currentSlotIndex = slotStartIndex;
930
+ const { placeholder, charLimit: baseLimit } = getSlotDisplayMeta(currentSlotToken);
931
+ const slotValue = textAreaValue(currentSlotIndex);
895
932
  renderArray.push(
896
- <TextArea
897
- id={index}
898
- key={`${elem}${index}`}
899
- placeholder={props.intl.formatMessage(
900
- messages.inputplaceHolderText,
901
- )}
902
- autosize={{ minRows: 1, maxRows: 3 }}
903
- onChange={textAreaValueChange}
904
- value={textAreaValue(index)}
905
- onFocus={setTextAreaId}
906
- errorMessage={
907
- isValidationError &&
908
- updatedSmsEditor[index] === SMS_TRAI_VAR &&
909
- formatMessage(messages.textAreaError)
910
- }
911
- />,
933
+ <CapRow
934
+ type="flex-column"
935
+ key={`slot_${currentSlotToken}_${index}`}
936
+ className="sms-trai-slot"
937
+ >
938
+ <CapRow type="flex" noWrap align="middle" className="sms-trai-slot__field">
939
+ <CapColumn className="sms-trai-slot__chip">
940
+ <CapLabel type="label3">{currentSlotToken}</CapLabel>
941
+ </CapColumn>
942
+ <CapColumn flex="auto" className="sms-trai-slot__input-wrap">
943
+ <TextArea
944
+ id={currentSlotIndex}
945
+ placeholder={placeholder}
946
+ autosize={{ minRows: 1, maxRows: 1 }}
947
+ onChange={textAreaValueChange}
948
+ value={slotValue}
949
+ onFocus={setTextAreaId}
950
+ errorMessage={
951
+ isValidationError &&
952
+ isAnyDltVarToken(updatedSmsEditor[currentSlotIndex]) &&
953
+ formatMessage(messages.textAreaError)
954
+ }
955
+ />
956
+ </CapColumn>
957
+ </CapRow>
958
+ {renderSlotCounter(slotValue, baseLimit * varCount)}
959
+ </CapRow>,
912
960
  );
913
961
  }
914
962
  if (index === tempMsgArray.length - 1) {
915
- renderArray.push(capHeading(index, varCount));
916
963
  varCount = 0;
964
+ slotStartIndex = -1;
917
965
  }
918
- } else if (varCount > 0 && elem.replace(/[^\S\r\n]/gm, '') !== '') {
966
+ } else if (varCount > 0 && elem?.replace(HORIZONTAL_WHITESPACE_ONLY_REGEX, '') !== '') {
919
967
  renderArray.push(
920
- capHeading(index, varCount),
921
968
  <CapHeading
922
969
  key={`${elem}${index}`}
923
970
  style={{ margin: `${CAP_SPACE_16} 0`, clear: 'both' }}
@@ -926,7 +973,8 @@ export const SmsTraiEdit = (props) => {
926
973
  </CapHeading>,
927
974
  );
928
975
  varCount = 0;
929
- } else if (elem.replace(/[^\S\r\n]/gm, '') !== '') {
976
+ slotStartIndex = -1;
977
+ } else if (elem?.replace(HORIZONTAL_WHITESPACE_ONLY_REGEX, '') !== '') {
930
978
  renderArray.push(
931
979
  <CapHeading
932
980
  key={`${elem}${index}`}
@@ -969,7 +1017,7 @@ export const SmsTraiEdit = (props) => {
969
1017
  return;
970
1018
  }
971
1019
  const msgLenWithoutVar = tempMsgArray
972
- ?.filter((i) => i !== SMS_TRAI_VAR)
1020
+ ?.filter((i) => !isAnyDltVarToken(i))
973
1021
  .join('');
974
1022
  const varLen = calculateLenForTextBox();
975
1023
  const totalMsgLen = msgLenWithoutVar?.length + varLen;
@@ -979,13 +1027,15 @@ export const SmsTraiEdit = (props) => {
979
1027
  const calculateLenForTextBox = () => {
980
1028
  let countVarChar = 0;
981
1029
  Object.keys(varMapRef.current).forEach((i) => {
982
- if (varMapRef.current[i].data) {
983
- countVarChar += varMapRef.current[i].data?.length;
984
- if (!showMsgLengthNote && varMapRef.current[i].data?.length > varMapRef.current[i].count * CHARLIMIT) {
1030
+ const slot = varMapRef.current[i];
1031
+ const slotLimit = getDltVarCharLimit(slot.token, CHARLIMIT) * slot.count;
1032
+ if (slot.data) {
1033
+ countVarChar += slot.data?.length;
1034
+ if (!showMsgLengthNote && slot.data?.length > slotLimit) {
985
1035
  updateshowMsgLengthNote(true);
986
1036
  }
987
1037
  } else {
988
- countVarChar += varMapRef.current[i].count * CHARLIMIT;
1038
+ countVarChar += slotLimit;
989
1039
  }
990
1040
  });
991
1041
  return countVarChar;
@@ -1014,12 +1064,8 @@ export const SmsTraiEdit = (props) => {
1014
1064
  const disablehandler = () => {
1015
1065
  if (isRcsSmsFallback) return false;
1016
1066
  if (traiDataRef.current && !isEmpty(traiDataRef.current)) {
1017
- const msg = get(traiDataRef.current, `versions.base.sms-editor`, '');
1018
- const index = msg.search(SMS_TRAI_VAR);
1019
- if (index === -1) {
1020
- return true;
1021
- }
1022
- return false;
1067
+ const msg = get(traiDataRef.current, `versions.base.sms-editor`, '') || '';
1068
+ return !ANY_DLT_HASH_TOKEN_PROBE_REGEX.test(msg);
1023
1069
  }
1024
1070
  return true;
1025
1071
  };
@@ -1069,7 +1115,7 @@ export const SmsTraiEdit = (props) => {
1069
1115
  const shouldShowPreview =
1070
1116
  !isRcsSmsFallback || (showPreviewInRcsFallback && !hidePreview);
1071
1117
  const smsSidePreviewColumn = (
1072
- <CapColumn span={8} offset={0}>
1118
+ <CapColumn span={6} offset={0}>
1073
1119
  <UnifiedPreview
1074
1120
  channel={SMS}
1075
1121
  content={updatedSmsEditor.join('')}
@@ -1097,25 +1143,35 @@ export const SmsTraiEdit = (props) => {
1097
1143
  <CapRow typeof='flex' align='top' noWrap fullWidth>
1098
1144
  <CapRow type='flex' vertical fullWidth>
1099
1145
  {traiDataRef.current && !isEmpty(traiDataRef.current) && !isRcsSmsFallback && (
1100
- <TraiEditTemplateDetails>
1101
- <CapLabelInline type="label1">
1102
- {formatMessage(messages.templateLabel)}
1103
- </CapLabelInline>
1104
- <CapLabelInline type="label2">
1105
- {get(traiDataRef.current, `versions.base.template_name`, '')}
1106
- </CapLabelInline>
1107
-
1108
- <CapLabelInline type="label1">
1109
- {formatMessage(messages.traiEditSeperator)}
1110
- </CapLabelInline>
1111
-
1112
- <CapLabelInline type="label1">
1113
- {formatMessage(messages.senderIdlabel)}
1114
- </CapLabelInline>
1115
- <CapLabelInline type="label2">
1116
- {[...get(traiDataRef.current, `versions.base.header`, [])].join(', ')}
1117
- </CapLabelInline>
1118
- </TraiEditTemplateDetails>
1146
+ <>
1147
+ <TraiEditTemplateDetails>
1148
+ <CapLabelInline type="label1">
1149
+ {formatMessage(messages.templateLabel)}
1150
+ </CapLabelInline>
1151
+ <CapLabelInline type="label2">
1152
+ {get(traiDataRef.current, 'versions.base.template_name', '')}
1153
+ </CapLabelInline>
1154
+ <CapLabelInline type="label1">
1155
+ {formatMessage(messages.traiEditSeperator)}
1156
+ </CapLabelInline>
1157
+ <CapLabelInline type="label1">
1158
+ {formatMessage(messages.senderIdlabel)}
1159
+ </CapLabelInline>
1160
+ <CapLabelInline className="sms-trai-sender-id-chip">
1161
+ <CapIcon
1162
+ type="closed-lock"
1163
+ size="s"
1164
+ className="sms-trai-sender-id-chip__lock"
1165
+ />
1166
+ <CapLabelInline type="label2">
1167
+ {[...get(traiDataRef.current, 'versions.base.header', [])].join(', ')}
1168
+ </CapLabelInline>
1169
+ </CapLabelInline>
1170
+ <CapLabelInline type="label1" className="sms-trai-sender-id-locked-note">
1171
+ {formatMessage(messages.senderIdLockedNote)}
1172
+ </CapLabelInline>
1173
+ </TraiEditTemplateDetails>
1174
+ </>
1119
1175
  )}
1120
1176
  {isRcsSmsFallback &&
1121
1177
  traiDataRef.current &&
@@ -1137,14 +1193,68 @@ export const SmsTraiEdit = (props) => {
1137
1193
  <CapLabelInline type="label1">
1138
1194
  {formatMessage(messages.senderIdlabel)}
1139
1195
  </CapLabelInline>
1140
- <CapLabelInline type="label2">
1141
- {[...get(traiDataRef.current, 'versions.base.header', [])].join(', ')}
1196
+ <CapLabelInline className="sms-trai-sender-id-chip">
1197
+ <CapIcon type="closed-lock" size="s" className="sms-trai-sender-id-chip__lock" />
1198
+ <CapLabelInline type="label2">
1199
+ {[...get(traiDataRef.current, 'versions.base.header', [])].join(', ')}
1200
+ </CapLabelInline>
1201
+ </CapLabelInline>
1202
+ <CapLabelInline type="label1" className="sms-trai-sender-id-locked-note">
1203
+ {formatMessage(messages.senderIdLockedNote)}
1142
1204
  </CapLabelInline>
1143
1205
  </>
1144
1206
  ) : null}
1145
1207
  </TraiEditTemplateDetails>
1146
1208
  )}
1147
- <CapColumn span={shouldShowPreview ? 16 : 24}>
1209
+ {isRcsSmsFallback
1210
+ && traiDltEnabled
1211
+ && !isEmpty(traiDataRef.current)
1212
+ && get(traiDataRef.current, 'versions.base.type', '')
1213
+ ?.toLowerCase()
1214
+ ?.includes('promo') && (
1215
+ <CapInfoNote
1216
+ type="clock-circle"
1217
+ className="sms-trai-promotional-window-banner"
1218
+ noteText={formatMessage(messages.promotionalWindowBanner)}
1219
+ />
1220
+ )}
1221
+ {isRcsSmsFallback
1222
+ && traiDltEnabled
1223
+ && DLT_LEGACY_VAR_REGEX.test(
1224
+ get(traiDataRef.current, 'versions.base.sms-editor', '') || '',
1225
+ ) && (
1226
+ <CapInfoNote
1227
+ type="warning"
1228
+ className="sms-trai-legacy-warning-banner"
1229
+ noteText={formatMessage(messages.smsLegacyFormatWarning, {
1230
+ legacyToken: DLT_LEGACY_VAR_TOKEN,
1231
+ })}
1232
+ />
1233
+ )}
1234
+ <CapColumn span={shouldShowPreview ? 18 : 24}>
1235
+ {!isRcsSmsFallback
1236
+ && !isEmpty(traiDataRef.current)
1237
+ && get(traiDataRef.current, 'versions.base.type', '')
1238
+ ?.toLowerCase()
1239
+ ?.includes('promo') && (
1240
+ <CapInfoNote
1241
+ type="clock-circle"
1242
+ className="sms-trai-promotional-window-banner"
1243
+ noteText={formatMessage(messages.promotionalWindowBanner)}
1244
+ />
1245
+ )}
1246
+ {!isRcsSmsFallback
1247
+ && DLT_LEGACY_VAR_REGEX.test(
1248
+ get(traiDataRef.current, 'versions.base.sms-editor', '') || '',
1249
+ ) && (
1250
+ <CapInfoNote
1251
+ type="warning"
1252
+ className="sms-trai-legacy-warning-banner"
1253
+ noteText={formatMessage(messages.smsLegacyFormatWarning, {
1254
+ legacyToken: DLT_LEGACY_VAR_TOKEN,
1255
+ })}
1256
+ />
1257
+ )}
1148
1258
  <CapRow useLegacy>
1149
1259
  <CapHeader
1150
1260
  title={formatMessage(messages.traiEditTitle)}
@@ -107,7 +107,9 @@
107
107
  /* Non–RCS fallback: segmented editor shell + character count row (was inline styles on CapRow) */
108
108
  .sms-trai-editor-segment-row {
109
109
  background-color: $CAP_G10;
110
- padding: $CAP_SPACE_16;
110
+ padding: $CAP_SPACE_24 $CAP_SPACE_24 $CAP_SPACE_16 $CAP_SPACE_24;
111
+ border-radius: 0.375rem;
112
+ margin-right: $CAP_SPACE_16;
111
113
  }
112
114
 
113
115
  .sms-trai-length-row {
@@ -124,3 +126,117 @@
124
126
  .sms-trai-edit-bottom-spacer {
125
127
  margin-bottom: 6.25rem;
126
128
  }
129
+
130
+ .sms-trai-slot {
131
+ display: flex;
132
+ flex-direction: column;
133
+ margin: 0 0 $CAP_SPACE_16 0;
134
+ }
135
+
136
+ .sms-trai-slot__field {
137
+ display: flex;
138
+ align-items: stretch;
139
+ background-color: $CAP_WHITE;
140
+ border: 1px solid $CAP_G07;
141
+ border-radius: 0.375rem;
142
+ overflow: hidden;
143
+ }
144
+
145
+ .sms-trai-slot__chip {
146
+ flex-shrink: 0;
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ min-width: 4.5rem;
151
+ padding: 0 $CAP_SPACE_08;
152
+ background-color: $CAP_G10;
153
+ border-right: 1px solid $CAP_G07;
154
+ color: $FONT_COLOR_02;
155
+ font-family: Menlo, Monaco, 'Courier New', monospace;
156
+ font-size: 0.75rem;
157
+ line-height: 1.125rem;
158
+ white-space: nowrap;
159
+ }
160
+
161
+ .sms-trai-slot__input-wrap {
162
+ flex: 1;
163
+ min-width: 0;
164
+ }
165
+
166
+ .sms-trai-slot__input-wrap .ant-input,
167
+ .sms-trai-slot__input-wrap textarea.ant-input {
168
+ background-color: transparent;
169
+ border: none;
170
+ border-radius: 0;
171
+ padding: $CAP_SPACE_08 $CAP_SPACE_12;
172
+ min-height: 2.5rem;
173
+ box-shadow: none;
174
+ overflow-x: auto;
175
+ }
176
+
177
+ .sms-trai-slot__input-wrap .ant-input::placeholder,
178
+ .sms-trai-slot__input-wrap textarea.ant-input::placeholder {
179
+ white-space: nowrap;
180
+ overflow: hidden;
181
+ text-overflow: ellipsis;
182
+ }
183
+
184
+ .sms-trai-slot__input-wrap .ant-input:focus,
185
+ .sms-trai-slot__input-wrap .ant-input:active,
186
+ .sms-trai-slot__input-wrap textarea.ant-input:focus,
187
+ .sms-trai-slot__input-wrap textarea.ant-input:active {
188
+ border: none;
189
+ box-shadow: none;
190
+ outline: none;
191
+ }
192
+
193
+ .sms-trai-slot-counter {
194
+ align-self: flex-end;
195
+ margin-top: $CAP_SPACE_04;
196
+ color: $FONT_COLOR_02;
197
+ font-size: 0.75rem;
198
+ line-height: 1rem;
199
+ }
200
+
201
+ .sms-trai-sender-id-chip {
202
+ display: inline-flex;
203
+ align-items: center;
204
+ gap: $CAP_SPACE_04;
205
+ padding: $CAP_SPACE_02 $CAP_SPACE_08;
206
+ border: 1px solid $CAP_G07;
207
+ border-radius: 0.25rem;
208
+ background-color: $CAP_G10;
209
+ line-height: 1.25rem;
210
+ }
211
+
212
+ .sms-trai-sender-id-chip__lock {
213
+ display: inline-flex;
214
+ align-items: center;
215
+ font-size: 0.75rem;
216
+ line-height: 1;
217
+ color: $FONT_COLOR_02;
218
+ }
219
+ .sms-trai-sender-id-chip__lock svg {
220
+ width: 0.75rem;
221
+ height: 0.75rem;
222
+ }
223
+
224
+ .sms-trai-sender-id-locked-note {
225
+ margin-left: $CAP_SPACE_08;
226
+ color: $FONT_COLOR_02;
227
+ font-style: italic;
228
+ font-size: 0.75rem;
229
+ line-height: 1rem;
230
+ font-weight: 400;
231
+ }
232
+
233
+ .sms-trai-promotional-window-banner.ant-alert,
234
+ .sms-trai-legacy-warning-banner.ant-alert {
235
+ margin: 0 $CAP_SPACE_16 $CAP_SPACE_16 0;
236
+
237
+ .ant-alert-title,
238
+ .ant-alert-title span {
239
+ font-weight: $FONT_WEIGHT_REGULAR;
240
+ white-space: normal;
241
+ }
242
+ }
@@ -82,4 +82,44 @@ export default defineMessages({
82
82
  id: `${prefix}.incompleteOrInvalidTagsError`,
83
83
  defaultMessage: 'Incomplete or invalid tags. Please provide values for all variables (e.g. {{optout}}, {{fullname}}).',
84
84
  },
85
+ slotCharCounter: {
86
+ id: `${prefix}.slotCharCounter`,
87
+ defaultMessage: '{current}/{max}',
88
+ },
89
+ senderIdLockedNote: {
90
+ id: `${prefix}.senderIdLockedNote`,
91
+ defaultMessage: "— locked to this template, can't be overridden",
92
+ },
93
+ promotionalWindowBanner: {
94
+ id: `${prefix}.promotionalWindowBanner`,
95
+ defaultMessage: 'Promotional template — can only be delivered between 10:00 AM and 9:00 PM IST.',
96
+ },
97
+ smsLegacyFormatWarning: {
98
+ id: `${prefix}.smsLegacyFormatWarning`,
99
+ defaultMessage: "Uses old {legacyToken} placeholders — can't be used until re-registered with typed variables on the DLT portal.",
100
+ },
101
+ dltVarPlaceholderNumeric: {
102
+ id: `${prefix}.dltVarPlaceholderNumeric`,
103
+ defaultMessage: 'Digits 0–9 only. No letters, spaces, symbols or links.',
104
+ },
105
+ dltVarPlaceholderAlphanumeric: {
106
+ id: `${prefix}.dltVarPlaceholderAlphanumeric`,
107
+ defaultMessage: 'Letters & digits, common punctuation and spaces. No links, numbers as CTA, or email.',
108
+ },
109
+ dltVarPlaceholderUrl: {
110
+ id: `${prefix}.dltVarPlaceholderUrl`,
111
+ defaultMessage: 'A valid web link that matches a Call-To-Action (CTA) registered for this template.',
112
+ },
113
+ dltVarPlaceholderUrlott: {
114
+ id: `${prefix}.dltVarPlaceholderUrlott`,
115
+ defaultMessage: 'A valid app / OTT deep link that matches a registered CTA for this template.',
116
+ },
117
+ dltVarPlaceholderCbn: {
118
+ id: `${prefix}.dltVarPlaceholderCbn`,
119
+ defaultMessage: 'A valid phone number — digits with an optional +/country code. No free text.',
120
+ },
121
+ dltVarPlaceholderEmail: {
122
+ id: `${prefix}.dltVarPlaceholderEmail`,
123
+ defaultMessage: 'A valid email address in the form name@domain.tld.',
124
+ },
85
125
  });