@capillarytech/creatives-library 8.0.319 → 8.0.321

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 (139) hide show
  1. package/constants/unified.js +14 -0
  2. package/package.json +1 -1
  3. package/utils/templateVarUtils.js +172 -0
  4. package/utils/tests/tagValidations.test.js +34 -0
  5. package/utils/tests/templateVarUtils.test.js +160 -0
  6. package/v2Components/CapTagList/index.js +25 -22
  7. package/v2Components/CapTagList/style.scss +48 -0
  8. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  9. package/v2Components/CapTagListWithInput/index.js +4 -0
  10. package/v2Components/CapWhatsappCTA/index.js +2 -0
  11. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  18. package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
  19. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  21. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
  22. package/v2Components/CommonTestAndPreview/constants.js +38 -0
  23. package/v2Components/CommonTestAndPreview/index.js +693 -155
  24. package/v2Components/CommonTestAndPreview/messages.js +41 -3
  25. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  26. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  27. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
  29. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  30. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  31. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
  32. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  33. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
  34. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  35. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  36. package/v2Components/FormBuilder/index.js +14 -1
  37. package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
  38. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  39. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
  40. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
  41. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  42. package/v2Components/SmsFallback/constants.js +73 -0
  43. package/v2Components/SmsFallback/index.js +956 -0
  44. package/v2Components/SmsFallback/index.scss +265 -0
  45. package/v2Components/SmsFallback/messages.js +78 -0
  46. package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
  47. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  48. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  49. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  50. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  51. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
  52. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  53. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  54. package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
  55. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  56. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  57. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  58. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  59. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  60. package/v2Containers/BeeEditor/index.js +3 -0
  61. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  62. package/v2Containers/CreativesContainer/SlideBoxContent.js +64 -5
  63. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  64. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  65. package/v2Containers/CreativesContainer/constants.js +9 -0
  66. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  67. package/v2Containers/CreativesContainer/index.js +292 -99
  68. package/v2Containers/CreativesContainer/index.scss +51 -1
  69. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  70. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
  71. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
  72. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  73. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
  74. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
  75. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  76. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  77. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  78. package/v2Containers/Email/index.js +1 -0
  79. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +7 -1
  80. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  81. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
  82. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  83. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -0
  84. package/v2Containers/EmailWrapper/index.js +4 -0
  85. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  86. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
  87. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
  88. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
  89. package/v2Containers/InAppWrapper/index.js +3 -0
  90. package/v2Containers/MobilePush/Create/index.js +2 -0
  91. package/v2Containers/MobilePush/Edit/index.js +2 -0
  92. package/v2Containers/MobilepushWrapper/index.js +3 -1
  93. package/v2Containers/Rcs/constants.js +32 -1
  94. package/v2Containers/Rcs/index.js +951 -873
  95. package/v2Containers/Rcs/index.scss +85 -6
  96. package/v2Containers/Rcs/messages.js +10 -1
  97. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
  98. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
  99. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  100. package/v2Containers/Rcs/tests/index.test.js +41 -38
  101. package/v2Containers/Rcs/tests/mockData.js +38 -0
  102. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
  103. package/v2Containers/Rcs/tests/utils.test.js +379 -1
  104. package/v2Containers/Rcs/utils.js +358 -10
  105. package/v2Containers/Sms/Create/index.js +83 -36
  106. package/v2Containers/Sms/Edit/index.js +2 -0
  107. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  108. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  109. package/v2Containers/SmsTrai/Create/index.js +9 -4
  110. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  111. package/v2Containers/SmsTrai/Edit/index.js +611 -128
  112. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  113. package/v2Containers/SmsTrai/Edit/messages.js +9 -4
  114. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
  115. package/v2Containers/SmsWrapper/index.js +39 -8
  116. package/v2Containers/TagList/index.js +47 -2
  117. package/v2Containers/TagList/messages.js +4 -0
  118. package/v2Containers/TagList/tests/TagList.test.js +122 -20
  119. package/v2Containers/TagList/tests/mockdata.js +17 -0
  120. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  121. package/v2Containers/Templates/_templates.scss +61 -2
  122. package/v2Containers/Templates/actions.js +11 -0
  123. package/v2Containers/Templates/constants.js +2 -0
  124. package/v2Containers/Templates/index.js +90 -40
  125. package/v2Containers/Templates/sagas.js +57 -12
  126. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  127. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  128. package/v2Containers/Templates/tests/sagas.test.js +193 -12
  129. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  130. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  131. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  132. package/v2Containers/TemplatesV2/index.js +86 -23
  133. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  134. package/v2Containers/Viber/index.js +5 -0
  135. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
  136. package/v2Containers/WebPush/Create/index.js +9 -1
  137. package/v2Containers/Whatsapp/index.js +8 -20
  138. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +598 -34
  139. package/v2Containers/Zalo/index.js +2 -0
@@ -1,10 +1,11 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  /* eslint-disable no-undef */
3
- import React, { useState, useEffect } from 'react';
3
+ import React, { useState, useEffect, useMemo, useRef } from 'react';
4
+ import PropTypes from 'prop-types';
4
5
  import { createStructuredSelector } from 'reselect';
5
6
  import { bindActionCreators } from 'redux';
6
7
  import { FormattedMessage } from 'react-intl';
7
- import { get, cloneDeep, isEmpty, isObject } from 'lodash';
8
+ import { get, cloneDeep, isEmpty } from 'lodash';
8
9
  import styled from 'styled-components';
9
10
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
10
11
  import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
@@ -27,13 +28,13 @@ import {
27
28
  CAP_SPACE_32,
28
29
  CAP_SPACE_04,
29
30
  CAP_WHITE,
30
- CAP_G10,
31
31
  } from '@capillarytech/cap-ui-library/styled/variables';
32
32
  import { makeSelectTemplateDetailsResponse } from '../../Sms/Edit/selectors';
33
33
  import { makeSelectMetaEntities, selectLiquidStateDetails, setInjectedTags } from '../../Cap/selectors';
34
34
  import * as smsEditActions from '../../Sms/Edit/actions';
35
35
  import * as globalActions from '../../Cap/actions';
36
36
  import messages from './messages';
37
+ import globalMessages from '../../Cap/messages';
37
38
  import TagList from '../../TagList';
38
39
  import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
39
40
  import UnifiedPreview from '../../../v2Components/CommonTestAndPreview/UnifiedPreview';
@@ -41,6 +42,8 @@ import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox
41
42
  import withCreatives from '../../../hoc/withCreatives';
