@capillarytech/creatives-library 8.0.223-alpha.0 → 8.0.223

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.223-alpha.0",
4
+ "version": "8.0.223",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -16,7 +16,7 @@ import {
16
16
  IOS,
17
17
  } from "../v2Containers/CreativesContainer/constants";
18
18
  import { GLOBAL_CONVERT_OPTIONS } from "../v2Components/FormBuilder/constants";
19
- import { checkSupport, extractNames } from "./tagValidations";
19
+ import { checkSupport, extractNames, skipTags as defaultSkipTags } from "./tagValidations";
20
20
  import { SMS_TRAI_VAR } from '../v2Containers/SmsTrai/Edit/constants';
21
21
  export const apiMessageFormatHandler = (id, fallback) => (
22
22
  <FormattedMessage id={id} defaultMessage={fallback} />
@@ -146,7 +146,7 @@ export const validateLiquidTemplateContent = async (
146
146
  isLiquidFlow,
147
147
  forwardedTags = {},
148
148
  tabType,
149
- skipTags = () => false,
149
+ skipTags = defaultSkipTags,
150
150
  } = options;
151
151
  const emptyBodyError = formatMessage(messages?.emailBodyEmptyError);
152
152
  const somethingWrongMsg = formatMessage(messages?.somethingWentWrong);
@@ -221,7 +221,7 @@ export const validateTags = ({
221
221
  /**
222
222
  * Checks if the given tag is supported based on the injected tags.
223
223
  * @param {string} checkingTag - The tag to check.
224
- * @param {Array} injectedTags - The injected tags.
224
+ * @param {Object} injectedTags - The injected tags.
225
225
  * @returns {boolean} - True if the tag is supported, false otherwise.
226
226
  */
227
227
  export const checkIfSupportedTag = (checkingTag, injectedTags) => {
@@ -235,7 +235,7 @@ export const checkIfSupportedTag = (checkingTag, injectedTags) => {
235
235
  });
236
236
 
237
237
  return result;
238
- }
238
+ };
239
239
 
240
240
  const indexOfEnd = (targetString, string) => {
241
241
  let io = targetString.indexOf(string);
@@ -247,19 +247,14 @@ export const skipTags = (tag) => {
247
247
  if (tag?.match(ENTRY_TRIGGER_TAG_REGEX)) {
248
248
  return false;
249
249
  }
250
- const regexGroups = ["dynamic_expiry_date_after_\\d+_days.FORMAT_\\d", "unsubscribe\\(#[a-zA-Z\\d]{6}\\)","Link_to_[a-zA-z]","SURVEY.*.TOKEN", "^[A-Za-z].*\\([a-zA-Z\\d]*\\)"];
251
- let skipped = false;
252
- lodashForEach(regexGroups, (group) => {
253
- const groupRegex = new RegExp(group, "g");
254
- let match = groupRegex.exec(tag);
255
- if (match !== null ) {
256
- skipped = true;
257
- return true;
258
- }
259
- return true;
250
+ const regexGroups = ["dynamic_expiry_date_after_\\d+_days.FORMAT_\\d", "unsubscribe\\(#[a-zA-Z\\d]{6}\\)", "Link_to_[a-zA-z]", "SURVEY.*.TOKEN", "^[A-Za-z].*\\([a-zA-Z\\d]*\\)", "referral_unique_(code|url).*userid"];
251
+ // Use some() to check if any pattern matches (stops on first match)
252
+ return regexGroups.some((group) => {
253
+ // Create a new RegExp for each test to avoid state issues with global flag
254
+ const groupRegex = new RegExp(group);
255
+ return groupRegex.test(tag);
260
256
  });
261
- return skipped;
262
- }
257
+ };
263
258
 
264
259
  export const transformInjectedTags = (tags) => {
265
260
  lodashForEach(tags, (tag) => {
@@ -7,6 +7,7 @@ import {
7
7
  addBaseToTemplate,
8
8
  validateCarouselCards,
9
9
  } from "../commonUtils";
10
+ import { skipTags } from "../tagValidations";
10
11
  import { SMS_TRAI_VAR } from '../../v2Containers/SmsTrai/Edit/constants';
11
12
  import { ANDROID, IOS } from '../../v2Containers/CreativesContainer/constants';
12
13
 
@@ -122,6 +123,150 @@ describe("validateLiquidTemplateContent", () => {
122
123
  expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
123
124
  });
124
125
 
126
+ it("should skip referral_unique_code tags and not trigger unsupported tag error", async () => {
127
+ const getLiquidTags = jest.fn((content, cb) =>
128
+ cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_C6SOE_userid" }] }, isError: false })
129
+ );
130
+ await validateLiquidTemplateContent("foo", {
131
+ getLiquidTags,
132
+ formatMessage,
133
+ messages,
134
+ onError,
135
+ onSuccess,
136
+ tagLookupMap,
137
+ eventContextTags,
138
+ skipTags
139
+ });
140
+ expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
141
+ expect(onError).not.toHaveBeenCalled();
142
+ });
143
+
144
+ it("should skip referral_unique_url tags and not trigger unsupported tag error", async () => {
145
+ const getLiquidTags = jest.fn((content, cb) =>
146
+ cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_url_C6SOE_userid" }] }, isError: false })
147
+ );
148
+ await validateLiquidTemplateContent("foo", {
149
+ getLiquidTags,
150
+ formatMessage,
151
+ messages,
152
+ onError,
153
+ onSuccess,
154
+ tagLookupMap,
155
+ eventContextTags,
156
+ skipTags
157
+ });
158
+ expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
159
+ expect(onError).not.toHaveBeenCalled();
160
+ });
161
+
162
+ it("should skip referral_unique_code tags with different codes", async () => {
163
+ const getLiquidTags = jest.fn((content, cb) =>
164
+ cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_ABC123_userid" }] }, isError: false })
165
+ );
166
+ await validateLiquidTemplateContent("foo", {
167
+ getLiquidTags,
168
+ formatMessage,
169
+ messages,
170
+ onError,
171
+ onSuccess,
172
+ tagLookupMap,
173
+ eventContextTags,
174
+ skipTags
175
+ });
176
+ expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
177
+ expect(onError).not.toHaveBeenCalled();
178
+ });
179
+
180
+ it("should skip referral_unique_url tags with different codes", async () => {
181
+ const getLiquidTags = jest.fn((content, cb) =>
182
+ cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_url_ABC123_userid" }] }, isError: false })
183
+ );
184
+ await validateLiquidTemplateContent("foo", {
185
+ getLiquidTags,
186
+ formatMessage,
187
+ messages,
188
+ onError,
189
+ onSuccess,
190
+ tagLookupMap,
191
+ eventContextTags,
192
+ skipTags
193
+ });
194
+ expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
195
+ expect(onError).not.toHaveBeenCalled();
196
+ });
197
+
198
+ it("should skip referral_unique_code tags with alphanumeric codes", async () => {
199
+ const getLiquidTags = jest.fn((content, cb) =>
200
+ cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_XYZ789_userid" }] }, isError: false })
201
+ );
202
+ await validateLiquidTemplateContent("foo", {
203
+ getLiquidTags,
204
+ formatMessage,
205
+ messages,
206
+ onError,
207
+ onSuccess,
208
+ tagLookupMap,
209
+ eventContextTags,
210
+ skipTags
211
+ });
212
+ expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
213
+ expect(onError).not.toHaveBeenCalled();
214
+ });
215
+
216
+ it("should skip referral_unique_url tags with alphanumeric codes", async () => {
217
+ const getLiquidTags = jest.fn((content, cb) =>
218
+ cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_url_XYZ789_userid" }] }, isError: false })
219
+ );
220
+ await validateLiquidTemplateContent("foo", {
221
+ getLiquidTags,
222
+ formatMessage,
223
+ messages,
224
+ onError,
225
+ onSuccess,
226
+ tagLookupMap,
227
+ eventContextTags,
228
+ skipTags
229
+ });
230
+ expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
231
+ expect(onError).not.toHaveBeenCalled();
232
+ });
233
+
234
+ it("should skip both referral_unique_code and referral_unique_url tags in the same content", async () => {
235
+ const getLiquidTags = jest.fn((content, cb) =>
236
+ cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_C6SOE_userid" }, { name: "referral_unique_url_C6SOE_userid" }] }, isError: false })
237
+ );
238
+ await validateLiquidTemplateContent("foo", {
239
+ getLiquidTags,
240
+ formatMessage,
241
+ messages,
242
+ onError,
243
+ onSuccess,
244
+ tagLookupMap,
245
+ eventContextTags,
246
+ skipTags
247
+ });
248
+ expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
249
+ expect(onError).not.toHaveBeenCalled();
250
+ });
251
+
252
+ it("should skip referral_unique_code_632L7_userid tag (real-world example)", async () => {
253
+ const getLiquidTags = jest.fn((content, cb) =>
254
+ cb({ askAiraResponse: { errors: [], data: [{ name: "referral_unique_code_632L7_userid" }] }, isError: false })
255
+ );
256
+ await validateLiquidTemplateContent("foo", {
257
+ getLiquidTags,
258
+ formatMessage,
259
+ messages,
260
+ onError,
261
+ onSuccess,
262
+ tagLookupMap,
263
+ eventContextTags,
264
+ skipTags
265
+ });
266
+ expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
267
+ expect(onError).not.toHaveBeenCalled();
268
+ });
269
+
125
270
  it("calls onError with emailBodyEmptyError when validString is falsy", async () => {
126
271
  const getLiquidTags = jest.fn((content, cb) => cb({ askAiraResponse: { errors: [], data: [] }, isError: false }));
127
272
  const formatMessage = jest.fn((msg) => msg.id);
@@ -952,6 +952,92 @@ describe("skipTags", () => {
952
952
  const result = skipTags(tag);
953
953
  expect(result).toEqual(true);
954
954
  });
955
+
956
+ it("should return true for referral unique code tags with userid", () => {
957
+ const tag = "referral_unique_code_C6SOE_userid";
958
+ const result = skipTags(tag);
959
+ expect(result).toEqual(true);
960
+ });
961
+
962
+ it("should return true for referral unique url tags with userid", () => {
963
+ const tag = "referral_unique_url_C6SOE_userid";
964
+ const result = skipTags(tag);
965
+ expect(result).toEqual(true);
966
+ });
967
+
968
+ it("should return true for referral unique code tags with different codes", () => {
969
+ const tag = "referral_unique_code_ABC123_userid";
970
+ const result = skipTags(tag);
971
+ expect(result).toEqual(true);
972
+ });
973
+
974
+ it("should return true for referral unique url tags with different codes", () => {
975
+ const tag = "referral_unique_url_ABC123_userid";
976
+ const result = skipTags(tag);
977
+ expect(result).toEqual(true);
978
+ });
979
+
980
+ it("should return true for referral unique code tags with alphanumeric codes", () => {
981
+ const tag = "referral_unique_code_XYZ789_userid";
982
+ const result = skipTags(tag);
983
+ expect(result).toEqual(true);
984
+ });
985
+
986
+ it("should return true for referral unique url tags with alphanumeric codes", () => {
987
+ const tag = "referral_unique_url_XYZ789_userid";
988
+ const result = skipTags(tag);
989
+ expect(result).toEqual(true);
990
+ });
991
+
992
+ it("should return false for tags that don't match referral pattern", () => {
993
+ const tag = "referral_code_userid";
994
+ const result = skipTags(tag);
995
+ expect(result).toEqual(false);
996
+ });
997
+
998
+ it("should return false for tags with referral but missing unique_code or unique_url", () => {
999
+ const tag = "referral_C6SOE_userid";
1000
+ const result = skipTags(tag);
1001
+ expect(result).toEqual(false);
1002
+ });
1003
+
1004
+ it("should return false for tags with referral_unique_code but missing userid", () => {
1005
+ const tag = "referral_unique_code_C6SOE";
1006
+ const result = skipTags(tag);
1007
+ expect(result).toEqual(false);
1008
+ });
1009
+
1010
+ it("should return false for tags with referral_unique_url but missing userid", () => {
1011
+ const tag = "referral_unique_url_C6SOE";
1012
+ const result = skipTags(tag);
1013
+ expect(result).toEqual(false);
1014
+ });
1015
+
1016
+ it("should return true for referral_unique_code tags with numeric codes like 632L7", () => {
1017
+ const tag = "referral_unique_code_632L7_userid";
1018
+ const result = skipTags(tag);
1019
+ expect(result).toEqual(true);
1020
+ });
1021
+
1022
+ it("should return true for referral_unique_url tags with numeric codes like 632L7", () => {
1023
+ const tag = "referral_unique_url_632L7_userid";
1024
+ const result = skipTags(tag);
1025
+ expect(result).toEqual(true);
1026
+ });
1027
+
1028
+ it("should return true for referral tags with various token formats", () => {
1029
+ const tags = [
1030
+ "referral_unique_code_123_userid",
1031
+ "referral_unique_code_ABC_userid",
1032
+ "referral_unique_code_123ABC_userid",
1033
+ "referral_unique_url_456_userid",
1034
+ "referral_unique_url_DEF_userid",
1035
+ "referral_unique_url_456DEF_userid",
1036
+ ];
1037
+ tags.forEach((tag) => {
1038
+ expect(skipTags(tag)).toEqual(true);
1039
+ });
1040
+ });
955
1041
  });
956
1042
 
957
1043
 
@@ -1491,20 +1491,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1491
1491
  if (tag?.match(ENTRY_TRIGGER_TAG_REGEX)) {
1492
1492
  return false;
1493
1493
  }
1494
- const regexGroups = ["dynamic_expiry_date_after_\\d+_days.FORMAT_\\d", "unsubscribe\\(#[a-zA-Z\\d]{6}\\)","Link_to_[a-zA-z]","SURVEY.*.TOKEN","^[A-Za-z].*\\([a-zA-Z\\d]*\\)"];
1495
- //const regexGroups = [];
1496
- let skipped = false;
1497
- _.forEach(regexGroups, (group) => {
1498
- //const groupRegex = /dynamic_expiry_date_after_\(\d+\)_days.FORMAT_\d/g;
1499
- const groupRegex = new RegExp(group, "g");
1500
- let match = groupRegex.exec(tag);
1501
- if (match !== null ) {
1502
- skipped = true;
1503
- return true;
1504
- }
1505
- return true;
1494
+ const regexGroups = ["dynamic_expiry_date_after_\\d+_days.FORMAT_\\d", "unsubscribe\\(#[a-zA-Z\\d]{6}\\)", "Link_to_[a-zA-z]", "SURVEY.*.TOKEN", "^[A-Za-z].*\\([a-zA-Z\\d]*\\)", "referral_unique_(code|url).*userid"];
1495
+ // Use some() to check if any pattern matches (stops on first match)
1496
+ return regexGroups.some((group) => {
1497
+ // Create a new RegExp for each test to avoid state issues with global flag
1498
+ const groupRegex = new RegExp(group);
1499
+ return groupRegex.test(tag);
1506
1500
  });
1507
- return skipped;
1508
1501
  }
1509
1502
 
1510
1503
  validateTags(content, tagsParam, injectedTagsParams, isEmail = false) {
@@ -19,7 +19,6 @@ const SendTestMessage = ({
19
19
  formData,
20
20
  isSendingTestMessage,
21
21
  formatMessage,
22
- isContentValid = true,
23
22
  }) => (
24
23
  <CapStepsAccordian
25
24
  showNumberSteps={false}
@@ -44,11 +43,7 @@ const SendTestMessage = ({
44
43
  multiple
45
44
  placeholder={formatMessage(messages.testCustomersPlaceholder)}
46
45
  />
47
- <CapButton
48
- onClick={handleSendTestMessage}
49
- disabled={isEmpty(selectedTestEntities) || (isEmpty(formData['template-subject']) && isEmpty(formData[0]?.['template-subject'])) || isSendingTestMessage || !isContentValid}
50
- title={!isContentValid ? formatMessage(messages.contentInvalid) : ''}
51
- >
46
+ <CapButton onClick={handleSendTestMessage} disabled={isEmpty(selectedTestEntities) || (isEmpty(formData['template-subject']) && isEmpty(formData[0]?.['template-subject'])) || isSendingTestMessage}>
52
47
  <FormattedMessage {...messages.sendTestButton} />
53
48
  </CapButton>
54
49
  </CapRow>),
@@ -68,7 +63,6 @@ SendTestMessage.propTypes = {
68
63
  formData: PropTypes.object.isRequired,
69
64
  isSendingTestMessage: PropTypes.bool.isRequired,
70
65
  formatMessage: PropTypes.func.isRequired,
71
- isContentValid: PropTypes.bool,
72
66
  };
73
67
 
74
68
  export default SendTestMessage;
@@ -52,7 +52,6 @@ import {
52
52
  INITIAL_PAYLOAD, EMAIL, TEST, DESKTOP, ACTIVE, MOBILE,
53
53
  } from './constants';
54
54
  import { GLOBAL_CONVERT_OPTIONS } from '../FormBuilder/constants';
55
- import { validateIfTagClosed } from '../../utils/tagValidations';
56
55
 
57
56
  const TestAndPreviewSlidebox = (props) => {
58
57
  const {
@@ -104,147 +103,10 @@ const TestAndPreviewSlidebox = (props) => {
104
103
  const [selectedTestEntities, setSelectedTestEntities] = useState([]);
105
104
  const [beeContent, setBeeContent] = useState(''); // Track BEE editor content separately
106
105
  const previousBeeContentRef = useRef(''); // Track previous BEE content to prevent unnecessary updates
107
- const [isContentValid, setIsContentValid] = useState(true); // Track if content tags are valid
108
106
 
109
107
  const isUpdatePreviewDisabled = useMemo(() => (
110
- requiredTags.some((tag) => !customValues[tag.fullPath]) || !isContentValid
111
- ), [requiredTags, customValues, isContentValid]);
112
-
113
- // Function to validate tags in content
114
- const validateContentTags = (content) => {
115
- if (!content) return true;
116
-
117
- try {
118
- // Convert HTML to text (same as what's used for tag extraction)
119
- // This ensures we validate the same content that will be used for tag extraction
120
- const textContent = convert(content, GLOBAL_CONVERT_OPTIONS);
121
-
122
- // Check if there are any braces in the content
123
- const hasBraces = textContent.includes('{') || textContent.includes('}');
124
-
125
- // If no braces exist, content is valid (no tag validation needed)
126
- if (!hasBraces) {
127
- return true;
128
- }
129
-
130
- // First check if tags are properly closed using the utility function
131
- // This validates that all opening braces have corresponding closing braces
132
- if (!validateIfTagClosed(textContent)) {
133
- return false;
134
- }
135
-
136
- // Now validate tag format: tags must be in format {{tag_name}}
137
- // Find all valid tag patterns {{tag_name}}
138
- const tagPattern = /{{[^}]*}}/g;
139
- const matches = textContent.match(tagPattern) || [];
140
-
141
- // Remove all valid {{tag}} patterns from content to check for invalid braces
142
- let contentWithoutValidTags = textContent;
143
- matches.forEach((match) => {
144
- contentWithoutValidTags = contentWithoutValidTags.replace(match, '');
145
- });
146
-
147
- // Check if there are any remaining braces (single braces or unclosed braces)
148
- // These would be invalid patterns like {tag}, {first, first}, etc.
149
- if (contentWithoutValidTags.includes('{') || contentWithoutValidTags.includes('}')) {
150
- return false;
151
- }
152
-
153
- // Check each tag for valid format
154
- for (const match of matches) {
155
- // Valid tag format: {{tag_name}} - must start with {{ and end with }}
156
- if (!match.startsWith('{{') || !match.endsWith('}}')) {
157
- return false;
158
- }
159
-
160
- // Extract tag name (content between {{ and }})
161
- const tagName = match.slice(2, -2).trim();
162
-
163
- // Tag name should not be empty
164
- if (!tagName) {
165
- return false;
166
- }
167
-
168
- // Check for invalid patterns in tag name
169
- // Invalid patterns: "first or first", "first and first", etc.
170
- const invalidPatterns = [
171
- /\s+or\s+/i, // " or " as separate word (e.g., "first or first")
172
- /\s+and\s+/i, // " and " as separate word
173
- ];
174
-
175
- for (const pattern of invalidPatterns) {
176
- if (pattern.test(tagName)) {
177
- return false;
178
- }
179
- }
180
-
181
- // Check for unclosed single braces in tag name (e.g., {{first{name}})
182
- const singleOpenBraces = (tagName.match(/{/g) || []).length;
183
- const singleCloseBraces = (tagName.match(/}/g) || []).length;
184
- if (singleOpenBraces !== singleCloseBraces) {
185
- return false;
186
- }
187
- }
188
-
189
- return true;
190
- } catch (error) {
191
- // If conversion fails, fall back to validating the original content
192
- console.warn('Error converting content for validation:', error);
193
- const hasBraces = content.includes('{') || content.includes('}');
194
- if (!hasBraces) {
195
- return true;
196
- }
197
-
198
- // Apply same validation to original content
199
- if (!validateIfTagClosed(content)) {
200
- return false;
201
- }
202
-
203
- const tagPattern = /{{[^}]*}}/g;
204
- const matches = content.match(tagPattern) || [];
205
-
206
- // Remove all valid {{tag}} patterns from content to check for invalid braces
207
- let contentWithoutValidTags = content;
208
- matches.forEach((match) => {
209
- contentWithoutValidTags = contentWithoutValidTags.replace(match, '');
210
- });
211
-
212
- // Check if there are any remaining braces (single braces or unclosed braces)
213
- if (contentWithoutValidTags.includes('{') || contentWithoutValidTags.includes('}')) {
214
- return false;
215
- }
216
-
217
- for (const match of matches) {
218
- if (!match.startsWith('{{') || !match.endsWith('}}')) {
219
- return false;
220
- }
221
-
222
- const tagName = match.slice(2, -2).trim();
223
- if (!tagName) {
224
- return false;
225
- }
226
-
227
- const invalidPatterns = [
228
- /\s+or\s+/i,
229
- /\s+and\s+/i,
230
- ];
231
-
232
- for (const pattern of invalidPatterns) {
233
- if (pattern.test(tagName)) {
234
- return false;
235
- }
236
- }
237
-
238
- const singleOpenBraces = (tagName.match(/{/g) || []).length;
239
- const singleCloseBraces = (tagName.match(/}/g) || []).length;
240
- if (singleOpenBraces !== singleCloseBraces) {
241
- return false;
242
- }
243
- }
244
-
245
- return true;
246
- }
247
- };
108
+ requiredTags.some((tag) => !customValues[tag.fullPath])
109
+ ), [requiredTags, customValues]);
248
110
 
249
111
  // Function to resolve tags in text with custom values
250
112
  const resolveTagsInText = (text, tagValues) => {
@@ -291,10 +153,6 @@ const TestAndPreviewSlidebox = (props) => {
291
153
  if (existingContent && existingContent.trim() !== '') {
292
154
  // We already have content, update local state only if it's different
293
155
  if (existingContent !== previousBeeContentRef.current) {
294
- // Validate content tags for BEE editor
295
- const isValid = validateContentTags(existingContent);
296
- setIsContentValid(isValid);
297
-
298
156
  previousBeeContentRef.current = existingContent;
299
157
  setBeeContent(existingContent);
300
158
  setPreviewDataHtml({
@@ -328,10 +186,6 @@ const TestAndPreviewSlidebox = (props) => {
328
186
  }
329
187
 
330
188
  if (htmlFile) {
331
- // Validate content tags
332
- const isValid = validateContentTags(htmlFile);
333
- setIsContentValid(isValid);
334
-
335
189
  // Update our states
336
190
  previousBeeContentRef.current = htmlFile;
337
191
  setBeeContent(htmlFile);
@@ -340,16 +194,9 @@ const TestAndPreviewSlidebox = (props) => {
340
194
  resolvedTitle: formData['template-subject'] || ''
341
195
  });
342
196
 
343
- // Only extract tags if content is valid
344
- if (isValid) {
345
- const payloadContent = convert(htmlFile, GLOBAL_CONVERT_OPTIONS);
346
- actions.extractTagsRequested(formData['template-subject'] || '', payloadContent);
347
- } else {
348
- // Show error notification for invalid content
349
- CapNotification.error({
350
- message: formatMessage(messages.contentInvalid),
351
- });
352
- }
197
+ // Always extract tags when content changes
198
+ const payloadContent = convert(htmlFile, GLOBAL_CONVERT_OPTIONS);
199
+ actions.extractTagsRequested(formData['template-subject'] || '', payloadContent);
353
200
  }
354
201
 
355
202
  // Restore original handler
@@ -364,45 +211,23 @@ const TestAndPreviewSlidebox = (props) => {
364
211
  const templateContent = currentTabData?.[activeTab]?.['template-content'];
365
212
 
366
213
  if (templateContent) {
367
- // Validate content tags
368
- const isValid = validateContentTags(templateContent);
369
- setIsContentValid(isValid);
370
-
371
214
  // Update preview with initial content
372
215
  setPreviewDataHtml({
373
216
  resolvedBody: templateContent,
374
217
  resolvedTitle: formData['template-subject'] || ''
375
218
  });
376
219
 
377
- // Only extract tags if content is valid
378
- if (isValid) {
379
- const payloadContent = convert(templateContent, GLOBAL_CONVERT_OPTIONS);
380
- actions.extractTagsRequested(formData['template-subject'] || '', payloadContent);
381
- } else {
382
- // Show error notification for invalid content
383
- CapNotification.error({
384
- message: formatMessage(messages.contentInvalid),
385
- });
386
- }
220
+ // Always extract tags when showing
221
+ const payloadContent = convert(templateContent, GLOBAL_CONVERT_OPTIONS);
222
+ actions.extractTagsRequested(formData['template-subject'] || '', payloadContent);
387
223
  } else {
388
224
  // Fallback to content prop if no template content
389
- const contentToValidate = getCurrentContent;
390
- const isValid = validateContentTags(contentToValidate);
391
- setIsContentValid(isValid);
392
-
393
- // Only extract tags if content is valid
394
- if (isValid) {
395
- const payloadContent = convert(
396
- contentToValidate,
397
- GLOBAL_CONVERT_OPTIONS
398
- );
399
- actions.extractTagsRequested(formData['template-subject'] || '', payloadContent);
400
- } else {
401
- // Show error notification for invalid content
402
- CapNotification.error({
403
- message: formatMessage(messages.contentInvalid),
404
- });
405
- }
225
+ const payloadContent = convert(
226
+ getCurrentContent,
227
+ GLOBAL_CONVERT_OPTIONS
228
+ );
229
+ // Always extract tags when showing
230
+ actions.extractTagsRequested(formData['template-subject'] || '', payloadContent);
406
231
  }
407
232
  }
408
233
 
@@ -418,28 +243,17 @@ const TestAndPreviewSlidebox = (props) => {
418
243
  const isDragDrop = currentTabData?.[activeTab]?.is_drag_drop;
419
244
  const templateContent = currentTabData?.[activeTab]?.['template-content'];
420
245
 
421
- if (templateContent && templateContent.trim() !== '' && show) {
422
- // Common function to handle content update with validation
246
+ if (templateContent && templateContent.trim() !== '') {
247
+ // Common function to handle content update
423
248
  const handleContentUpdate = (content) => {
424
- // Validate content tags for each update
425
- const isValid = validateContentTags(content);
426
- setIsContentValid(isValid);
427
-
428
249
  setPreviewDataHtml({
429
250
  resolvedBody: content,
430
251
  resolvedTitle: formData['template-subject'] || ''
431
252
  });
432
253
 
433
- // Only extract tags if content is valid
434
- if (isValid) {
435
- const payloadContent = convert(content, GLOBAL_CONVERT_OPTIONS);
436
- actions.extractTagsRequested(formData['template-subject'] || '', payloadContent);
437
- } else {
438
- // Show error notification for invalid content
439
- CapNotification.error({
440
- message: formatMessage(messages.contentInvalid),
441
- });
442
- }
254
+ // Extract tags from content
255
+ const payloadContent = convert(content, GLOBAL_CONVERT_OPTIONS);
256
+ actions.extractTagsRequested(formData['template-subject'] || '', payloadContent);
443
257
  };
444
258
 
445
259
  if (isDragDrop) {
@@ -473,7 +287,6 @@ const TestAndPreviewSlidebox = (props) => {
473
287
  setTagsExtracted(false);
474
288
  setPreviewDevice('desktop');
475
289
  setSelectedTestEntities([]);
476
- setIsContentValid(true);
477
290
  actions.clearPrefilledValues();
478
291
  }
479
292
  }, [show]);
@@ -717,22 +530,6 @@ const TestAndPreviewSlidebox = (props) => {
717
530
 
718
531
  // Handle update preview
719
532
  const handleUpdatePreview = async () => {
720
- // Re-validate content to get latest state (in case liquid errors were fixed)
721
- const currentTabData = formData[currentTab - 1];
722
- const activeTab = currentTabData?.activeTab;
723
- const templateContent = currentTabData?.[activeTab]?.['template-content'];
724
- const contentToValidate = templateContent || getCurrentContent;
725
- const isValid = validateContentTags(contentToValidate);
726
- setIsContentValid(isValid);
727
-
728
- // Check if content is valid before updating preview
729
- if (!isValid) {
730
- CapNotification.error({
731
- message: formatMessage(messages.contentInvalid),
732
- });
733
- return;
734
- }
735
-
736
533
  try {
737
534
  // Include unsubscribe tag if content contains it
738
535
  const resolvedTags = { ...customValues };
@@ -762,20 +559,9 @@ const TestAndPreviewSlidebox = (props) => {
762
559
  const currentTabData = formData[currentTab - 1];
763
560
  const activeTab = currentTabData?.activeTab;
764
561
  const templateContent = currentTabData?.[activeTab]?.['template-content'];
765
- const content = templateContent || getCurrentContent;
766
-
767
- // Validate content tags before extracting
768
- const isValid = validateContentTags(content);
769
- setIsContentValid(isValid);
770
-
771
- if (!isValid) {
772
- CapNotification.error({
773
- message: formatMessage(messages.contentInvalid),
774
- });
775
- return;
776
- }
777
562
 
778
563
  // Check for personalization tags (excluding unsubscribe)
564
+ const content = templateContent || getCurrentContent;
779
565
  const tags = content.match(/{{[^}]+}}/g) || [];
780
566
  const hasPersonalizationTags = tags.some(tag => !tag.includes('unsubscribe'));
781
567
 
@@ -804,22 +590,6 @@ const TestAndPreviewSlidebox = (props) => {
804
590
  };
805
591
 
806
592
  const handleSendTestMessage = () => {
807
- // Re-validate content to get latest state (in case liquid errors were fixed)
808
- const currentTabData = formData[currentTab - 1];
809
- const activeTab = currentTabData?.activeTab;
810
- const templateContent = currentTabData?.[activeTab]?.['template-content'];
811
- const contentToValidate = templateContent || getCurrentContent;
812
- const isValid = validateContentTags(contentToValidate);
813
- setIsContentValid(isValid);
814
-
815
- // Check if content is valid before sending test message
816
- if (!isValid) {
817
- CapNotification.error({
818
- message: formatMessage(messages.contentInvalid),
819
- });
820
- return;
821
- }
822
-
823
593
  const allUserIds = [];
824
594
  selectedTestEntities.forEach((entityId) => {
825
595
  const group = testGroups.find((g) => g.groupId === entityId);
@@ -915,7 +685,6 @@ const TestAndPreviewSlidebox = (props) => {
915
685
  formData={formData}
916
686
  isSendingTestMessage={isSendingTestMessage}
917
687
  formatMessage={formatMessage}
918
- isContentValid={isContentValid}
919
688
  />
920
689
  );
921
690
 
@@ -144,12 +144,4 @@ export default defineMessages({
144
144
  id: `${scope}.invalidJSON`,
145
145
  defaultMessage: 'Invalid JSON input',
146
146
  },
147
- contentInvalid: {
148
- id: `${scope}.contentInvalid`,
149
- defaultMessage: 'Content is invalid. Please fix the tags in your content before testing or previewing.',
150
- },
151
- previewUpdateError: {
152
- id: `${scope}.previewUpdateError`,
153
- defaultMessage: 'Failed to update preview',
154
- },
155
147
  });