@capillarytech/creatives-library 8.0.329 → 8.0.330

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 (122) hide show
  1. package/constants/unified.js +0 -14
  2. package/package.json +1 -1
  3. package/services/api.js +0 -17
  4. package/services/tests/api.test.js +0 -85
  5. package/utils/commonUtils.js +0 -10
  6. package/utils/tests/commonUtil.test.js +0 -169
  7. package/v2Components/CapTagList/index.js +0 -10
  8. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  9. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  15. package/v2Components/CommonTestAndPreview/SendTestMessage.js +53 -87
  16. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -20
  17. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  18. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -145
  19. package/v2Components/CommonTestAndPreview/actions.js +0 -10
  20. package/v2Components/CommonTestAndPreview/constants.js +1 -53
  21. package/v2Components/CommonTestAndPreview/index.js +168 -1006
  22. package/v2Components/CommonTestAndPreview/messages.js +3 -147
  23. package/v2Components/CommonTestAndPreview/reducer.js +0 -10
  24. package/v2Components/CommonTestAndPreview/sagas.js +6 -15
  25. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +286 -328
  26. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  27. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  29. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +24 -65
  30. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  31. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
  32. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -168
  33. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
  34. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  35. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
  36. package/v2Components/FormBuilder/index.js +1 -7
  37. package/v2Components/TestAndPreviewSlidebox/index.js +1 -8
  38. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  39. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  40. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  41. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  42. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  43. package/v2Containers/CreativesContainer/constants.js +0 -9
  44. package/v2Containers/CreativesContainer/index.js +93 -286
  45. package/v2Containers/CreativesContainer/index.scss +1 -51
  46. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  47. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  48. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  49. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  50. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -20
  51. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  52. package/v2Containers/Rcs/constants.js +1 -34
  53. package/v2Containers/Rcs/index.js +884 -999
  54. package/v2Containers/Rcs/index.scss +6 -85
  55. package/v2Containers/Rcs/messages.js +1 -10
  56. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2453 -41456
  57. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  58. package/v2Containers/Rcs/tests/index.test.js +38 -41
  59. package/v2Containers/Rcs/tests/mockData.js +0 -38
  60. package/v2Containers/Rcs/tests/utils.test.js +1 -379
  61. package/v2Containers/Rcs/utils.js +10 -358
  62. package/v2Containers/Sms/Create/index.js +38 -100
  63. package/v2Containers/SmsTrai/Create/index.js +4 -9
  64. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  65. package/v2Containers/SmsTrai/Edit/index.js +128 -609
  66. package/v2Containers/SmsTrai/Edit/messages.js +4 -9
  67. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2600 -4586
  68. package/v2Containers/SmsWrapper/index.js +8 -37
  69. package/v2Containers/TagList/index.js +0 -6
  70. package/v2Containers/Templates/_templates.scss +2 -63
  71. package/v2Containers/Templates/actions.js +0 -11
  72. package/v2Containers/Templates/constants.js +0 -2
  73. package/v2Containers/Templates/index.js +40 -90
  74. package/v2Containers/Templates/sagas.js +12 -57
  75. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  76. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  77. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  78. package/v2Containers/TemplatesV2/index.js +23 -86
  79. package/v2Containers/Whatsapp/index.js +20 -3
  80. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5790
  81. package/utils/templateVarUtils.js +0 -172
  82. package/utils/tests/templateVarUtils.test.js +0 -160
  83. package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
  84. package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -155
  85. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -93
  86. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  87. package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
  88. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -648
  89. package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -174
  90. package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
  91. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  92. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
  93. package/v2Components/SmsFallback/constants.js +0 -73
  94. package/v2Components/SmsFallback/index.js +0 -955
  95. package/v2Components/SmsFallback/index.scss +0 -265
  96. package/v2Components/SmsFallback/messages.js +0 -78
  97. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -107
  98. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  99. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  100. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  101. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
  102. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -261
  103. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  104. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  105. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  106. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  107. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  108. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  109. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
  110. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  111. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  112. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  113. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -205
  114. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -251
  115. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  116. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  117. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  118. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  119. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  120. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  121. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  122. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -1,172 +0,0 @@