42
43
  import {
43
44
  CHARLIMIT,
45
+ SMS,
46
+ SMS_TRAI_CONTENT_MAX_LENGTH,
44
47
  SMS_TRAI_VAR,
45
48
  TAG,
46
49
  EMBEDDED,
@@ -49,24 +52,39 @@ import {
49
52
  ALL,
50
53
  LIBRARY,
51
54
  } from './constants';
52
- import { SMS } from '../../CreativesContainer/constants';
53
55
  import v2EditSmsReducer from '../../Sms/Edit/reducer';
54
56
  import { v2SmsEditSagas } from '../../Sms/Edit/sagas';
55
57
  import ErrorInfoNote from '../../../v2Components/ErrorInfoNote';
58
+ import { isTraiDLTEnable, hasTraiDltFeature } from '../../../utils/common';
56
59
  import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
57
60
  import { validateTags } from '../../../utils/tagValidations';
58
- import globalMessages from '../../Cap/messages';
59
61
  import { ANDROID } from '../../../v2Components/CommonTestAndPreview/constants';
62
+ import {
63
+ getFallbackResolvedContent,
64
+ splitTemplateVarString,
65
+ COMBINED_SMS_TEMPLATE_VAR_REGEX,
66
+ isAnyTemplateVarToken,
67
+ isDltHashVarToken,
68
+ } from '../../../utils/templateVarUtils';
69
+ import VarSegmentMessageEditor from '../../../v2Components/VarSegmentMessageEditor';
70
+ import rcsMessages from '../../Rcs/messages';
71
+
72
+ import './index.scss';
60
73
 
61
- let varMap = {};
62
- let traiData = {};
63
74
  const { TextArea } = CapInput;
64
75
  const { CapLabelInline } = CapLabel;
65
76
 
77
+ /** Redux `metaEntities` may be an Immutable.Map; TagList needs plain `tags.standard`. */
78
+ const getStandardTagsFromMeta = (metaEntities) => {
79
+ if (!metaEntities) return [];
80
+ const plain = typeof metaEntities.toJS === 'function' ? metaEntities.toJS() : metaEntities;
81
+ const standard = get(plain, 'tags.standard');
82
+ return Array.isArray(standard) ? standard : [];
83
+ };
84
+
66
85
  export const SmsTraiEdit = (props) => {
67
86
  const {
68
87
  intl,
69
- handleClose,
70
88
  params,
71
89
  actions,
72
90
  templateDetails,
@@ -82,9 +100,21 @@ export const SmsTraiEdit = (props) => {
82
100
  templateData = {},
83
101
  selectedOfferDetails,
84
102
  eventContextTags,
103
+ waitEventContextTags,
85
104
  fetchingLiquidTags,
86
105
  getLiquidTags,
87
106
  showLiquidErrorInFooter,
107
+ smsRegister,
108
+ // RCS -> SMS fallback edit mode
109
+ isRcsSmsFallback = false,
110
+ /** When editing an existing RCS template, lock Unicode (matches product: no mid-edit toggle). */
111
+ isRcsEditFlow = false,
112
+ showPreviewInRcsFallback = false,
113
+ hidePreview = false,
114
+ isOverview = false,
115
+ forceFullTagContext = false,
116
+ /** RCS parent: merge `rcsSmsFallbackVarMapped` into `smsFallbackData` (same idea as `cardVarMapped`). */
117
+ onRcsFallbackEditorStateChange,
88
118
  } = props || {};
89
119
 
90
120
  const { formatMessage } = intl;
@@ -94,14 +124,149 @@ export const SmsTraiEdit = (props) => {
94
124
  const [tags, updateTags] = useState([]);
95
125
  const [textAreaId, updateTextAreaId] = useState();
96
126
  const [isValidationError, updateIsValidationError] = useState(false);
127
+ const [isTagValidationError, updateIsTagValidationError] = useState(false);
97
128
  const [totalMessageLength, setTotalMessageLength] = useState(0);
98
129
  const [isUnicodeAllowed, updateIsUnicodeAllowed] = useState(true);
130
+ const [fallbackText, setFallbackText] = useState('');
131
+ const [fallbackVarMappedData, setFallbackVarMappedData] = useState({});
132
+ const [fallbackFocusedId, setFallbackFocusedId] = useState('');
99
133
  const [showMsgLengthNote, updateshowMsgLengthNote] = useState(false);
100
134
  const [liquidErrorMessages, setLiquidErrorMessages] = useState({});
101
135
  const [isLiquidValidationError, setIsLiquidValidationError] = useState(false);
102
136
  /** After user closes the validation panel, keep it hidden until Save is clicked again (even if ErrorInfoNote remounts). */
103
137
  const [liquidErrorPanelDismissed, setLiquidErrorPanelDismissed] = useState(false);
104
138
  const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
139
+
140
+ /** Per-instance only — was module-level (leaked across mounts / multiple editors). */
141
+ const varMapRef = useRef({});
142
+ const traiDataRef = useRef({});
143
+ const tagValidationResponseRef = useRef({});
144
+ /** De-dupe repeated /meta/TAG fetches from popover/context callbacks. */
145
+ const lastTagSchemaQueryKeyRef = useRef(null);
146
+ /** Latest template props for RCS fallback init — avoids effect deps on unstable `templateData` references. */
147
+ const rcsFallbackTemplateSourceRef = useRef({ isFullMode, templateDetails, templateData });
148
+ rcsFallbackTemplateSourceRef.current = { isFullMode, templateDetails, templateData };
149
+
150
+ const fetchTagSchemaIfNewQuery = (query) => {
151
+ const key = JSON.stringify(query);
152
+ if (lastTagSchemaQueryKeyRef.current === key) return;
153
+ lastTagSchemaQueryKeyRef.current = key;
154
+ globalActions.fetchSchemaForEntity(query);
155
+ };
156
+
157
+ const traiDltEnabled = useMemo(
158
+ () => isTraiDLTEnable(isFullMode, smsRegister),
159
+ [isFullMode, smsRegister],
160
+ );
161
+ /**
162
+ * VarSegment for RCS SMS fallback when:
163
+ * - `isTraiDLTEnable` passes (library + `smsRegister === 'DLT'`, or full-mode library), or
164
+ * - org has TRAI DLT (`hasTraiDltFeature`) — needed when slidebox used to force `isFullMode={false}`
165
+ * or `smsRegister` is missing so `isTraiDLTEnable` alone is false, or
166
+ * - RCS template inline edit (`isRcsEditFlow`).
167
+ */
168
+ const useRcsFallbackVarSegment = useMemo(() => {
169
+ if (!isRcsSmsFallback) return false;
170
+ return traiDltEnabled || hasTraiDltFeature() || isRcsEditFlow;
171
+ }, [isRcsSmsFallback, traiDltEnabled, isRcsEditFlow]);
172
+
173
+ /**
174
+ * RCS SMS fallback: always show character count vs TRAI max (`SMS_TRAI_CONTENT_MAX_LENGTH`).
175
+ * Do not use `totalCharacters` ({smsCount} SMS via length/160) here: resolved template + variable
176
+ * values can exceed one GSM segment while DLT still shows a single registered template — length/160
177
+ * is only a rough segment hint and reads as “wrong SMS count” in campaigns.
178
+ */
179
+ const renderDescriptionCharacterCount = (className = "rcs-character-count") => (
180
+ <CapLabel type="label1" className={className}>
181
+ {formatMessage(messages.charactersCountLabel, {
182
+ current: totalMessageLength,
183
+ max: SMS_TRAI_CONTENT_MAX_LENGTH,
184
+ })}
185
+ </CapLabel>
186
+ );
187
+
188
+ const dltConsecutiveRunLength = (segments, index) => {
189
+ if (!Array.isArray(segments) || typeof segments[index] !== 'string' || !isDltHashVarToken(segments[index])) {
190
+ return 0;
191
+ }
192
+ let lo = index;
193
+ while (lo > 0 && isDltHashVarToken(segments[lo - 1])) lo -= 1;
194
+ let hi = index;
195
+ while (hi < segments.length - 1 && isDltHashVarToken(segments[hi + 1])) hi += 1;
196
+ return hi - lo + 1;
197
+ };
198
+
199
+ const renderRcsFallbackMessage = (str = '') => {
200
+ if (!useRcsFallbackVarSegment) {
201
+ return (
202
+ <CapRow className="rcs-create-template-message-input">
203
+ <div className="rcs_text_area_wrapper">
204
+ <TextArea
205
+ id="rcs_fallback_message_textarea"
206
+ autosize={{ minRows: 4, maxRows: 12 }}
207
+ value={fallbackText}
208
+ onChange={(e) => setFallbackText(e.target.value)}
209
+ placeholder={formatMessage(rcsMessages.fallbackMsgPlaceholder)}
210
+ data-testid="rcs_fallback_plain_text_area"
211
+ />
212
+ {renderDescriptionCharacterCount()}
213
+ </div>
214
+ </CapRow>
215
+ );
216
+ }
217
+ return (
218
+ <CapRow className="rcs-create-template-message-input">
219
+ <div className="rcs_text_area_wrapper">
220
+ <VarSegmentMessageEditor
221
+ templateString={str}
222
+ valueMap={fallbackVarMappedData || {}}
223
+ onChange={(varSegmentFieldId, nextSlotValue) => {
224
+ setFallbackVarMappedData((previousSlotMap) => ({
225
+ ...(previousSlotMap || {}),
226
+ [varSegmentFieldId]: nextSlotValue,
227
+ }));
228
+ }}
229
+ onFocus={setFallbackFocusedId}
230
+ varRegex={COMBINED_SMS_TEMPLATE_VAR_REGEX}
231
+ placeholderPrefix=""
232
+ getPlaceholder={() => formatMessage(rcsMessages.fallbackMsgPlaceholder)}
233
+ renderVarFooter={(dltSegmentToken, dltSegmentIndex) => {
234
+ if (!isDltHashVarToken(dltSegmentToken)) return null;
235
+ const segments = splitTemplateVarString(str, COMBINED_SMS_TEMPLATE_VAR_REGEX);
236
+ const varCounts = dltConsecutiveRunLength(segments, dltSegmentIndex);
237
+ if (varCounts < 1) return null;
238
+ /* Inline layout/color so hint is visible even if CapSpin / load order blocks SCSS */
239
+ const dltVarSlotHintStyle = {
240
+ display: 'block',
241
+ marginTop: CAP_SPACE_04,
242
+ marginLeft: 'auto',
243
+ marginRight: 0,
244
+ width: 'fit-content',
245
+ maxWidth: '100%',
246
+ textAlign: 'right',
247
+ color: 'rgba(0, 0, 0, 0.45)',
248
+ fontSize: 12,
249
+ fontWeight: 400,
250
+ lineHeight: '16px',
251
+ };
252
+ return (
253
+ <span className="sms-trai-rcs-fallback-var-hint" style={dltVarSlotHintStyle}>
254
+ {formatMessage(messages.textAreaCounts, {
255
+ varCounts,
256
+ var: SMS_TRAI_VAR,
257
+ charCounts: varCounts * CHARLIMIT,
258
+ })}
259
+ </span>
260
+ );
261
+ }}
262
+ />
263
+ {(!isFullMode)
264
+ ? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
265
+ : (isFullMode && renderDescriptionCharacterCount())}
266
+ </div>
267
+ </CapRow>
268
+ );
269
+ };
105
270
  const SMSTraiFooter = styled.div`
106
271
  background-color: ${CAP_WHITE};
107
272
  padding: ${CAP_SPACE_32} ${CAP_SPACE_24};
@@ -117,7 +282,6 @@ export const SmsTraiEdit = (props) => {
117
282
  .ant-btn {
118
283
  margin-right: ${CAP_SPACE_16};
119
284
  }
120
- }
121
285
  `;
122
286
  const TraiEditTemplateDetails = styled.div`
123
287
  margin-bottom: ${CAP_SPACE_16};
@@ -126,41 +290,127 @@ export const SmsTraiEdit = (props) => {
126
290
  }
127
291
  `;
128
292
 
293
+ /**
294
+ * RCS embedded SMS fallback receives a new `templateData` object from `mapFallbackValueToEditTemplateData`
295
+ * on every parent render. Depending on `[templateDetails || templateData]` re-ran init and wiped
296
+ * VarSegmentMessageEditor state whenever Test & Preview (or any) re-rendered Rcs. Key only changes
297
+ * when template id / sms-editor / header actually change.
298
+ *
299
+ * Do NOT include `unicode-validity` in the key: parent merges `onRcsFallbackEditorStateChange`
300
+ * unicode patches into `templateData`, which would change the key every toggle and re-run init —
301
+ * resetting local checkbox state (visible as the unicode control flipping).
302
+ *
303
+ * Do NOT include `rcs-sms-fallback-var-mapped` in the key: the parent merges slot edits into
304
+ * `templateData` on every change, so the key would change each keystroke and this effect would
305
+ * re-run and reset slots — visible as toggling between old and new values.
306
+ */
307
+ const rcsFallbackTemplateInitKey = useMemo(() => {
308
+ if (!isRcsSmsFallback) return null;
309
+ // RCS fallback slidebox always passes `templateData` from `mapFallbackValueToEditTemplateData`.
310
+ // `templateDetails` stays empty there (no route id fetch) — do not branch on `isFullMode` or init
311
+ // key is null, the effect never runs, and `loading` stays true forever.
312
+ const activeSmsTemplateSource = templateData;
313
+ if (!activeSmsTemplateSource || isEmpty(activeSmsTemplateSource)) return null;
314
+ const templateBase = get(activeSmsTemplateSource, 'versions.base', {}) || {};
315
+ const smsEditorTemplateString = templateBase?.['sms-editor'] ?? '';
316
+ const registeredSenderHeaderList = templateBase?.header;
317
+ const headerListFingerprint = Array.isArray(registeredSenderHeaderList)
318
+ ? registeredSenderHeaderList.join('\u001f')
319
+ : '';
320
+ const templateRecordId = activeSmsTemplateSource?._id ?? '';
321
+ return [
322
+ templateRecordId,
323
+ smsEditorTemplateString,
324
+ headerListFingerprint,
325
+ ].join('\u0000');
326
+ }, [isRcsSmsFallback, templateData]);
327
+
129
328
  useEffect(() => {
130
- //fetching tags
131
329
  const { type, module } = location.query || {};
132
330
  const isEmbedded = type === EMBEDDED;
133
331
  const query = {
134
332
  layout: SMS,
135
333
  type: TAG,
136
334
  context: isEmbedded ? module : DEFAULT,
137
- embedded: isEmbedded ? type : FULL,
335
+ embedded: forceFullTagContext ? FULL : (isEmbedded ? type : FULL),
138
336
  };
139
337
  if (getDefaultTags) {
140
338
  query.context = getDefaultTags;
141
339
  }
142
- globalActions.fetchSchemaForEntity(query);
143
- //fetching template data in fullmode
340
+ fetchTagSchemaIfNewQuery(query);
144
341
  const { id } = params || {};
145
342
  if (id) {
146
343
  actions.getTemplateDetails(id);
147
344
  }
148
- //cleanup code
149
345
  return () => {
150
346
  actions.resetEditTemplate();
151
- varMap = {};
347
+ varMapRef.current = {};
152
348
  };
153
349
  }, []);
154
350
 
155
- //computing placeholder array for mapping values and rendering dynamic form
156
351
  useEffect(() => {
157
- traiData = isFullMode ? templateDetails : templateData;
158
- if (traiData && !isEmpty(traiData)) {
159
- let msg = get(traiData, `versions.base.sms-editor`, '');
352
+ if (!isRcsSmsFallback || rcsFallbackTemplateInitKey == null) return;
353
+ const {
354
+ isFullMode: isFullModeFromRef,
355
+ templateDetails: templateDetailsFromRef,
356
+ templateData: templateDataFromRef,
357
+ } = rcsFallbackTemplateSourceRef.current;
358
+ const activeTemplateSourceForInit = isRcsSmsFallback
359
+ ? templateDataFromRef
360
+ : (isFullModeFromRef ? templateDetailsFromRef : templateDataFromRef);
361
+ if (!activeTemplateSourceForInit || isEmpty(activeTemplateSourceForInit)) return;
362
+ traiDataRef.current = activeTemplateSourceForInit;
363
+ const templateBase = get(activeTemplateSourceForInit, 'versions.base', {});
364
+ const unicodeValidity = get(templateBase, 'unicode-validity', true);
365
+ const templateMsg = get(activeTemplateSourceForInit, 'versions.base.sms-editor', '') || '';
366
+ if (!useRcsFallbackVarSegment) {
367
+ setFallbackText(templateMsg);
368
+ setFallbackVarMappedData({});
369
+ setUpdatedSmsEditor(String(templateMsg).split(''));
370
+ setTotalMessageLength(String(templateMsg).length);
371
+ updateIsUnicodeAllowed(unicodeValidity);
372
+ updateLoading(false);
373
+ return;
374
+ }
375
+ const savedVarMap = get(templateBase, 'rcs-sms-fallback-var-mapped', {}) || {};
376
+ const initialVarMap = {};
377
+ const templateSegments = splitTemplateVarString(templateMsg, COMBINED_SMS_TEMPLATE_VAR_REGEX);
378
+ let varOrdinal = 0;
379
+ templateSegments.forEach((segmentToken, segmentIndexInTemplate) => {
380
+ const isVar =
381
+ typeof segmentToken === 'string' && isAnyTemplateVarToken(segmentToken);
382
+ if (!isVar) return;
383
+ varOrdinal += 1;
384
+ const varSegmentSlotId = `${segmentToken}_${segmentIndexInTemplate}`;
385
+ const persistedSlotValue =
386
+ savedVarMap?.[varSegmentSlotId]
387
+ ?? savedVarMap?.[`${segmentToken}_${varOrdinal}`];
388
+ // Persisted '' means the user cleared the slot — must not fall back to `segmentToken` for mustache
389
+ // (that would resurrect {{…}} in the input and look like the tag "came back").
390
+ if (typeof persistedSlotValue === 'string') {
391
+ initialVarMap[varSegmentSlotId] = persistedSlotValue;
392
+ } else if (isDltHashVarToken(segmentToken)) {
393
+ initialVarMap[varSegmentSlotId] = '';
394
+ } else {
395
+ initialVarMap[varSegmentSlotId] = segmentToken;
396
+ }
397
+ });
398
+ setFallbackText(templateMsg);
399
+ setFallbackVarMappedData(initialVarMap);
400
+ const initialResolvedFallbackDisplay = getFallbackResolvedContent(templateMsg, initialVarMap);
401
+ setUpdatedSmsEditor(initialResolvedFallbackDisplay.split(''));
402
+ setTotalMessageLength(initialResolvedFallbackDisplay.length);
403
+ updateIsUnicodeAllowed(unicodeValidity);
404
+ updateLoading(false);
405
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- init only when semantic key changes; template props are read from ref
406
+ }, [isRcsSmsFallback, rcsFallbackTemplateInitKey, useRcsFallbackVarSegment]);
407
+
408
+ useEffect(() => {
409
+ if (isRcsSmsFallback) return;
410
+ traiDataRef.current = isFullMode ? templateDetails : templateData;
411
+ if (traiDataRef.current && !isEmpty(traiDataRef.current)) {
412
+ let msg = get(traiDataRef.current, `versions.base.sms-editor`, '');
160
413
  const templateMessageArray = [];
161
- //converting sms-editor string to an array split at '{#var#}'
162
- //split and push string before '{#var#}[0 to index]', push '{#var#}',
163
- //split and repeat for remaining string[index+7 to length]
164
414
  while (msg.length !== 0) {
165
415
  const index = msg.search(SMS_TRAI_VAR);
166
416
  if (index !== -1) {
@@ -174,30 +424,59 @@ export const SmsTraiEdit = (props) => {
174
424
  }
175
425
  const filteredTemplateMessageArray = templateMessageArray.filter((i) => i === 0 || i);
176
426
  updateTempMsgArray(filteredTemplateMessageArray);
177
- //stop spinner
178
427
  updateLoading(false);
179
428
  }
180
- }, [templateDetails || templateData]);
429
+ }, [templateDetails || templateData, isRcsSmsFallback, isFullMode]);
181
430
 
182
- //compute/get varMapped and updated-sms-editor
183
431
  useEffect(() => {
432
+ if (!isRcsSmsFallback) return;
433
+ if (!useRcsFallbackVarSegment) {
434
+ const plainFallbackSmsText = fallbackText || '';
435
+ setUpdatedSmsEditor(plainFallbackSmsText.split(''));
436
+ setTotalMessageLength(plainFallbackSmsText.length);
437
+ return;
438
+ }
439
+ const resolvedFallbackDisplay = getFallbackResolvedContent(
440
+ fallbackText || '',
441
+ fallbackVarMappedData || {},
442
+ );
443
+ setUpdatedSmsEditor(resolvedFallbackDisplay.split(''));
444
+ setTotalMessageLength(resolvedFallbackDisplay.length);
445
+ }, [isRcsSmsFallback, useRcsFallbackVarSegment, fallbackText, fallbackVarMappedData]);
446
+
447
+ useEffect(() => {
448
+ if (!isRcsSmsFallback) return;
449
+ if (typeof onRcsFallbackEditorStateChange !== 'function') return;
450
+ onRcsFallbackEditorStateChange({
451
+ rcsSmsFallbackVarMapped: fallbackVarMappedData || {},
452
+ });
453
+ }, [isRcsSmsFallback, fallbackVarMappedData, onRcsFallbackEditorStateChange]);
454
+
455
+ useEffect(() => {
456
+ if (!isRcsSmsFallback) return;
457
+ if (typeof onRcsFallbackEditorStateChange !== 'function') return;
458
+ onRcsFallbackEditorStateChange({
459
+ unicodeValidity: isUnicodeAllowed,
460
+ });
461
+ }, [isRcsSmsFallback, isUnicodeAllowed, onRcsFallbackEditorStateChange]);
462
+
463
+ useEffect(() => {
464
+ if (isRcsSmsFallback) return;
184
465
  if (tempMsgArray.length !== 0) {
185
- const traiBase = traiData?.versions?.base || {};
466
+ const traiBase = traiDataRef.current?.versions?.base || {};
186
467
  const {
187
468
  'var-mapped': varMapped = {},
188
469
  'updated-sms-editor': traiSmsEditor = '',
189
470
  'unicode-validity': unicodeValidity = true,
190
471
  } = traiBase;
191
- //if varMap and updated-sms-editor is already present on non first edits,use those values
192
472
  if (!isEmpty(varMapped)) {
193
- varMap = cloneDeep(varMapped);
473
+ varMapRef.current = cloneDeep(varMapped);
194
474
  if (isFullMode) {
195
475
  setUpdatedSmsEditor(traiSmsEditor);
196
476
  } else {
197
477
  computeUpdatedSmsEditor();
198
478
  }
199
479
  } else {
200
- //computing and setting varMap for first edit
201
480
  let varCount = 1;
202
481
  let horizontalSpaceCount = 0;
203
482
  for (let i = 0; i < tempMsgArray.length; i += 1) {
@@ -208,7 +487,7 @@ export const SmsTraiEdit = (props) => {
208
487
  horizontalSpaceCount += 1;
209
488
  }
210
489
  if (tempMsgArray[i] !== nextElem && nextElem?.replace(/[^\S\r\n]/gm, '') !== '') {
211
- varMap[`${tempMsgArray[i]}_${(i - varCount - horizontalSpaceCount) + 1}`] = {
490
+ varMapRef.current[`${tempMsgArray[i]}_${(i - varCount - horizontalSpaceCount) + 1}`] = {
212
491
  data: '',
213
492
  count: varCount,
214
493
  };
@@ -222,7 +501,6 @@ export const SmsTraiEdit = (props) => {
222
501
  setUpdatedSmsEditor(tempMsgArray);
223
502
  }
224
503
  updateIsUnicodeAllowed(unicodeValidity);
225
- // calcaulate message length here
226
504
  calculateTotalMessageLength();
227
505
  }
228
506
  }, [tempMsgArray]);
@@ -236,17 +514,84 @@ export const SmsTraiEdit = (props) => {
236
514
  }
237
515
  }, []);
238
516
 
517
+ useEffect(() => {
518
+ const runValidateTags = (content) =>
519
+ validateTags({
520
+ content,
521
+ tagsParam: tags,
522
+ location,
523
+ tagModule: getDefaultTags,
524
+ isFullMode,
525
+ }) || {};
526
+
527
+ if (isRcsSmsFallback) {
528
+ if (isFullMode) {
529
+ tagValidationResponseRef.current = {};
530
+ updateIsTagValidationError(false);
531
+ return;
532
+ }
533
+ // TRAI/DLT VarSegment: `validateIfTagClosed` only understands paired `{{…}}` and breaks on
534
+ // legitimate `{#…#}` / mixed TRAI shapes (extra `{`/`}` counts). Do not tie Done to it;
535
+ // slot completeness is enforced by `areAllRcsSmsFallbackVarSlotsFilled` on the RCS screen.
536
+ if (useRcsFallbackVarSegment) {
537
+ tagValidationResponseRef.current = {};
538
+ updateIsTagValidationError(false);
539
+ return;
540
+ }
541
+ const validationContent = fallbackText || '';
542
+ if (!validationContent.trim()) {
543
+ tagValidationResponseRef.current = {};
544
+ updateIsTagValidationError(false);
545
+ return;
546
+ }
547
+ tagValidationResponseRef.current = validateTags({
548
+ content: validationContent,
549
+ tagsParam: tags,
550
+ location,
551
+ tagModule: getDefaultTags,
552
+ isFullMode: true,
553
+ }) || {};
554
+ const braceErr = !!tagValidationResponseRef.current.isBraceError;
555
+ // Plain (non-VarSegment) fallback textarea: only unbalanced `{{…}}` disables Done.
556
+ updateIsTagValidationError(braceErr);
557
+ return;
558
+ }
559
+ if (
560
+ !isFullMode &&
561
+ updatedSmsEditor?.length > 0 &&
562
+ !updatedSmsEditor.includes(SMS_TRAI_VAR)
563
+ ) {
564
+ tagValidationResponseRef.current = runValidateTags(updatedSmsEditor.join(''));
565
+ const missing = (tagValidationResponseRef.current.missingTags || []).length > 0;
566
+ const braceErr = !!tagValidationResponseRef.current.isBraceError;
567
+ updateIsTagValidationError(missing || braceErr);
568
+ } else if (!isRcsSmsFallback) {
569
+ tagValidationResponseRef.current = {};
570
+ updateIsTagValidationError(false);
571
+ }
572
+ }, [
573
+ updatedSmsEditor,
574
+ tags,
575
+ isRcsSmsFallback,
576
+ isFullMode,
577
+ useRcsFallbackVarSegment,
578
+ fallbackText,
579
+ fallbackVarMappedData,
580
+ getDefaultTags,
581
+ location,
582
+ ]);
583
+
239
584
  const computeUpdatedSmsEditor = () => {
240
585
  const arr = [...tempMsgArray];
241
- const varMapKeys = Object.keys(varMap)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
242
- for (const key in varMap) {
243
- if (varMap[key].data !== '') {
586
+ const varMapKeys = Object.keys(varMapRef.current)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
587
+ for (const key in varMapRef.current) {
588
+ if (varMapRef.current[key].data !== '') {
244
589
  const _id = Number(key.slice(8)); //Eg: -> extracting index 1 from keys like {#var# } _1
245
590
  const loopIndex =
246
591
  varMapKeys[varMapKeys?.indexOf(_id) + 1] || arr.length;
247
592
  for (let i = _id; i < loopIndex; i += 1) {
248
593
  if (i === _id) {
249
- arr[i] = varMap[key].data; //data for first #var# of the textbox
594
+ arr[i] = varMapRef.current[key].data; //data for first #var# of the textbox
250
595
  } else if (arr[i] === SMS_TRAI_VAR) {
251
596
  arr[i] = ''; //'' for remaining #var# of a textbox
252
597
  }
@@ -256,8 +601,8 @@ export const SmsTraiEdit = (props) => {
256
601
  setUpdatedSmsEditor(arr);
257
602
  };
258
603
 
259
- //Saving on done start
260
604
  const onUpdateTemplateComplete = (editResponse, errorMsg) => {
605
+ updateLoading(false);
261
606
  if (editResponse?.templateId) {
262
607
  CapNotification.success({
263
608
  message: formatMessage(messages.smsEditNotification),
@@ -336,66 +681,121 @@ export const SmsTraiEdit = (props) => {
336
681
  };
337
682
 
338
683
  const onDoneCallback = () => {
339
- if (updatedSmsEditor.includes(SMS_TRAI_VAR) && !isFullMode) {
340
- //during save textbox should not be empty
684
+ // RCS fallback: allow Save when only "missing tags" ({{name}} may not be in TagList); still block unbalanced braces.
685
+ if (isTagValidationError) {
686
+ if (!isRcsSmsFallback) return;
687
+ const tagValidationSnapshot = tagValidationResponseRef.current ?? {};
688
+ if (tagValidationSnapshot.isBraceError) return;
689
+ }
690
+ const editorJoined = Array.isArray(updatedSmsEditor)
691
+ ? updatedSmsEditor.join('')
692
+ : String(updatedSmsEditor || '');
693
+ if (!isRcsSmsFallback && editorJoined.includes(SMS_TRAI_VAR) && !isFullMode) {
341
694
  updateIsValidationError(true);
342
695
  } else {
343
- //start spinner
344
- updateLoading(true);
345
- const traiVersions = traiData.versions || {};
346
- traiVersions.base = {
347
- ...traiVersions.base,
348
- 'var-mapped': varMap,
349
- 'updated-sms-editor': updatedSmsEditor,
350
- 'unicode-validity': isUnicodeAllowed,
351
- };
696
+ // Only show full-screen spinner when waiting on full-mode SMS edit API — not for RCS SMS fallback
697
+ // handoff (getFormSubscriptionData → SmsFallback closeSlidebox) and not for embedded sync handoff.
698
+ if (isFullMode && !isRcsSmsFallback) {
699
+ updateLoading(true);
700
+ }
701
+ if (!traiDataRef.current.versions) {
702
+ traiDataRef.current.versions = { base: {}, history: [] };
703
+ }
704
+ const traiVersions = traiDataRef.current.versions;
705
+ if (isRcsSmsFallback) {
706
+ const {
707
+ 'var-mapped': _ignoredVarMapped,
708
+ 'updated-sms-editor': _ignoredUpdatedSmsEditor,
709
+ 'rcs-sms-fallback-var-mapped': _ignoredFallbackVarMapped,
710
+ ...baseWithoutDerivedFields
711
+ } = traiVersions.base || {};
712
+ traiVersions.base = {
713
+ ...baseWithoutDerivedFields,
714
+ 'sms-editor': fallbackText || '',
715
+ 'unicode-validity': isUnicodeAllowed,
716
+ ...(useRcsFallbackVarSegment && {
717
+ 'rcs-sms-fallback-var-mapped': fallbackVarMappedData || {},
718
+ }),
719
+ };
720
+ } else {
721
+ traiVersions.base = {
722
+ ...traiVersions.base,
723
+ 'var-mapped': varMapRef.current,
724
+ 'updated-sms-editor': updatedSmsEditor,
725
+ 'unicode-validity': isUnicodeAllowed,
726
+ };
727
+ }
352
728
  traiVersions.history = [traiVersions.base];
353
- if (isFullMode) {
354
- actions.editTemplate(traiData, onUpdateTemplateComplete);
729
+ // RCS → SMS fallback (slidebox / inline): always hand off via getFormSubscriptionData so parent
730
+ // can persist fallback state and close the slide — never the standalone TRAI editTemplate API here.
731
+ if (isFullMode && !isRcsSmsFallback) {
732
+ actions.editTemplate(traiDataRef.current, onUpdateTemplateComplete);
355
733
  } else {
356
734
  getFormSubscriptionData({
357
- value: traiData.versions,
735
+ value: traiVersions,
736
+ // Consumers/tests read `versions.base` first (see getBaseFromSmsTraiFormData, rcsDltEditCompletionHandler).
737
+ versions: traiVersions,
358
738
  _id: params && params.id,
359
739
  validity: true,
360
740
  type: SMS,
361
741
  });
742
+ updateLoading(false);
362
743
  }
363
744
  }
364
745
  };
365
- //Saving on done end
366
746
 
367
- // tag code start
747
+ const locationQueryType = location?.query?.type;
748
+ const locationQueryModule = location?.query?.module;
749
+
368
750
  useEffect(() => {
369
- let tag =
370
- metaEntities && metaEntities.tags ? metaEntities.tags.standard : [];
371
- const { type, module } = location.query || {};
372
- if (type === EMBEDDED && module === LIBRARY && !getDefaultTags) {
751
+ let tag = getStandardTagsFromMeta(metaEntities);
752
+ if ((!Array.isArray(tag) || tag.length === 0) && Array.isArray(supportedTags) && supportedTags.length > 0) {
373
753
  tag = supportedTags;
374
754
  }
375
- updateTags(tag);
376
- }, [metaEntities]);
755
+ if (
756
+ locationQueryType === EMBEDDED &&
757
+ locationQueryModule === LIBRARY &&
758
+ !getDefaultTags
759
+ ) {
760
+ tag = supportedTags || [];
761
+ }
762
+ updateTags(Array.isArray(tag) ? tag : []);
763
+ }, [metaEntities, getDefaultTags, supportedTags, locationQueryType, locationQueryModule]);
377
764
 
378
765
  const handleOnTagsContextChange = (data) => {
766
+ // CapTagList passes "Outbound" | "Loyalty" from the module filter; TagList may pass "Outbound" on init.
767
+ if (data == null || data === '') return;
768
+ const normalizedContext = String(data).toLowerCase();
379
769
  const { type } = location.query || {};
380
770
  const isEmbedded = type === EMBEDDED;
381
771
  const query = {
382
772
  layout: SMS,
383
773
  type: TAG,
384
- context:
385
- (data || '').toLowerCase() === ALL
386
- ? DEFAULT
387
- : (data || '').toLowerCase(),
388
- embedded: isEmbedded ? type : FULL,
774
+ context: normalizedContext === ALL ? DEFAULT : normalizedContext,
775
+ embedded: forceFullTagContext ? FULL : (isEmbedded ? type : FULL),
389
776
  };
390
- globalActions.fetchSchemaForEntity(query);
777
+ fetchTagSchemaIfNewQuery(query);
391
778
  };
392
779
 
393
780
  const onTagSelect = (data) => {
394
- if (textAreaId >= 0 && varMap && updatedSmsEditor) {
781
+ if (isRcsSmsFallback) {
782
+ if (!useRcsFallbackVarSegment) {
783
+ setFallbackText((prev) => `${prev || ''}{{${data}}}`);
784
+ return;
785
+ }
786
+ if (!fallbackFocusedId) return;
787
+ const prevVal = fallbackVarMappedData?.[fallbackFocusedId] ?? '';
788
+ const nextVal = `${prevVal}{{${data}}}`;
789
+ setFallbackVarMappedData((prev) => ({
790
+ ...(prev || {}),
791
+ [fallbackFocusedId]: nextVal,
792
+ }));
793
+ return;
794
+ }
795
+ if (textAreaId >= 0 && varMapRef.current && updatedSmsEditor) {
395
796
  const arr = [...updatedSmsEditor];
396
- const varMapKeys = Object.keys(varMap)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
797
+ const varMapKeys = Object.keys(varMapRef.current)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
397
798
  const loopIndex = varMapKeys[varMapKeys?.indexOf(textAreaId) + 1] || arr.length;
398
- //when trying to insert tag in empty textarea,{#var#} is replaced with "" and then tag is added
399
799
  for (let i = textAreaId; i < loopIndex; i += 1) {
400
800
  if (arr[i] === SMS_TRAI_VAR) {
401
801
  arr[i] = '';
@@ -403,26 +803,23 @@ export const SmsTraiEdit = (props) => {
403
803
  }
404
804
  const messageData = `${arr[textAreaId]}{{${data}}}`;
405
805
  arr[textAreaId] = messageData;
406
- varMap[`${SMS_TRAI_VAR}_${textAreaId}`].data = messageData;
806
+ varMapRef.current[`${SMS_TRAI_VAR}_${textAreaId}`].data = messageData;
407
807
  setUpdatedSmsEditor(arr);
408
808
  }
409
809
  };
410
- //setting the id of currently selected text area, is used onTagSelect
810
+
411
811
  const setTextAreaId = (event) => {
412
812
  updateTextAreaId(Number(event.target.id));
413
813
  };
414
- // tag code end
415
814
 
416
- // on change event of Text Area
417
815
  const textAreaValueChange = ({ target: { value, id } }) => {
816
+ if (isRcsSmsFallback) return;
418
817
  const _id = Number(id);
419
818
  const arr = [...updatedSmsEditor];
420
- const varMapKeys = Object.keys(varMap)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
819
+ const varMapKeys = Object.keys(varMapRef.current)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
421
820
  const loopIndex = varMapKeys[varMapKeys?.indexOf(_id) + 1] || arr.length;
422
821
 
423
- //assign entered value to varMap
424
- varMap[`${SMS_TRAI_VAR}_${_id}`].data = value;
425
- //based on entered value update updatedSmsEditor
822
+ varMapRef.current[`${SMS_TRAI_VAR}_${_id}`].data = value;
426
823
  if (value === '') {
427
824
  for (let i = _id; i < loopIndex; i += 1) {
428
825
  if (i === _id || arr[i] === '') {
@@ -432,7 +829,7 @@ export const SmsTraiEdit = (props) => {
432
829
  } else {
433
830
  for (let i = _id; i < loopIndex; i += 1) {
434
831
  if (i === _id) {
435
- arr[i] = varMap[`${SMS_TRAI_VAR}_${_id}`].data;
832
+ arr[i] = varMapRef.current[`${SMS_TRAI_VAR}_${_id}`].data;
436
833
  } else if (arr[i] === SMS_TRAI_VAR) {
437
834
  arr[i] = '';
438
835
  }
@@ -535,18 +932,33 @@ export const SmsTraiEdit = (props) => {
535
932
  };
536
933
 
537
934
  const smsLengthForVar = () => (
538
- <CapHeading type="h5" style={{ marginTop: CAP_SPACE_04, float: 'right' }}>
539
- {formatMessage(messages.totalCharacters, {
540
- smsCount: Math.ceil(totalMessageLength / 160),
541
- number: totalMessageLength,
542
- })}
935
+ <CapHeading
936
+ type="h5"
937
+ className={isRcsSmsFallback ? 'rcs-character-count rcs-character-count--compact' : ''}
938
+ style={isRcsSmsFallback ? {} : { marginTop: CAP_SPACE_04, marginBottom: 0 }}
939
+ >
940
+ {isRcsSmsFallback
941
+ ? formatMessage(messages.charactersCountLabel, {
942
+ current: totalMessageLength,
943
+ max: SMS_TRAI_CONTENT_MAX_LENGTH,
944
+ })
945
+ : formatMessage(messages.totalCharacters, {
946
+ smsCount: Math.ceil(totalMessageLength / 160),
947
+ number: totalMessageLength,
948
+ })}
543
949
  </CapHeading>
544
950
  );
545
951
 
546
- // to compute the length of the message
547
- //40 characters is blocked per'{#var#}' if textbox is empty otherwise it will be textbox length
548
- // and the remaining string length is added to it
549
952
  const calculateTotalMessageLength = () => {
953
+ if (isRcsSmsFallback) {
954
+ if (useRcsFallbackVarSegment) {
955
+ const resolved = getFallbackResolvedContent(fallbackText || '', fallbackVarMappedData || {});
956
+ setTotalMessageLength(resolved.length);
957
+ } else {
958
+ setTotalMessageLength((fallbackText || '').length);
959
+ }
960
+ return;
961
+ }
550
962
  const msgLenWithoutVar = tempMsgArray
551
963
  ?.filter((i) => i !== SMS_TRAI_VAR)
552
964
  .join('');
@@ -557,22 +969,43 @@ export const SmsTraiEdit = (props) => {
557
969
 
558
970
  const calculateLenForTextBox = () => {
559
971
  let countVarChar = 0;
560
- Object.keys(varMap).forEach((i) => {
561
- if (varMap[i].data) {
562
- countVarChar += varMap[i].data?.length;
563
- if (!showMsgLengthNote && varMap[i].data?.length > varMap[i].count * CHARLIMIT) {
972
+ Object.keys(varMapRef.current).forEach((i) => {
973
+ if (varMapRef.current[i].data) {
974
+ countVarChar += varMapRef.current[i].data?.length;
975
+ if (!showMsgLengthNote && varMapRef.current[i].data?.length > varMapRef.current[i].count * CHARLIMIT) {
564
976
  updateshowMsgLengthNote(true);
565
977
  }
566
978
  } else {
567
- countVarChar += varMap[i].count * CHARLIMIT;
979
+ countVarChar += varMapRef.current[i].count * CHARLIMIT;
568
980
  }
569
981
  });
570
982
  return countVarChar;
571
983
  };
572
984
 
985
+ const tagValidationErrorMessage = () => {
986
+ const { missingTags = [], unsupportedTags = [], isBraceError } = tagValidationResponseRef.current || {};
987
+ const listForMessage = (unsupportedTags && unsupportedTags.length > 0)
988
+ ? unsupportedTags
989
+ : missingTags;
990
+ if (isBraceError) {
991
+ return <CapError>{formatMessage(globalMessages.unbalanacedCurlyBraces)}</CapError>;
992
+ }
993
+ if (listForMessage.length > 0) {
994
+ return (
995
+ <CapError>
996
+ {formatMessage(messages.unsupportedTagsValidationError, {
997
+ unsupportedTags: listForMessage,
998
+ })}
999
+ </CapError>
1000
+ );
1001
+ }
1002
+ return null;
1003
+ };
1004
+
573
1005
  const disablehandler = () => {
574
- if (traiData && !isEmpty(traiData)) {
575
- const msg = get(traiData, `versions.base.sms-editor`, '');
1006
+ if (isRcsSmsFallback) return false;
1007
+ if (traiDataRef.current && !isEmpty(traiDataRef.current)) {
1008
+ const msg = get(traiDataRef.current, `versions.base.sms-editor`, '');
576
1009
  const index = msg.search(SMS_TRAI_VAR);
577
1010
  if (index === -1) {
578
1011
  return true;
@@ -586,7 +1019,6 @@ export const SmsTraiEdit = (props) => {
586
1019
  updateIsUnicodeAllowed(checked);
587
1020
  };
588
1021
 
589
- // Get template content for test and preview
590
1022
  const getTemplateContent = () => {
591
1023
  if (!updatedSmsEditor || updatedSmsEditor.length === 0) {
592
1024
  return '';
@@ -594,42 +1026,69 @@ export const SmsTraiEdit = (props) => {
594
1026
  return updatedSmsEditor.join('');
595
1027
  };
596
1028
 
597
- // Build formData for TestAndPreviewSlidebox - templateConfigs with templateId and template for DLT test
598
1029
  const getFormDataForTestAndPreview = () => {
599
- const smsBase = get(traiData, 'versions.base') || get(templateDetails, 'versions.base') || get(templateData, 'versions.base');
1030
+ const smsBase = get(traiDataRef.current, 'versions.base') || get(templateDetails, 'versions.base') || get(templateData, 'versions.base');
600
1031
  if (!smsBase || !smsBase.template_id) {
601
1032
  return {};
602
1033
  }
603
1034
  const templateRaw = smsBase['updated-sms-editor'] || smsBase['sms-editor'] || '';
604
1035
  const template = Array.isArray(templateRaw) ? templateRaw.join('') : templateRaw;
1036
+ const traiDlt = isTraiDLTEnable(isFullMode, smsRegister);
1037
+ const headerIds = get(traiDataRef.current, 'versions.base.header', []) || [];
605
1038
  return {
606
1039
  templateConfigs: {
607
- templateId: smsBase.template_id, template, traiDltEnabled: true, registeredSenderIds: get(traiData, `versions.base.header`, []),
1040
+ templateId: smsBase.template_id,
1041
+ template,
1042
+ traiDltEnabled: traiDlt,
1043
+ registeredSenderIds: traiDlt ? headerIds : [],
608
1044
  },
609
1045
  };
610
1046
  };
611
1047
 
612
- // Handle test and preview button click
613
1048
  const handleTestAndPreview = () => {
614
1049
  setShowTestAndPreviewSlidebox(true);
615
1050
  };
616
1051
 
617
- // Handle close test and preview slidebox
618
1052
  const handleCloseTestAndPreview = () => {
619
1053
  setShowTestAndPreviewSlidebox(false);
620
1054
  };
621
1055
 
1056
+ const shouldShowPreview =
1057
+ !isRcsSmsFallback || (showPreviewInRcsFallback && !hidePreview);
1058
+ const smsSidePreviewColumn = (
1059
+ <CapColumn span={8} offset={1}>
1060
+ <UnifiedPreview
1061
+ channel={SMS}
1062
+ content={updatedSmsEditor.join('')}
1063
+ device={ANDROID}
1064
+ showDeviceToggle={false}
1065
+ showHeader={false}
1066
+ formatMessage={formatMessage}
1067
+ senderId={isUnicodeAllowed ? 'Unicode' : 'ASCII'}
1068
+ />
1069
+ </CapColumn>
1070
+ );
1071
+
622
1072
  return (
623
1073
  <>
624
- <CapSpin spinning={loading || fetchingLiquidTags} tip={fetchingLiquidTags && formatMessage(formBuilderMessages.liquidSpinText)}>
1074
+ <CapSpin
1075
+ spinning={loading || fetchingLiquidTags}
1076
+ tip={fetchingLiquidTags && formatMessage(formBuilderMessages.liquidSpinText)}
1077
+ className={[
1078
+ isRcsSmsFallback && 'sms-trai-edit-rcs-fallback',
1079
+ isOverview && 'sms-trai-edit--overview',
1080
+ ]
1081
+ .filter(Boolean)
1082
+ .join(' ') || undefined}
1083
+ >
625
1084
  <CapRow>
626
- {traiData && !isEmpty(traiData) && (
1085
+ {traiDataRef.current && !isEmpty(traiDataRef.current) && !isRcsSmsFallback && (
627
1086
  <TraiEditTemplateDetails>
628
1087
  <CapLabelInline type="label1">
629
1088
  {formatMessage(messages.templateLabel)}
630
1089
  </CapLabelInline>
631
1090
  <CapLabelInline type="label2">
632
- {get(traiData, `versions.base.template_name`, '')}
1091
+ {get(traiDataRef.current, `versions.base.template_name`, '')}
633
1092
  </CapLabelInline>
634
1093
 
635
1094
  <CapLabelInline type="label1">
@@ -640,15 +1099,15 @@ export const SmsTraiEdit = (props) => {
640
1099
  {formatMessage(messages.senderIdlabel)}
641
1100
  </CapLabelInline>
642
1101
  <CapLabelInline type="label2">
643
- {[...get(traiData, `versions.base.header`, [])].join(', ')}
1102
+ {[...get(traiDataRef.current, `versions.base.header`, [])].join(', ')}
644
1103
  </CapLabelInline>
645
1104
  </TraiEditTemplateDetails>
646
1105
  )}
647
- <CapColumn span={14}>
1106
+ <CapColumn span={shouldShowPreview ? 14 : 24}>
648
1107
  <CapRow>
649
1108
  <CapHeader
650
1109
  title={formatMessage(messages.traiEditTitle)}
651
- size="regular"
1110
+ size={isRcsSmsFallback ? 'label1' : 'regular'}
652
1111
  suffix={(
653
1112
  <TagList
654
1113
  label={formatMessage(messages.addLabels)}
@@ -657,42 +1116,56 @@ export const SmsTraiEdit = (props) => {
657
1116
  tags={tags || []}
658
1117
  onContextChange={handleOnTagsContextChange}
659
1118
  injectedTags={injectedTags || {}}
660
- hidePopover={disablehandler()}
1119
+ channel={SMS}
1120
+ hidePopover={false}
1121
+ disabled={!isRcsSmsFallback && disablehandler()}
661
1122
  selectedOfferDetails={selectedOfferDetails}
662
1123
  eventContextTags={eventContextTags}
1124
+ waitEventContextTags={waitEventContextTags}
1125
+ popoverOverlayStyle={isRcsSmsFallback ? { zIndex: 10020 } : undefined}
1126
+ popoverOverlayClassName={isRcsSmsFallback ? 'sms-fallback-taglist-popover rcs-sms-fallback-taglist-popover' : undefined}
663
1127
  />
664
1128
  )}
665
1129
  />
666
1130
  </CapRow>
667
1131
 
668
- <CapRow
669
- style={{
670
- backgroundColor: CAP_G10,
671
- padding: CAP_SPACE_16,
672
- }}
1132
+ {isRcsSmsFallback ? (
1133
+ <>
1134
+ {renderRcsFallbackMessage(fallbackText)}
1135
+ </>
1136
+ ) : (
1137
+ <>
1138
+ <CapRow className="sms-trai-editor-segment-row">
1139
+ <div className="sms-trai-segmented-editor">
1140
+ {renderedContent()}
1141
+ </div>
1142
+ </CapRow>
1143
+ <CapRow className="sms-trai-length-row">
1144
+ {smsLengthForVar()}
1145
+ </CapRow>
1146
+ </>
1147
+ )}
1148
+ {isRcsSmsFallback && isTagValidationError && (
1149
+ <CapRow>
1150
+ {tagValidationErrorMessage()}
1151
+ </CapRow>
1152
+ )}
1153
+ {!isRcsSmsFallback && isTagValidationError && tagValidationErrorMessage()}
1154
+ <CapCheckbox
1155
+ onChange={unicodeHandler}
1156
+ checked={isUnicodeAllowed}
1157
+ disabled={
1158
+ isRcsSmsFallback
1159
+ ? isRcsEditFlow
1160
+ : disablehandler()
1161
+ }
673
1162
  >
674
- {renderedContent()}
675
- </CapRow>
676
- <CapRow>
677
- {smsLengthForVar()}
678
- </CapRow>
679
- <CapCheckbox onChange={unicodeHandler} checked={isUnicodeAllowed} disabled={disablehandler()}>
680
1163
  {formatMessage(messages.unicodeLabel)}
681
1164
  </CapCheckbox>
682
1165
  {showMsgLengthNote && <CapInfoNote message={<FormattedMessage {...messages.msgLengthNote} values={{ var: '{#var#}' }} />} />}
683
- <div style={{ marginBottom: '100px' }} />
684
- </CapColumn>
685
- <CapColumn span={8} offset={1}>
686
- <UnifiedPreview
687
- channel={SMS}
688
- content={updatedSmsEditor.join('')}
689
- device={ANDROID}
690
- showDeviceToggle={false}
691
- showHeader={false}
692
- formatMessage={formatMessage}
693
- senderId={isUnicodeAllowed ? 'Unicode' : 'ASCII'}
694
- />
1166
+ <div className="sms-trai-edit-bottom-spacer" />
695
1167
  </CapColumn>
1168
+ {shouldShowPreview && smsSidePreviewColumn}
696
1169
  </CapRow>
697
1170
  <SMSTraiFooter>
698
1171
  {isLiquidValidationError && !liquidErrorPanelDismissed && (
@@ -718,8 +1191,9 @@ export const SmsTraiEdit = (props) => {
718
1191
  <FormattedMessage {...messages.testAndPreviewButtonLabel} />
719
1192
  </CapButton>
720
1193
  <CapButton
721
- onClick={onSubmitWrapper}
1194
+ onClick={onDoneCallback}
722
1195
  className="create-msg"
1196
+ disabled={isTagValidationError}
723
1197
  >
724
1198
  <FormattedMessage {...messages.saveButtonLabel} />
725
1199
  </CapButton>
@@ -736,6 +1210,15 @@ export const SmsTraiEdit = (props) => {
736
1210
  );
737
1211
  };
738
1212
 
1213
+ SmsTraiEdit.propTypes = {
1214
+ isRcsSmsFallback: PropTypes.bool,
1215
+ isRcsEditFlow: PropTypes.bool,
1216
+ showPreviewInRcsFallback: PropTypes.bool,
1217
+ hidePreview: PropTypes.bool,
1218
+ isOverview: PropTypes.bool,
1219
+ onRcsFallbackEditorStateChange: PropTypes.func,
1220
+ };
1221
+
739
1222
  const mapStateToProps = createStructuredSelector({
740
1223
  templateDetails: makeSelectTemplateDetailsResponse(),
741
1224
  metaEntities: makeSelectMetaEntities(),