@capillarytech/creatives-library 8.0.262 → 8.0.263

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.262",
4
+ "version": "8.0.263",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -406,7 +406,7 @@ const ErrorSection = ({
406
406
  <CapButton
407
407
  type="flat"
408
408
  className="add-btn"
409
- onClick={() => window.open('https://docs.capillarytech.com/docs/liquid-language-in-messages', '_blank')}
409
+ onClick={() => window.open(LIQUID_DOC_URL, '_blank')}
410
410
  >
411
411
  <FormattedMessage {...messages.liquidDoc} />
412
412
  <CapIcon size="s" type="launch" />
@@ -42,6 +42,7 @@ import { useValidation } from './hooks/useValidation';
42
42
  import {
43
43
  HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT, TAG, EMBEDDED, DEFAULT, FULL, ALL, SMS, EMAIL,
44
44
  BLOCKING_ERROR_RULE_IDS,
45
+ VALIDATION_SEVERITY,
45
46
  } from './constants';
46
47
 
47
48
  // Styles
@@ -50,12 +51,13 @@ import './components/FullscreenModal/_fullscreenModal.scss';
50
51
 
51
52
  // Messages
52
53
  import messages from './messages';
54
+ import { ISSUE_SOURCES } from './utils/validationConstants';
53
55
 
54
56
  /** Check if an issue is a blocking error (Errors tab). Non-blocking = Warnings. */
55
57
  const isBlockingError = (issue) => {
56
58
  const { rule, source, severity } = issue || {};
57
59
  if (rule === 'liquid-api-validation' || rule === 'standard-api-validation') return true;
58
- if (source === 'liquid-validator' && severity === 'error') return true;
60
+ if (source === ISSUE_SOURCES.LIQUID && severity === VALIDATION_SEVERITY.ERROR) return true;
59
61
  if (BLOCKING_ERROR_RULE_IDS.includes(rule)) return true;
60
62
  return false;
61
63
  };
@@ -20,7 +20,6 @@
20
20
  &__left {
21
21
  display: flex;
22
22
  align-items: center;
23
- gap: 1rem;
24
23
  flex-shrink: 0;
25
24
  }
26
25
 
@@ -12,17 +12,19 @@
12
12
  import React from 'react';
13
13
  import PropTypes from 'prop-types';
14
14
  import { Layout, Typography } from 'antd';
15
- import { injectIntl, intlShape } from 'react-intl';
15
+ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
16
16
 
17
17
  import CapButton from '@capillarytech/cap-ui-library/CapButton';
18
18
  import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
19
19
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
20
+ import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
21
+ import { FONT_COLOR_01, CAP_SPACE_08, CAP_SPACE_02 } from '@capillarytech/cap-ui-library/styled/variables';
20
22
 
21
23
  // Component imports
22
24
  import { useEditorContext } from '../common/EditorContext';
23
25
 
24
26
  // Constants
25
- import { HTML_EDITOR_VARIANTS } from '../../constants';
27
+ import { HTML_EDITOR_VARIANTS, LIQUID_DOC_URL } from '../../constants';
26
28
 
27
29
  // Styles
28
30
  import './_editorToolbar.scss';
@@ -66,6 +68,29 @@ const EditorToolbar = ({
66
68
  : intl.formatMessage(messages.htmlEditor)
67
69
  }
68
70
  </Text>
71
+ <CapTooltipWithInfo
72
+ title={(
73
+ <FormattedMessage
74
+ {...messages.htmlEditorTooltip}
75
+ values={{
76
+ docLink: (
77
+ <a
78
+ href={LIQUID_DOC_URL}
79
+ target="_blank"
80
+ rel="noopener noreferrer"
81
+ >
82
+ <FormattedMessage {...messages.viewDocumentation} />
83
+ </a>
84
+ ),
85
+ }}
86
+ />
87
+ )}
88
+ infoIconProps={{
89
+ style: { marginLeft: CAP_SPACE_08, color: FONT_COLOR_01 },
90
+ }}
91
+ autoAdjustOverflow
92
+ placement="top"
93
+ />
69
94
  </CapRow>
70
95
  )}
71
96
 
@@ -27,6 +27,7 @@ import messages from './messages';
27
27
  import { BLOCKING_ERROR_RULE_IDS } from '../../constants';
28
28
  import './_validationPanel.scss';
29
29
  import { SEVERITY } from './constants';
30
+ import { ISSUE_SOURCES } from '../../utils/validationConstants';
30
31
 
31
32
  const { Panel } = Collapse;
32
33
  /**
@@ -72,7 +73,7 @@ const ValidationPanel = ({
72
73
  return true;
73
74
  }
74
75
  // Client-side Liquid validation errors are blocking (genuine syntax errors)
75
- if (source === 'liquid-validator' && severity === 'error') {
76
+ if (source === ISSUE_SOURCES.LIQUID && severity === SEVERITY.ERROR) {
76
77
  return true;
77
78
  }
78
79
  // Rule Group #1 errors are blocking
@@ -246,8 +247,8 @@ const ValidationPanel = ({
246
247
  : messages[key] || { id: `htmlEditor.validation.${key}`, defaultMessage: key };
247
248
 
248
249
  const severity = isSourceGroup
249
- ? (issues.find((i) => i.severity === 'error') ? 'error'
250
- : issues.find((i) => i.severity === 'warning') ? 'warning' : 'info')
250
+ ? (issues.find((i) => i.severity === SEVERITY.ERROR) ? SEVERITY.ERROR
251
+ : issues.find((i) => i.severity === SEVERITY.WARNING) ? SEVERITY.WARNING : SEVERITY.INFO)
251
252
  : key;
252
253
 
253
254
  return (
@@ -17,11 +17,12 @@ import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
17
17
 
18
18
  // Messages
19
19
  import messages from './messages';
20
- import { BLOCKING_ERROR_RULE_IDS } from '../../constants';
20
+ import { BLOCKING_ERROR_RULE_IDS, VALIDATION_SEVERITY } from '../../constants';
21
21
 
22
22
  // Styles
23
23
  import './_validationTabs.scss';
24
24
  import { StyledCapTab } from '../../../../v2Containers/MobilePushNew/style';
25
+ import { ISSUE_SOURCES } from '../../utils/validationConstants';
25
26
 
26
27
  /**
27
28
  * Group issues into Errors (blocking) and Warnings (non-blocking)
@@ -51,7 +52,7 @@ const isBlockingError = (issue) => {
51
52
  return true;
52
53
  }
53
54
  // Client-side Liquid validation errors are blocking (genuine syntax errors)
54
- if (source === 'liquid-validator' && severity === 'error') {
55
+ if (source === ISSUE_SOURCES.LIQUID && severity === VALIDATION_SEVERITY.ERROR) {
55
56
  return true;
56
57
  }
57
58
  // Rule Group #1 errors are blocking
@@ -4,6 +4,9 @@
4
4
  * Centralized constants for the HTML Editor component
5
5
  */
6
6
 
7
+ // Documentation URL for Liquid / HTML editor
8
+ export const LIQUID_DOC_URL = 'https://docs.capillarytech.com/docs/liquid-language-in-messages';
9
+
7
10
  // HTML Editor Variants
8
11
  export const HTML_EDITOR_VARIANTS = {
9
12
  EMAIL: 'email',
@@ -10,7 +10,8 @@ import {
10
10
  } from 'react';
11
11
  import { validateHTML, extractAndValidateCSS } from '../utils/htmlValidator';
12
12
  import { sanitizeHTML, isContentSafe, findUnsafeContent } from '../utils/contentSanitizer';
13
- import { BLOCKING_ERROR_RULE_IDS } from '../constants';
13
+ import { BLOCKING_ERROR_RULE_IDS, VALIDATION_SEVERITY } from '../constants';
14
+ import { ISSUE_SOURCES } from '../utils/validationConstants';
14
15
 
15
16
  /**
16
17
  * Custom hook for managing HTML/CSS validation
@@ -341,25 +342,25 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
341
342
  const apiLiquidErrors = (apiValidationErrors?.liquidErrors || []).map((errorMessage) => {
342
343
  const extractedLine = extractLineNumberFromMessage(errorMessage);
343
344
  return {
344
- type: 'error',
345
+ type: VALIDATION_SEVERITY.ERROR,
345
346
  message: errorMessage,
346
347
  line: extractedLine,
347
348
  column: null,
348
349
  rule: 'liquid-api-validation',
349
- severity: 'error',
350
- source: 'liquid-validator',
350
+ severity: VALIDATION_SEVERITY.ERROR,
351
+ source: ISSUE_SOURCES.LIQUID,
351
352
  };
352
353
  });
353
354
 
354
355
  const apiStandardErrors = (apiValidationErrors?.standardErrors || []).map((errorMessage) => {
355
356
  const extractedLine = extractLineNumberFromMessage(errorMessage);
356
357
  return {
357
- type: 'error',
358
+ type: VALIDATION_SEVERITY.ERROR,
358
359
  message: errorMessage,
359
360
  line: extractedLine,
360
361
  column: null,
361
362
  rule: 'standard-api-validation',
362
- severity: 'error',
363
+ severity: VALIDATION_SEVERITY.ERROR,
363
364
  source: 'api-validator',
364
365
  };
365
366
  });
@@ -374,19 +375,19 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
374
375
  ? getLineAndColumnFromPosition(contentStr, issue.position)
375
376
  : { line: 1, column: 1 };
376
377
  return {
377
- type: isBlocking ? 'error' : 'warning',
378
+ type: isBlocking ? VALIDATION_SEVERITY.ERROR : VALIDATION_SEVERITY.WARNING,
378
379
  message: `Security issue: ${issue.type}`,
379
380
  line,
380
381
  column,
381
382
  rule: isBlocking ? 'sanitizer.dangerousProtocolDetected' : 'security-violation',
382
- severity: isBlocking ? 'error' : 'warning',
383
+ severity: isBlocking ? VALIDATION_SEVERITY.ERROR : VALIDATION_SEVERITY.WARNING,
383
384
  source: 'security',
384
385
  };
385
386
  });
386
387
 
387
388
  // Sanitization warnings (Rule Group #1 entries have rule set by contentSanitizer)
388
389
  const sanitizationAsIssues = (validationState.sanitizationWarnings || []).map((w) => {
389
- const sev = BLOCKING_ERROR_RULE_IDS.includes(w.rule) ? 'error' : 'warning';
390
+ const sev = BLOCKING_ERROR_RULE_IDS.includes(w.rule) ? VALIDATION_SEVERITY.ERROR : VALIDATION_SEVERITY.WARNING;
390
391
  return {
391
392
  ...w,
392
393
  severity: sev,
@@ -451,7 +452,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
451
452
 
452
453
  const protocolTypes = ['JavaScript Protocol', 'Data URL', 'VBScript Protocol'];
453
454
  // Client-side Liquid validation errors are blocking (genuine syntax errors)
454
- const hasClientSideLiquidErrors = (validationState.htmlErrors || []).some((e) => e.source === 'liquid-validator' && e.severity === 'error');
455
+ const hasClientSideLiquidErrors = (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
455
456
  const hasBlockingErrors = (validationState.sanitizationWarnings || []).some((w) => BLOCKING_ERROR_RULE_IDS.includes(w.rule)) || (validationState.securityIssues || []).some((s) => protocolTypes.includes(s?.type)) || hasApiErrors || hasClientSideLiquidErrors;
456
457
 
457
458
  return {
@@ -328,6 +328,16 @@ export default defineMessages({
328
328
  },
329
329
  },
330
330
 
331
+ htmlEditorTooltip: {
332
+ id: `${scope}.htmlEditorTooltip`,
333
+ defaultMessage: 'This editor supports standard HTML for emails. Avoid CSS frameworks, external stylesheets, and scripts. {docLink}',
334
+ },
335
+
336
+ viewDocumentation: {
337
+ id: `${scope}.viewDocumentation`,
338
+ defaultMessage: 'View documentation',
339
+ },
340
+
331
341
  // HTML Validator messages
332
342
  validator: {
333
343
  // General validation messages
@@ -7,6 +7,8 @@ import { html } from '@codemirror/lang-html';
7
7
  import { syntaxHighlighting, HighlightStyle } from '@codemirror/language';
8
8
  import { tags } from '@lezer/highlight';
9
9
  import { EditorView } from '@codemirror/view';
10
+ import { VALIDATION_SEVERITY } from '../constants';
11
+ import { ISSUE_SOURCES } from './validationConstants';
10
12
 
11
13
  /**
12
14
  * Liquid Template Syntax Patterns
@@ -145,13 +147,13 @@ export class LiquidValidator {
145
147
  } else {
146
148
  // Stray closing brace - no matching opening brace
147
149
  this.errors.push({
148
- type: 'error',
150
+ type: VALIDATION_SEVERITY.ERROR,
149
151
  message: 'Stray closing }} without matching opening {{',
150
152
  line: this.getLineNumber(html, position),
151
153
  column: 1,
152
154
  rule: 'liquid-stray-closing-output',
153
- severity: 'error',
154
- source: 'liquid-validator',
155
+ severity: VALIDATION_SEVERITY.ERROR,
156
+ source: ISSUE_SOURCES.LIQUID,
155
157
  });
156
158
  }
157
159
  }
@@ -162,13 +164,13 @@ export class LiquidValidator {
162
164
  // Report each unclosed opening brace
163
165
  stack.forEach((position) => {
164
166
  this.errors.push({
165
- type: 'error',
167
+ type: VALIDATION_SEVERITY.ERROR,
166
168
  message: 'unclosed Liquid output tag - missing }}',
167
169
  line: this.getLineNumber(html, position),
168
170
  column: 1,
169
171
  rule: 'liquid-unclosed-output',
170
- severity: 'error',
171
- source: 'liquid-validator',
172
+ severity: VALIDATION_SEVERITY.ERROR,
173
+ source: ISSUE_SOURCES.LIQUID,
172
174
  });
173
175
  });
174
176
  }
@@ -197,13 +199,13 @@ export class LiquidValidator {
197
199
  if (openTags.length > closeTags.length) {
198
200
  const unmatchedCount = openTags.length - closeTags.length;
199
201
  this.errors.push({
200
- type: 'error',
202
+ type: VALIDATION_SEVERITY.ERROR,
201
203
  message: `${unmatchedCount} unclosed Liquid logic tag(s) - missing %}`,
202
204
  line: this.getLineNumber(html, openTags[openTags.length - 1]),
203
205
  column: 1,
204
206
  rule: 'liquid-unclosed-logic',
205
- severity: 'error',
206
- source: 'liquid-validator',
207
+ severity: VALIDATION_SEVERITY.ERROR,
208
+ source: ISSUE_SOURCES.LIQUID,
207
209
  });
208
210
  }
209
211
  }
@@ -218,13 +220,13 @@ export class LiquidValidator {
218
220
  if (nestedOutput) {
219
221
  nestedOutput.forEach((match) => {
220
222
  this.errors.push({
221
- type: 'error',
223
+ type: VALIDATION_SEVERITY.ERROR,
222
224
  message: `Nested braces in Liquid output tag: ${match}`,
223
225
  line: this.getLineNumber(html, html.indexOf(match)),
224
226
  column: 1,
225
227
  rule: 'liquid-nested-braces',
226
- severity: 'error',
227
- source: 'liquid-validator',
228
+ severity: VALIDATION_SEVERITY.ERROR,
229
+ source: ISSUE_SOURCES.LIQUID,
228
230
  });
229
231
  });
230
232
  }
@@ -235,13 +237,13 @@ export class LiquidValidator {
235
237
  if (nestedLogic) {
236
238
  nestedLogic.forEach((match) => {
237
239
  this.errors.push({
238
- type: 'error',
240
+ type: VALIDATION_SEVERITY.ERROR,
239
241
  message: `Nested braces in Liquid logic tag: ${match}`,
240
242
  line: this.getLineNumber(html, html.indexOf(match)),
241
243
  column: 1,
242
244
  rule: 'liquid-nested-braces',
243
- severity: 'error',
244
- source: 'liquid-validator',
245
+ severity: VALIDATION_SEVERITY.ERROR,
246
+ source: ISSUE_SOURCES.LIQUID,
245
247
  });
246
248
  });
247
249
  }
@@ -302,23 +304,23 @@ export class LiquidValidator {
302
304
 
303
305
  if (!lastOpening) {
304
306
  this.errors.push({
305
- type: 'error',
307
+ type: VALIDATION_SEVERITY.ERROR,
306
308
  message: `Unexpected closing tag: {% ${keyword} %}`,
307
309
  line: tag.line,
308
310
  column: 1,
309
311
  rule: 'liquid-unexpected-closing',
310
- severity: 'error',
311
- source: 'liquid-validator',
312
+ severity: VALIDATION_SEVERITY.ERROR,
313
+ source: ISSUE_SOURCES.LIQUID,
312
314
  });
313
315
  } else if (lastOpening.keyword !== expectedOpening) {
314
316
  this.errors.push({
315
- type: 'error',
317
+ type: VALIDATION_SEVERITY.ERROR,
316
318
  message: `Mismatched Liquid tags: {% ${lastOpening.keyword} %} ... {% ${keyword} %}`,
317
319
  line: tag.line,
318
320
  column: 1,
319
321
  rule: 'liquid-mismatched-tags',
320
- severity: 'error',
321
- source: 'liquid-validator',
322
+ severity: VALIDATION_SEVERITY.ERROR,
323
+ source: ISSUE_SOURCES.LIQUID,
322
324
  });
323
325
  }
324
326
  }
@@ -329,13 +331,13 @@ export class LiquidValidator {
329
331
  stack.forEach(({ keyword, tag }) => {
330
332
  const expectedClosing = pairs[keyword];
331
333
  const error = {
332
- type: 'error',
334
+ type: VALIDATION_SEVERITY.ERROR,
333
335
  message: `Unclosed Liquid tag: {% ${keyword} %} - missing {% ${expectedClosing} %}`,
334
336
  line: tag.line,
335
337
  column: 1,
336
338
  rule: 'liquid-unclosed-tag',
337
- severity: 'error',
338
- source: 'liquid-validator',
339
+ severity: VALIDATION_SEVERITY.ERROR,
340
+ source: ISSUE_SOURCES.LIQUID,
339
341
  };
340
342
  this.errors.push(error);
341
343
  });
@@ -344,13 +346,13 @@ export class LiquidValidator {
344
346
  // Check for unclosed opening tags
345
347
  stack.forEach((unclosed) => {
346
348
  this.errors.push({
347
- type: 'error',
349
+ type: VALIDATION_SEVERITY.ERROR,
348
350
  message: `Unclosed Liquid tag: {% ${unclosed.keyword} %}`,
349
351
  line: unclosed.tag.line,
350
352
  column: 1,
351
353
  rule: 'liquid-unclosed-tag',
352
- severity: 'error',
353
- source: 'liquid-validator',
354
+ severity: VALIDATION_SEVERITY.ERROR,
355
+ source: ISSUE_SOURCES.LIQUID,
354
356
  });
355
357
  });
356
358
  }
@@ -364,13 +366,13 @@ export class LiquidValidator {
364
366
  if (malformedFilters) {
365
367
  malformedFilters.forEach((match) => {
366
368
  this.warnings.push({
367
- type: 'warning',
369
+ type: VALIDATION_SEVERITY.WARNING,
368
370
  message: `Malformed Liquid filter: ${match.trim()}`,
369
371
  line: this.getLineNumber(html, html.indexOf(match)),
370
372
  column: 1,
371
373
  rule: 'liquid-malformed-filter',
372
- severity: 'warning',
373
- source: 'liquid-validator',
374
+ severity: VALIDATION_SEVERITY.WARNING,
375
+ source: ISSUE_SOURCES.LIQUID,
374
376
  });
375
377
  });
376
378
  }
@@ -401,13 +403,13 @@ export class LiquidValidator {
401
403
  // Only show info for truly custom/unknown filters
402
404
  // Standard Liquid filters are now included in commonFilters list
403
405
  this.info.push({
404
- type: 'info',
406
+ type: VALIDATION_SEVERITY.INFO,
405
407
  message: `Using filter: ${filterName}`,
406
408
  line: this.getLineNumber(html, filterMatch.index),
407
409
  column: 1,
408
410
  rule: 'liquid-filter-usage',
409
- severity: 'info',
410
- source: 'liquid-validator',
411
+ severity: VALIDATION_SEVERITY.INFO,
412
+ source: ISSUE_SOURCES.LIQUID,
411
413
  });
412
414
  }
413
415
  }
@@ -422,13 +424,13 @@ export class LiquidValidator {
422
424
  if (suspiciousVariables) {
423
425
  suspiciousVariables.forEach((match) => {
424
426
  this.warnings.push({
425
- type: 'warning',
427
+ type: VALIDATION_SEVERITY.WARNING,
426
428
  message: `Potentially undefined variable: ${match}`,
427
429
  line: this.getLineNumber(html, html.indexOf(match)),
428
430
  column: 1,
429
431
  rule: 'liquid-undefined-variable',
430
- severity: 'warning',
431
- source: 'liquid-validator',
432
+ severity: VALIDATION_SEVERITY.WARNING,
433
+ source: ISSUE_SOURCES.LIQUID,
432
434
  });
433
435
  });
434
436
  }
@@ -0,0 +1,6 @@
1
+ export const DEVICE_TYPES = {
2
+ ANDROID: 'android',
3
+ IOS: 'ios',
4
+ };
5
+
6
+ export const IPHONE = 'iphone';
@@ -16,6 +16,7 @@ import { INAPP } from '../../v2Containers/App/constants';
16
16
  import { ANDROID, IOS } from '../../v2Containers/InApp/constants';
17
17
  import { getCtaObject } from '../../v2Containers/InApp/utils';
18
18
  import { CAROUSEL, VIDEO } from '../../v2Containers/MobilePushNew/constants';
19
+ import { DEVICE_TYPES, IPHONE } from './constants';
19
20
 
20
21
  class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react/prefer-stateless-function
21
22
  constructor(props) {
@@ -57,7 +58,7 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
57
58
  const isBeeFreeTemplate = get(androidContent, 'isBEEeditor') || get(iosContent, 'isBEEeditor');
58
59
  if (isBeeFreeTemplate) {
59
60
  // Normalize device to 'android' or 'ios' for comparison
60
- const normalizedDevice = device === 'iphone' ? 'ios' : device?.toLowerCase();
61
+ const normalizedDevice = device === IPHONE ? DEVICE_TYPES.IOS : device?.toLowerCase();
61
62
  const isAndroid = normalizedDevice === ANDROID.toLowerCase();
62
63
  content = {
63
64
  inAppPreviewContent: isAndroid ? androidContent?.beeHtml : iosContent?.beeHtml,
@@ -78,7 +79,7 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
78
79
  ctaData: getCtaObject(iosContent?.expandableDetails?.ctas),
79
80
  };
80
81
  // Normalize device to 'android' or 'ios' for comparison
81
- const normalizedDevice = device === 'iphone' ? 'ios' : device?.toLowerCase();
82
+ const normalizedDevice = device === IPHONE ? DEVICE_TYPES.IOS : device?.toLowerCase();
82
83
  const isAndroid = normalizedDevice === ANDROID.toLowerCase();
83
84
  content = {
84
85
  inAppPreviewContent: isAndroid ? androidPreviewContent : iosPreviewContent,
@@ -149,7 +150,7 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
149
150
 
150
151
  getPreview(device) {
151
152
  // Normalize device to 'android' or 'ios' for comparison
152
- const normalizedDevice = device === 'iphone' ? 'ios' : device?.toLowerCase();
153
+ const normalizedDevice = device === IPHONE ? DEVICE_TYPES.IOS : device?.toLowerCase();
153
154
  const deviceParam = normalizedDevice === ANDROID.toLowerCase() ? ANDROID : IOS;
154
155
  return (
155
156
  <TemplatePreview