1
- /**
2
- * Shared utilities for templates containing {{var}} and DLT `{#var#}` tokens.
3
- * Same split process used by WhatsApp/RCS: match vars with regex, then split content at each var.
4
- */
5
-
6
- import {
7
- COMBINED_SMS_TEMPLATE_VAR_REGEX,
8
- DEFAULT_MUSTACHE_VAR_REGEX,
9
- DLT_HASH_VAR_TOKEN_FULL_STRING_REGEX,
10
- DLT_VAR_BODY_CAPTURE_REGEX,
11
- MUSTACHE_VAR_NAME_CAPTURE_REGEX,
12
- MUSTACHE_VAR_TOKEN_FULL_STRING_REGEX,
13
- } from '../constants/unified';
14
-
15
- export { COMBINED_SMS_TEMPLATE_VAR_REGEX, DEFAULT_MUSTACHE_VAR_REGEX } from '../constants/unified';
16
-
17
- const isMustacheVarToken = (s) =>
18
- typeof s === 'string' && MUSTACHE_VAR_TOKEN_FULL_STRING_REGEX.test(s);
19
-
20
- export const isDltHashVarToken = (s) =>
21
- typeof s === 'string' && DLT_HASH_VAR_TOKEN_FULL_STRING_REGEX.test(s);
22
-
23
- export const isAnyTemplateVarToken = (s) => isMustacheVarToken(s) || isDltHashVarToken(s);
24
-
25
- /**
26
- * `RegExp.prototype.exec` only advances `lastIndex` when the `g` and/or `y` flag is set.
27
- * A non-global regex in a `while ((m = re.exec(s)) !== null)` loop never advances and can run forever.
28
- *
29
- * @param {RegExp} regex
30
- * @returns {RegExp} Same instance if already global; otherwise a new RegExp with `g` appended to flags.
31
- */
32
- function ensureGlobalRegexForExecLoop(regex) {
33
- if (!regex || !(regex instanceof RegExp) || regex.global) {
34
- return regex;
35
- }
36
- return new RegExp(regex.source, `${regex.flags}g`);
37
- }
38
-
39
- /**
40
- * Splits `content` into alternating plain-text segments and variable tokens, using the order of
41
- * matches in `matchedVariableTokens` (e.g. from `String.prototype.match` with a global regex).
42
- *
43
- * @param {string[]} matchedVariableTokens - Matched tokens in left-to-right order
44
- * @param {string} content - Full template string
45
- * @returns {string[]}
46
- */
47
- export const splitContentByOrderedVarTokens = (matchedVariableTokens, content) => {
48
- const segmentList = [];
49
- const tokenQueue = [...(matchedVariableTokens ?? [])];
50
- let remainder = content ?? '';
51
- while ((remainder?.length ?? 0) > 0) {
52
- const nextVarToken = tokenQueue?.[0];
53
- if (nextVarToken == null || nextVarToken === '') {
54
- segmentList.push(remainder);
55
- break;
56
- }
57
- const varStartIndex = remainder.indexOf(nextVarToken);
58
- if (varStartIndex !== -1) {
59
- segmentList.push(remainder.substring(0, varStartIndex));
60
- segmentList.push(nextVarToken);
61
- const afterVar = varStartIndex + (nextVarToken?.length ?? 0);
62
- remainder = remainder.substring(afterVar, remainder?.length ?? 0);
63
- tokenQueue.shift();
64
- } else {
65
- segmentList.push(remainder);
66
- break;
67
- }
68
- }
69
- return segmentList;
70
- };
71
-
72
- /**
73
- * Splits a template string into an array of text + {{var}} segments using the given regex.
74
- *
75
- * @param {string} str - Template string
76
- * @param {RegExp} [varRegex] - Regex that matches var tokens; defaults to DEFAULT_MUSTACHE_VAR_REGEX
77
- * @returns {string[]}
78
- */
79
- export const splitTemplateVarString = (str = '', varRegex = DEFAULT_MUSTACHE_VAR_REGEX) => {
80
- if (!str) return [];
81
- const matchedVariableTokens = str.match(varRegex) || [];
82
- return splitContentByOrderedVarTokens(matchedVariableTokens, str).filter(
83
- (segment) => segment === 0 || segment
84
- );
85
- };
86
-
87
- /**
88
- * Extracts unique variable names from `{{var}}` (and, when using default capture, `{#var#}`) strings.
89
- *
90
- * @param {string} templateStr
91
- * @param {RegExp} [captureRegex] - regex with a single capture group for the var name; if omitted, also scans DLT `{#…#}`.
92
- * @returns {string[]}
93
- */
94
- export const extractTemplateVariables = (templateStr = '', captureRegex) => {
95
- if (!templateStr) return [];
96
- const variables = [];
97
- const add = (name) => {
98
- const n = (name || '').trim();
99
- if (n && !variables.includes(n)) variables.push(n);
100
- };
101
- const mustacheRe = ensureGlobalRegexForExecLoop(
102
- captureRegex || MUSTACHE_VAR_NAME_CAPTURE_REGEX,
103
- );
104
- let match;
105
- while ((match = mustacheRe.exec(templateStr)) !== null) {
106
- add(match?.[1]);
107
- }
108
- if (!captureRegex) {
109
- const dltRe = new RegExp(DLT_VAR_BODY_CAPTURE_REGEX.source, DLT_VAR_BODY_CAPTURE_REGEX.flags);
110
- let dltMatch;
111
- while ((dltMatch = dltRe.exec(templateStr)) !== null) {
112
- add(dltMatch?.[1] || 'var');
113
- }
114
- }
115
- return variables;
116
- };
117
-
118
- /**
119
- * SMS / DLT template preview: replace `{{…}}` / `{#…#}` tokens using `varMapData` keys `${token}_${index}`.
120
- * Used by SmsTraiEdit (RCS SMS fallback) and UnifiedPreview fallback SMS bubble.
121
- * DLT `{#…#}`: empty / unset slot values show the raw token (matches DLT preview UX); mustache `{{…}}` still
122
- * resolves to empty when the slot is cleared.
123
- */
124
- export const getFallbackResolvedContent = (templateStr = '', varMapData = {}) => {
125
- const fallbackVarSlotMap = varMapData ?? {};
126
- const templateSegments = splitTemplateVarString(templateStr, COMBINED_SMS_TEMPLATE_VAR_REGEX);
127
- return templateSegments
128
- .map((segment, segmentIndex) => {
129
- const isVariableToken = typeof segment === 'string' && isAnyTemplateVarToken(segment);
130
- if (!isVariableToken) return segment;
131
- const slotKey = `${segment}_${segmentIndex}`;
132
- if (Object.prototype.hasOwnProperty.call(fallbackVarSlotMap, slotKey)) {
133
- const slotValue = fallbackVarSlotMap[slotKey];
134
- if (isDltHashVarToken(segment)) {
135
- if (slotValue == null) return segment;
136
- const str = String(slotValue);
137
- if (str.trim() === '') return segment;
138
- return str;
139
- }
140
- return slotValue == null ? '' : String(slotValue);
141
- }
142
- if (isDltHashVarToken(segment)) return segment;
143
- return '';
144
- })
145
- .join('');
146
- };
147
-
148
- /**
149
- * SMS fallback **card** (library list): show filled slot text; if a slot is empty, unset, or
150
- * whitespace-only, show the raw `{{…}}` / `{#…#}` token so “save without labels” still shows
151
- * `{#var#}` in the card. {@link getFallbackResolvedContent} keeps DLT placeholders in preview when
152
- * slots are empty; mustache cleared slots can still render empty.
153
- */
154
- export const getFallbackResolvedContentForCardDisplay = (templateStr = '', varMapData = {}) => {
155
- const fallbackVarSlotMap = varMapData ?? {};
156
- const templateSegments = splitTemplateVarString(templateStr, COMBINED_SMS_TEMPLATE_VAR_REGEX);
157
- return templateSegments
158
- .map((segment, segmentIndex) => {
159
- const isVariableToken = typeof segment === 'string' && isAnyTemplateVarToken(segment);
160
- if (!isVariableToken) return segment;
161
- const slotKey = `${segment}_${segmentIndex}`;
162
- if (Object.prototype.hasOwnProperty.call(fallbackVarSlotMap, slotKey)) {
163
- const slotValue = fallbackVarSlotMap[slotKey];
164
- if (slotValue == null) return segment;
165
- const str = String(slotValue);
166
- if (str.trim() === '') return segment;
167
- return str;
168
- }
169
- return segment;
170
- })
171
- .join('');
172
- };
@@ -1,160 +0,0 @@
1
- import {
2
- extractTemplateVariables,
3
- splitTemplateVarString,
4
- splitContentByOrderedVarTokens,
5
- getFallbackResolvedContent,
6
- getFallbackResolvedContentForCardDisplay,
7
- isDltHashVarToken,
8
- isAnyTemplateVarToken,
9
- } from '../templateVarUtils';
10
-
11
- describe('templateVarUtils', () => {
12
- describe('splitContentByOrderedVarTokens', () => {
13
- it('pushes remainder when next token is not found in string', () => {
14
- expect(splitContentByOrderedVarTokens(['{{b}}'], 'hello')).toEqual(['hello']);
15
- });
16
-
17
- it('handles empty token queue by pushing remainder', () => {
18
- expect(splitContentByOrderedVarTokens([], 'rest')).toEqual(['rest']);
19
- });
20
-
21
- it('handles null matchedVariableTokens as empty queue', () => {
22
- expect(splitContentByOrderedVarTokens(null, 'abc')).toEqual(['abc']);
23
- });
24
-
25
- it('stops and pushes remainder when next token in queue is empty string', () => {
26
- expect(splitContentByOrderedVarTokens(['{{a}}', ''], 'x{{a}}y')).toEqual(['x', '{{a}}', 'y']);
27
- });
28
-
29
- it('splits multiple ordered tokens across content', () => {
30
- expect(splitContentByOrderedVarTokens(['{{a}}', '{{b}}'], 'p{{a}}q{{b}}r')).toEqual([
31
- 'p',
32
- '{{a}}',
33
- 'q',
34
- '{{b}}',
35
- 'r',
36
- ]);
37
- });
38
-
39
- it('returns empty segment list when content is null and queue is empty', () => {
40
- expect(splitContentByOrderedVarTokens([], null)).toEqual([]);
41
- });
42
- });
43
-
44
- describe('splitTemplateVarString', () => {
45
- it('returns empty array for falsy string', () => {
46
- expect(splitTemplateVarString('')).toEqual([]);
47
- expect(splitTemplateVarString(null)).toEqual([]);
48
- });
49
-
50
- it('splits mustache segments', () => {
51
- const parts = splitTemplateVarString('Hi {{name}}!');
52
- expect(parts.some((p) => p.includes('name'))).toBe(true);
53
- });
54
-
55
- it('omits empty segments after filtering', () => {
56
- expect(splitTemplateVarString('a{{x}}b')).toEqual(['a', '{{x}}', 'b']);
57
- });
58
- });
59
-
60
- describe('getFallbackResolvedContent', () => {
61
- it('replaces var segments from varMapData', () => {
62
- const out = getFallbackResolvedContent('{{a}}', { '{{a}}_0': 'X' });
63
- expect(out).toBe('X');
64
- });
65
-
66
- it('keeps DLT token string when slot empty', () => {
67
- const out = getFallbackResolvedContent('{#v#}', {});
68
- expect(out).toContain('#');
69
- });
70
-
71
- it('returns empty string for mustache slot when map has no value', () => {
72
- const out = getFallbackResolvedContent('Hi {{missing}}', {});
73
- expect(out).toBe('Hi ');
74
- });
75
-
76
- it('treats null as empty; explicit empty string is kept (cleared input)', () => {
77
- expect(getFallbackResolvedContent('{{a}}', { '{{a}}_0': null })).toBe('');
78
- expect(getFallbackResolvedContent('{{a}}', { '{{a}}_0': '' })).toBe('');
79
- });
80
-
81
- it('keeps DLT token in preview when slot map has empty string (no tag yet)', () => {
82
- expect(getFallbackResolvedContent('{#v#}', { '{#v#}_0': '' })).toBe('{#v#}');
83
- });
84
-
85
- it('keeps DLT token in preview when slot map has null', () => {
86
- expect(getFallbackResolvedContent('{#v#}', { '{#v#}_0': null })).toBe('{#v#}');
87
- });
88
-
89
- it('uses slot replacement when value is non-empty string', () => {
90
- expect(getFallbackResolvedContent('A{{b}}C', { '{{b}}_1': 'ok' })).toBe('AokC');
91
- });
92
- });
93
-
94
- describe('getFallbackResolvedContentForCardDisplay', () => {
95
- it('shows raw tokens when map is empty or slots unset', () => {
96
- expect(getFallbackResolvedContentForCardDisplay('Hi {{name}}', {})).toBe('Hi {{name}}');
97
- expect(getFallbackResolvedContentForCardDisplay('{#var#}', {})).toBe('{#var#}');
98
- });
99
-
100
- it('shows raw token when slot key exists but value is empty (no labels)', () => {
101
- expect(getFallbackResolvedContentForCardDisplay('{#var#}', { '{#var#}_0': '' })).toBe('{#var#}');
102
- expect(getFallbackResolvedContentForCardDisplay('{{a}}', { '{{a}}_0': '' })).toBe('{{a}}');
103
- });
104
-
105
- it('replaces when slot has non-empty value', () => {
106
- expect(getFallbackResolvedContentForCardDisplay('Hi {{name}}', { '{{name}}_1': 'Pat' })).toBe('Hi Pat');
107
- });
108
- });
109
-
110
- describe('isDltHashVarToken / isAnyTemplateVarToken', () => {
111
- it('detects DLT hash token', () => {
112
- expect(isDltHashVarToken('{#x#}')).toBe(true);
113
- expect(isDltHashVarToken('plain')).toBe(false);
114
- });
115
-
116
- it('isAnyTemplateVarToken combines mustache and DLT', () => {
117
- expect(isAnyTemplateVarToken('{{a}}')).toBe(true);
118
- expect(isAnyTemplateVarToken('{#x#}')).toBe(true);
119
- });
120
- });
121
-
122
- describe('extractTemplateVariables', () => {
123
- it('returns empty array for empty or missing template', () => {
124
- expect(extractTemplateVariables('')).toEqual([]);
125
- expect(extractTemplateVariables()).toEqual([]);
126
- });
127
-
128
- it('includes DLT {#var#} names when no custom capture regex', () => {
129
- const vars = extractTemplateVariables('Hello {#promo#} and {{name}}');
130
- expect(vars).toEqual(expect.arrayContaining(['promo', 'name']));
131
- });
132
-
133
- it('skips DLT scan when captureRegex is provided', () => {
134
- const re = /\{\{([^}]+)\}\}/g;
135
- expect(extractTemplateVariables('{#x#} {{y}}', re)).toEqual(['y']);
136
- });
137
-
138
- it('dedupes duplicate variable names', () => {
139
- expect(extractTemplateVariables('{{a}} {{a}}')).toEqual(['a']);
140
- });
141
-
142
- it('uses default name var when DLT body capture is empty', () => {
143
- const vars = extractTemplateVariables('{##}');
144
- expect(vars).toContain('var');
145
- });
146
- });
147
-
148
- describe('extractTemplateVariables (regex global)', () => {
149
- it('extracts all vars when caller passes a non-global capture regex (avoids infinite exec loop)', () => {
150
- const nonGlobal = /\{\{([^}]+)\}\}/;
151
- expect(nonGlobal.global).toBe(false);
152
- expect(extractTemplateVariables('{{a}} and {{b}}', nonGlobal)).toEqual(['a', 'b']);
153
- });
154
-
155
- it('still works when capture regex already has the global flag', () => {
156
- const globalRe = /\{\{([^}]+)\}\}/g;
157
- expect(extractTemplateVariables('{{x}} {{y}}', globalRe)).toEqual(['x', 'y']);
158
- });
159
- });
160
- });
@@ -1,42 +0,0 @@
1
- import PropTypes from "prop-types";
2
- import { FormattedMessage } from "react-intl";
3
- import CapButton from "@capillarytech/cap-ui-library/CapButton";
4
- import CapIcon from "@capillarytech/cap-ui-library/CapIcon";
5
- import messages from "./messages";
6
- import React from "react";
7
-
8
- const AddTestCustomerButton = ({
9
- searchValue,
10
- handleAddTestCustomer
11
- }) => (
12
- <CapButton
13
- onClick={handleAddTestCustomer}
14
- type="flat"
15
- size="small"
16
- className="test-customer-add-btn"
17
- prefix={
18
- <CapIcon
19
- type="add-profile"
20
- className="add-test-customer-icon"
21
- />
22
- }
23
- >
24
- <FormattedMessage
25
- {...messages.addTestCustomerWithValue}
26
- values={{
27
- searchValue: (
28
- <span className="test-customer-add-btn-value" title={searchValue || ""}>
29
- "{searchValue}"
30
- </span>
31
- ),
32
- }}
33
- />
34
- </CapButton>
35
- );
36
-
37
- AddTestCustomerButton.propTypes = {
38
- searchValue: PropTypes.string.isRequired,
39
- handleAddTestCustomer: PropTypes.func.isRequired
40
- };
41
-
42
- export default AddTestCustomerButton;
@@ -1,155 +0,0 @@
1
- import CapModal from "@capillarytech/cap-ui-library/CapModal";
2
- import CapButton from "@capillarytech/cap-ui-library/CapButton";
3
- import CapInput from "@capillarytech/cap-ui-library/CapInput";
4
- import CapLabel from "@capillarytech/cap-ui-library/CapLabel";
5
- import CapRow from "@capillarytech/cap-ui-library/CapRow";
6
- import CapColumn from "@capillarytech/cap-ui-library/CapColumn";
7
- import { FormattedMessage, injectIntl, intlShape } from "react-intl";
8
- import messages from "./messages";
9
- import React, { useState } from "react";
10
- import PropTypes from "prop-types";
11
- import { CHANNELS } from "./constants";
12
-
13
- /** Identifier validation runs in the parent before this modal opens; email/mobile are read-only here. */
14
- const EMPTY_VALIDATION = { email: "", mobile: "" };
15
-
16
- const CustomerCreationModal = ({
17
- customerModal,
18
- onCloseCustomerModal,
19
- channel,
20
- customerData,
21
- setCustomerData,
22
- onSave,
23
- intl,
24
- }) => {
25
- const [isLoading, setIsLoading] = useState(false);
26
-
27
- // Check if required fields are filled based on channel
28
- const isRequiredFieldMissing = () => {
29
- if (channel === CHANNELS.EMAIL && !customerData.email) {
30
- return true;
31
- }
32
- if (channel === CHANNELS.SMS && !customerData.mobile) {
33
- return true;
34
- }
35
- return false;
36
- };
37
-
38
- const isSaveDisabled = () => isRequiredFieldMissing() || isLoading;
39
-
40
- return (
41
- <CapModal
42
- visible={customerModal[0]}
43
- onCancel={onCloseCustomerModal}
44
- centered={true}
45
- width={500}
46
- maskStyle={{ backgroundColor: 'rgba(244, 245, 247, 0.9)' }}
47
- footer={
48
- <CapRow justify="start" gutter={8}>
49
- <CapButton
50
- type="primary"
51
- onClick={() => onSave(EMPTY_VALIDATION, setIsLoading)}
52
- disabled={isSaveDisabled()}
53
- loading={isLoading}
54
- >
55
- <FormattedMessage {...messages.saveButton} />
56
- </CapButton>
57
- <CapButton
58
- type="secondary"
59
- onClick={onCloseCustomerModal}
60
- disabled={isLoading}
61
- >
62
- <FormattedMessage {...messages.cancelButton} />
63
- </CapButton>
64
- </CapRow>
65
- }
66
- title={<FormattedMessage {...messages.customerCreationModalTitle} />}
67
- wrapClassName="common-test-preview-modal-wrap"
68
- className="common-test-preview-modal"
69
- >
70
- <CapRow className="customer-creation-modal-row">
71
- <CapColumn span={24}>
72
- <span className="customer-creation-modal-description">
73
- <FormattedMessage {...messages.customerCreationModalDescription} />
74
- </span>
75
- </CapColumn>
76
- </CapRow>
77
-
78
- <CapRow className="customer-creation-modal-row">
79
- <CapColumn span={24}>
80
- <CapLabel type="label1" className="customer-creation-modal-label">
81
- <FormattedMessage {...messages.customerName} />{' '}
82
- <span className="customer-creation-modal-optional">
83
- <FormattedMessage {...messages.customerNameOptional} />
84
- </span>
85
- </CapLabel>
86
- <CapInput
87
- value={customerData.name || ""}
88
- onChange={e =>
89
- setCustomerData({ ...customerData, name: e.target.value })
90
- }
91
- placeholder={intl.formatMessage(messages.customerNamePlaceholder)}
92
- size="default"
93
- className="customer-creation-modal-input"
94
- />
95
- </CapColumn>
96
- </CapRow>
97
-
98
- {channel===CHANNELS.EMAIL && <CapRow className="customer-creation-modal-row">
99
- <CapColumn span={24}>
100
- <CapLabel type="label1" className="customer-creation-modal-label">
101
- <FormattedMessage {...messages.customerEmail} />
102
- </CapLabel>
103
- <CapInput
104
- value={customerData.email || ""}
105
- onChange={() => {}}
106
- placeholder={intl.formatMessage(messages.customerEmailPlaceholder)}
107
- size="default"
108
- className="customer-creation-modal-input"
109
- disabled={true}
110
- />
111
- </CapColumn>
112
- </CapRow>}
113
-
114
- {channel===CHANNELS.SMS &&
115
- <CapRow className="customer-creation-modal-row customer-creation-modal-row--last">
116
- <CapColumn span={24}>
117
- <CapLabel type="label1" className="customer-creation-modal-label">
118
- <FormattedMessage {...messages.customerMobile} />
119
- </CapLabel>
120
- <CapInput
121
- value={customerData.mobile || ""}
122
- onChange={() => {}}
123
- placeholder={intl.formatMessage(messages.customerMobilePlaceholder)}
124
- size="default"
125
- className="customer-creation-modal-input"
126
- disabled={true}
127
- />
128
- </CapColumn>
129
- </CapRow>}
130
- </CapModal>
131
- );
132
- };
133
-
134
- CustomerCreationModal.propTypes = {
135
- customerModal: PropTypes.arrayOf(
136
- PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number]),
137
- ).isRequired,
138
- onCloseCustomerModal: PropTypes.func.isRequired,
139
- channel: PropTypes.oneOf(Object.values(CHANNELS)).isRequired,
140
- customerData: PropTypes.shape({
141
- name: PropTypes.string,
142
- email: PropTypes.string,
143
- mobile: PropTypes.string,
144
- customerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
145
- }),
146
- setCustomerData: PropTypes.func.isRequired,
147
- onSave: PropTypes.func.isRequired,
148
- intl: intlShape.isRequired,
149
- };
150
-
151
- CustomerCreationModal.defaultProps = {
152
- customerData: {},
153
- };
154
-
155
- export default injectIntl(CustomerCreationModal);
@@ -1,93 +0,0 @@
1
- import CapModal from "@capillarytech/cap-ui-library/CapModal";
2
- import { FormattedMessage, injectIntl, intlShape } from "react-intl";
3
- import messages from "./messages";
4
- import React, { useState } from "react";
5
- import PropTypes from "prop-types";
6
- import { CapCard, CapRow, CapColumn } from "@capillarytech/cap-ui-library";
7
- import { CHANNELS } from "./constants";
8
- import CapButton from "@capillarytech/cap-ui-library/CapButton";
9
- import CapIcon from "@capillarytech/cap-ui-library/CapIcon";
10
-
11
-
12
- const ExistingCustomerModal = ({ customerModal, onCloseCustomerModal, customerData, channel, onSave, intl }) => {
13
- const [isLoading, setIsLoading] = useState(false);
14
- return (
15
- <CapModal
16
- visible={customerModal[0]}
17
- onCancel={onCloseCustomerModal}
18
- centered={true}
19
- width={500}
20
- maskStyle={{ backgroundColor: 'rgba(244, 245, 247, 0.9)' }}
21
- footer={
22
- <CapRow justify="start">
23
- <CapButton
24
- type="primary"
25
- onClick={() => onSave({}, setIsLoading)}
26
- loading={isLoading}
27
- >
28
- <FormattedMessage {...messages.saveButton} />
29
- </CapButton>
30
- <CapButton
31
- type="secondary"
32
- onClick={onCloseCustomerModal}
33
- disabled={isLoading}
34
- >
35
- <FormattedMessage {...messages.cancelButton} />
36
- </CapButton>
37
- </CapRow>
38
- }
39
- title={intl.formatMessage(messages.customerCreationModalTitle)}
40
- wrapClassName="common-test-preview-modal-wrap existing-customer-modal-wrap"
41
- className="common-test-preview-modal"
42
- >
43
- <div className="existing-customer-modal">
44
- <CapRow className="existing-customer-modal-intro-row">
45
- <FormattedMessage {...messages.existingCustomerModalDescription} />
46
- </CapRow>
47
- <CapCard className="existing-customer-modal-card">
48
- <CapRow className="existing-customer-modal-card-row">
49
- <CapColumn className="existing-customer-modal-avatar">
50
- <CapIcon type="user-profile" className="existing-customer-modal-avatar-icon" />
51
- </CapColumn>
52
- <CapColumn className="existing-customer-modal-details">
53
- <CapRow className="existing-customer-modal-name">
54
- {customerData.name || "-"}
55
- </CapRow>
56
- <CapColumn className="existing-customer-modal-meta">
57
- {channel === CHANNELS.EMAIL && customerData.email && (
58
- <CapRow><span className="existing-customer-modal-meta-label"><FormattedMessage {...messages.customerEmail} />: </span> {customerData.email}</CapRow>
59
- )}
60
- {channel === CHANNELS.SMS && customerData.mobile && (
61
- <CapRow><span className="existing-customer-modal-meta-label"><FormattedMessage {...messages.customerMobile} />: </span>{customerData.mobile}</CapRow>
62
- )}
63
- <CapRow><span className="existing-customer-modal-meta-label"><FormattedMessage {...messages.customerID} />: </span>{customerData.customerId}</CapRow>
64
- </CapColumn>
65
- </CapColumn>
66
- </CapRow>
67
- </CapCard>
68
- </div>
69
- </CapModal>
70
- );
71
- };
72
-
73
- ExistingCustomerModal.propTypes = {
74
- customerModal: PropTypes.arrayOf(
75
- PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number]),
76
- ).isRequired,
77
- onCloseCustomerModal: PropTypes.func.isRequired,
78
- customerData: PropTypes.shape({
79
- name: PropTypes.string,
80
- email: PropTypes.string,
81
- mobile: PropTypes.string,
82
- customerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
83
- }),
84
- channel: PropTypes.oneOf(Object.values(CHANNELS)).isRequired,
85
- onSave: PropTypes.func.isRequired,
86
- intl: intlShape.isRequired,
87
- };
88
-
89
- ExistingCustomerModal.defaultProps = {
90
- customerData: {},
91
- };
92
-
93
- export default injectIntl(ExistingCustomerModal);
@@ -1,59 +0,0 @@
1
- /**
2
- * Helpers for Liquid `/preview` (updateEmailPreview) responses.
3
- * The API may nest the payload under `data` or return fields at the top level.
4
- * SMS responses often use `messageBody` instead of `resolvedBody`.
5
- */
6
-
7
- import { getFallbackResolvedContent } from '../../utils/templateVarUtils';
8
- import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from './constants';
9
-
10
- export function normalizePreviewApiPayload(raw) {
11
- if (!raw || typeof raw !== 'object') return raw;
12
- const next = { ...raw };
13
- if (next.resolvedBody == null) {
14
- const alt = next.messageBody ?? next.previewMessage ?? next.previewText;
15
- if (alt != null) next.resolvedBody = alt;
16
- }
17
- return next;
18
- }
19
-
20
- /**
21
- * Returns a normalized preview object suitable for Redux `previewData`, or null on error / empty.
22
- */
23
- export function extractPreviewFromLiquidResponse(response) {
24
- if (!response || response.error) return null;
25
- if (response.errors && Array.isArray(response.errors) && response.errors.length > 0) return null;
26
-
27
- let body;
28
- if (response.data !== undefined && response.data !== null && typeof response.data === 'object') {
29
- body = normalizePreviewApiPayload(response.data);
30
- } else if (typeof response === 'object' && !Array.isArray(response)) {
31
- body = normalizePreviewApiPayload(response);
32
- } else {
33
- return null;
34
- }
35
-
36
- if (!body) return null;
37
- if (body.resolvedBody === undefined && body.messageBody === undefined) {
38
- return null;
39
- }
40
- return body;
41
- }
42
-
43
- /**
44
- * RCS SMS fallback: merge template (`templateContent` / `content`) with VarSegment
45
- * `rcsSmsFallbackVarMapped` before Liquid /preview, tag extraction, or createMessageMeta.
46
- * Raw template alone is stale when the user edits slot values.
47
- */
48
- export function getSmsFallbackTextForTagExtraction(smsFallbackContext) {
49
- const rawTemplateBody =
50
- smsFallbackContext?.templateContent ?? smsFallbackContext?.content ?? '';
51
- if (!rawTemplateBody) return '';
52
- const rcsSmsFallbackVarMapped = smsFallbackContext?.[RCS_SMS_FALLBACK_VAR_MAPPED_PROP];
53
- const hasRcsSmsFallbackVarMappedEntries =
54
- Object.keys(rcsSmsFallbackVarMapped ?? {}).length > 0;
55
- if (hasRcsSmsFallbackVarMappedEntries) {
56
- return getFallbackResolvedContent(rawTemplateBody, rcsSmsFallbackVarMapped ?? {});
57
- }
58
- return rawTemplateBody;
59
- }