@openlettermarketing/olc-react-sdk 2.1.6-beta.2 → 2.1.6-beta.3

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.
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const WarningIcon: (props: any) => React.JSX.Element;
3
+ export default WarningIcon;
@@ -17,6 +17,7 @@ interface DialogProps {
17
17
  isGallery?: boolean;
18
18
  currentTheme?: string | null | undefined;
19
19
  isQRCode?: boolean;
20
+ hideButtons?: boolean;
20
21
  }
21
22
  declare const DialogV2: React.FC<DialogProps>;
22
23
  export default DialogV2;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import './styles.scss';
3
+ interface FieldValidationModalProps {
4
+ open: boolean;
5
+ invalidFields: string[];
6
+ currentTheme?: string | null | undefined;
7
+ loading?: boolean;
8
+ handleClose: () => void;
9
+ handleContinue: () => void;
10
+ }
11
+ declare const FieldValidationModal: React.FC<FieldValidationModalProps>;
12
+ export default FieldValidationModal;
@@ -136,6 +136,13 @@ export declare const MESSAGES: {
136
136
  readonly DOWNLOAD_ENVELOPE_BUTTON: "Download Envelope Proof";
137
137
  readonly CANCEL_BUTTON: "Cancel";
138
138
  readonly SUBMIT_BUTTON: "Save";
139
+ readonly FIELD_VALIDATION: {
140
+ readonly TITLE: "Invalid Fields";
141
+ readonly HEADING: "The following fields are not recognized";
142
+ readonly DESCRIPTION: (fieldNames: string) => string;
143
+ readonly CONTINUE_BUTTON: "Continue Saving";
144
+ readonly CANCEL_BUTTON: "Cancel";
145
+ };
139
146
  };
140
147
  readonly QR_CODE_MODAL: {
141
148
  readonly CREATE_TITLE: "Create New QR Code";
@@ -72,3 +72,29 @@ export declare const extractCustomRistrictedBoxColor: (jsonData: any) => null;
72
72
  export declare const validateGSV: (pages: any) => boolean;
73
73
  export declare const validateEmoji: (pages: any) => boolean;
74
74
  export declare const isValidQR: (pages: any) => boolean;
75
+ /**
76
+ * Interface for invalid field validation results
77
+ */
78
+ export interface InvalidField {
79
+ field: string;
80
+ issue: 'INVALID_FORMAT' | 'NOT_UPPERCASE';
81
+ pageIndex: number;
82
+ }
83
+ /**
84
+ * Validates template fields for correct format and capitalization
85
+ * Returns array of invalid fields found in the template
86
+ *
87
+ * @param pages - Template pages array from store.toJSON()
88
+ * @returns Array of objects containing invalid fields and their issues
89
+ */
90
+ export declare const validateTemplateFields: (pages: any) => InvalidField[];
91
+ /**
92
+ * Validates that all dynamic field tokens used in the template exist in the allowed keys list.
93
+ * Scans the entire serialized template JSON for {{...}} patterns and returns any tokens
94
+ * that are not present in the provided allowedKeys list.
95
+ *
96
+ * @param templateJSON - Full template JSON object from store.toJSON()
97
+ * @param allowedKeys - Array of allowed {{...}} token strings (predefined + custom fields)
98
+ * @returns Array of unrecognized token strings; empty array means all fields are valid
99
+ */
100
+ export declare const validateAllowedTemplateFields: (templateJSON: any, allowedKeys: string[]) => string[];
@@ -38,8 +38,6 @@ interface Page {
38
38
  addElement: (element: Element) => void;
39
39
  }
40
40
  interface Store {
41
- width: number;
42
- height: number;
43
41
  pages: Page[];
44
42
  history: {
45
43
  clear: () => void;
@@ -1 +1 @@
1
- export const SDK_VERSION: "2.1.6-beta.2";
1
+ export const SDK_VERSION: "2.1.6-beta.3";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openlettermarketing/olc-react-sdk",
3
3
  "private": false,
4
- "version": "2.1.6-beta.2",
4
+ "version": "2.1.6-beta.3",
5
5
  "type": "module",
6
6
  "description": "Simplify template builder integration for any product.",
7
7
  "main": "build/index.js",
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+
3
+ const WarningIcon = (props: any) => {
4
+ const { fill = 'var(--primary-color)' } = props;
5
+
6
+ return (
7
+ <svg width="34" height="34" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
8
+ <path d="M24 4L4 44H44L24 4Z" stroke={fill} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
9
+ <path d="M24 18V28" stroke={fill} strokeWidth="2" strokeLinecap="round"/>
10
+ <circle cx="24" cy="36" r="2" fill={fill}/>
11
+ </svg>
12
+ );
13
+ };
14
+
15
+ export default WarningIcon;
@@ -27,6 +27,7 @@ interface DialogProps {
27
27
  isGallery?: boolean;
28
28
  currentTheme?: string | null | undefined;
29
29
  isQRCode?: boolean;
30
+ hideButtons?: boolean;
30
31
  }
31
32
 
32
33
  const buttonStyles: CSSProperties = {
@@ -80,7 +81,8 @@ const DialogV2: React.FC<DialogProps> = ({
80
81
  children = [],
81
82
  isGallery = false,
82
83
  currentTheme = "default",
83
- isQRCode = false
84
+ isQRCode = false,
85
+ hideButtons = false
84
86
  }) => {
85
87
  const contentAdjust = submitText.length > 6 ? "fit-content" : "100px";
86
88
 
@@ -204,7 +206,7 @@ const DialogV2: React.FC<DialogProps> = ({
204
206
  )}
205
207
  {children}
206
208
  <div className="confirm-modal-footer">
207
- {!isGallery &&
209
+ {!isGallery && !hideButtons &&
208
210
  <>
209
211
  <button onClick={onSubmit} disabled={loading}>
210
212
  {loading ? <CircularProgress style={progressStyles} /> : submitText}
@@ -223,6 +223,10 @@
223
223
  gap: 12px;
224
224
  margin-top: 16px;
225
225
 
226
+ &:empty {
227
+ margin-top: 0;
228
+ }
229
+
226
230
  button{
227
231
  min-height: 50px;
228
232
  font-size: 16px;
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+
3
+ // Utils
4
+ import { MESSAGES } from '../../../utils/message';
5
+
6
+ // Components
7
+ import Dialog from '../../GenericUIBlocks/Dialog';
8
+ import DialogV2 from '../../GenericUIBlocks/Dialog/V2';
9
+
10
+ // Icons
11
+ import Warning from '../../../assets/images/modal-icons/warning';
12
+
13
+ // Styles
14
+ import './styles.scss';
15
+
16
+ interface FieldValidationModalProps {
17
+ open: boolean;
18
+ invalidFields: string[];
19
+ currentTheme?: string | null | undefined;
20
+ loading?: boolean;
21
+ handleClose: () => void;
22
+ handleContinue: () => void;
23
+ }
24
+
25
+ const modalStyles = {
26
+ maxWidth: '600px',
27
+ minHeight: '300px',
28
+ };
29
+
30
+ const modalStylesV2 = {
31
+ maxWidth: '600px',
32
+ minHeight: '300px',
33
+ padding: '40px',
34
+ };
35
+
36
+ const FieldValidationModal: React.FC<FieldValidationModalProps> = ({
37
+ open,
38
+ invalidFields,
39
+ currentTheme,
40
+ loading = false,
41
+ handleClose,
42
+ handleContinue,
43
+ }) => {
44
+ const fieldNames = [...invalidFields].map(item => `"${item}"`).join(', ');
45
+ const description = MESSAGES.TEMPLATE.FIELD_VALIDATION.DESCRIPTION(fieldNames);
46
+
47
+ return currentTheme === 'v2' ? (
48
+ <DialogV2
49
+ icon={<Warning fill="var(--primary-color)" />}
50
+ customStyles={modalStylesV2}
51
+ open={open}
52
+ loading={loading}
53
+ handleClose={handleClose}
54
+ title={MESSAGES.TEMPLATE.FIELD_VALIDATION.TITLE}
55
+ subHeading=""
56
+ description={description}
57
+ currentTheme="v2"
58
+ isGallery={false}
59
+ onSubmit={handleContinue}
60
+ submitText={MESSAGES.TEMPLATE.FIELD_VALIDATION.CONTINUE_BUTTON}
61
+ onCancel={handleClose}
62
+ cancelText={MESSAGES.TEMPLATE.FIELD_VALIDATION.CANCEL_BUTTON}
63
+ />
64
+ ) : (
65
+ <Dialog
66
+ icon={<Warning fill="var(--primary-color)" />}
67
+ customStyles={modalStyles}
68
+ open={open}
69
+ loading={loading}
70
+ handleClose={handleClose}
71
+ title={MESSAGES.TEMPLATE.FIELD_VALIDATION.TITLE}
72
+ subHeading=""
73
+ description={description}
74
+ onSubmit={handleContinue}
75
+ submitText={MESSAGES.TEMPLATE.FIELD_VALIDATION.CONTINUE_BUTTON}
76
+ onCancel={handleClose}
77
+ cancelText={MESSAGES.TEMPLATE.FIELD_VALIDATION.CANCEL_BUTTON}
78
+ />
79
+ );
80
+ };
81
+
82
+ export default FieldValidationModal;
@@ -0,0 +1,39 @@
1
+ .field-validation-content {
2
+ margin-top: 20px;
3
+ }
4
+
5
+ .field-validation-list {
6
+ max-height: 300px;
7
+ overflow-y: auto;
8
+ padding: 10px;
9
+ background-color: #f9f9f9;
10
+ border-radius: 4px;
11
+ }
12
+
13
+ .field-validation-item {
14
+ margin-bottom: 15px;
15
+ padding: 12px;
16
+ background-color: #fff;
17
+ border-left: 3px solid #ff9800;
18
+ border-radius: 4px;
19
+
20
+ .field-name {
21
+ font-weight: 600;
22
+ color: #333;
23
+ margin-bottom: 5px;
24
+ font-family: 'Courier New', monospace;
25
+ font-size: 14px;
26
+ }
27
+
28
+ .field-error {
29
+ color: #666;
30
+ font-size: 13px;
31
+ margin-bottom: 4px;
32
+ }
33
+
34
+ .field-location {
35
+ color: #999;
36
+ font-size: 12px;
37
+ font-style: italic;
38
+ }
39
+ }
@@ -20,6 +20,7 @@ import { searchAndAdvanceChange } from '../../redux/actions/templateActions';
20
20
  import SaveTemplateModel from './SaveTemplateModel';
21
21
  import ConfirmNavigateDialog from './ConfirmNavigateDialog';
22
22
  import EditTemplateNameModel from './EditTemplateNameModel';
23
+ import FieldValidationModal from './FieldValidationModal';
23
24
 
24
25
  // Utils
25
26
  import {
@@ -31,6 +32,8 @@ import {
31
32
  removeBracketsFromRPL,
32
33
  validateEmoji,
33
34
  validateGSV,
35
+ validateTemplateFields,
36
+ validateAllowedTemplateFields,
34
37
  } from '../../utils/template-builder';
35
38
  import { addSafetyBordersToNonWindowProfessioanl } from '../../utils/templateSafetyBorders/professional';
36
39
  import { addIdentifierAreaToProfessionalNonWindow } from '../../utils/templateIdentifierArea/professional';
@@ -142,6 +145,7 @@ const TopNavigation: React.FC<TopNavigationProps> = ({
142
145
  const [downloadingProof, setDownloaingProof] = useState<boolean>(false);
143
146
  const [downloadingEnvelope, setDownloaingEnvelope] = useState<boolean>(false);
144
147
  const [templateTitle, setTemplateTitle] = useState('');
148
+ const [invalidFields, setInvalidFields] = useState<string[]>([]);
145
149
 
146
150
  const { id } = useParams<{ id: string }>();
147
151
 
@@ -572,10 +576,61 @@ const TopNavigation: React.FC<TopNavigationProps> = ({
572
576
  }
573
577
  };
574
578
 
579
+ const handleContinueAnyway = () => {
580
+ setIsShowModel((prev) => ({ ...prev, loading: true }));
581
+ handleSave();
582
+ };
583
+
575
584
  const handleChangeModel = (
576
585
  model: string = '',
577
586
  loading: string | null = null
578
587
  ) => {
588
+ // When opening the save modal, validate all {{...}} tokens against the allowed list
589
+ // and also catch any malformed/incomplete brace patterns (e.g. {{C.ZIP_COD without closing }})
590
+ if (model === 'save' && templateType === 'json') {
591
+ const tokenPattern = /\{\{[^{}]+\}\}/g;
592
+
593
+ // Flatten v2 custom field sections (each section has a .fields array)
594
+ const flattenedCustomFieldsV2 = (customFieldsV2 as any[]).length > 0
595
+ ? (customFieldsV2 as any[]).flatMap((section: { fields: any }) => section.fields)
596
+ : [];
597
+
598
+ const allAllowedFields = [
599
+ ...defaultFields,
600
+ ...customFields, // v1 custom fields from API
601
+ ...flattenedCustomFieldsV2, // v2 custom fields from API (flattened)
602
+ ...Object.values(dynamicFields),
603
+ ...defaultSenderFields,
604
+ ...defaultPropertyFields,
605
+ ...defaultMiscFields,
606
+ ];
607
+ const allowedKeys = Array.from(new Set(
608
+ allAllowedFields.flatMap((field: any) =>
609
+ (field?.key || '').match(tokenPattern) || []
610
+ )
611
+ ));
612
+
613
+ const jsonData = store.toJSON();
614
+
615
+ // Catch complete tokens not in the allowed list
616
+ const unrecognizedFields = validateAllowedTemplateFields(jsonData, allowedKeys);
617
+
618
+ // Catch malformed/incomplete patterns (e.g. {{C.ZIP_COD with no closing }})
619
+ const formatErrors = validateTemplateFields(jsonData.pages).map((f) => f.field);
620
+
621
+ const allInvalid = Array.from(new Set([...unrecognizedFields, ...formatErrors]));
622
+
623
+ if (allInvalid.length > 0) {
624
+ setInvalidFields(allInvalid);
625
+ setIsShowModel({
626
+ open: true,
627
+ model: 'field-validation',
628
+ loading: false,
629
+ });
630
+ return;
631
+ }
632
+ }
633
+
579
634
  setIsShowModel((prev) => ({
580
635
  ...prev,
581
636
  open: !prev.open,
@@ -913,6 +968,17 @@ const TopNavigation: React.FC<TopNavigationProps> = ({
913
968
  currentTheme={currentTheme}
914
969
  />
915
970
  )}
971
+ {/* Field Validation Modal */}
972
+ {isShowModel.open && isShowModel.model === 'field-validation' && (
973
+ <FieldValidationModal
974
+ open={isShowModel.open}
975
+ invalidFields={invalidFields}
976
+ loading={isShowModel.loading}
977
+ currentTheme={currentTheme}
978
+ handleClose={() => handleChangeModel()}
979
+ handleContinue={handleContinueAnyway}
980
+ />
981
+ )}
916
982
  {/* Duplicate Template Modal */}
917
983
  <DuplicateTemplateModal
918
984
  open={isShowModel.open && isShowModel.model === 'duplicate'}
@@ -142,7 +142,14 @@ export const MESSAGES = {
142
142
  DOWNLOAD_PROOF_BUTTON: "Download Mailer Proof",
143
143
  DOWNLOAD_ENVELOPE_BUTTON: "Download Envelope Proof",
144
144
  CANCEL_BUTTON: "Cancel",
145
- SUBMIT_BUTTON: "Save"
145
+ SUBMIT_BUTTON: "Save",
146
+ FIELD_VALIDATION: {
147
+ TITLE: "Invalid Fields",
148
+ HEADING: "The following fields are not recognized",
149
+ DESCRIPTION: (fieldNames: string) => `The following field(s) ${fieldNames} are not recognized.`,
150
+ CONTINUE_BUTTON: "Continue Saving",
151
+ CANCEL_BUTTON: "Cancel",
152
+ },
146
153
  },
147
154
  QR_CODE_MODAL: {
148
155
  CREATE_TITLE: "Create New QR Code",
@@ -121,7 +121,7 @@ export const Products: Product[] = [
121
121
  size: [
122
122
  {
123
123
  id: "11",
124
- size: "11x8.5",
124
+ size: "12x9",
125
125
  label: "8.5x11"
126
126
  }
127
127
  ]
@@ -84,10 +84,10 @@ export const selfMailerProduct = {
84
84
  ],
85
85
  },
86
86
  {
87
- id: "11", defaultSize: "11x8.5", title: "Self Mailer", productType: "Tri-Fold Self-Mailers", hasEnvelope: false, paper: '80# text', label: '8.5x11',
87
+ id: "11", defaultSize: "12x9", title: "Self Mailer", productType: "Tri-Fold Self-Mailers", hasEnvelope: false, paper: '80# text', label: '8.5x11',
88
88
  finish: 'Glass Coated', size: [{
89
89
  id: "11",
90
- size: "11x8.5",
90
+ size: "12x9",
91
91
  label: "8.5x11"
92
92
  }]
93
93
  },
@@ -325,4 +325,106 @@ export const isValidQR = (pages: any) => {
325
325
  }
326
326
  }
327
327
  return true;
328
- }
328
+ }
329
+
330
+ /**
331
+ * Interface for invalid field validation results
332
+ */
333
+ export interface InvalidField {
334
+ field: string;
335
+ issue: 'INVALID_FORMAT' | 'NOT_UPPERCASE';
336
+ pageIndex: number;
337
+ }
338
+
339
+ /**
340
+ * Validates template fields for correct format and capitalization
341
+ * Returns array of invalid fields found in the template
342
+ *
343
+ * @param pages - Template pages array from store.toJSON()
344
+ * @returns Array of objects containing invalid fields and their issues
345
+ */
346
+ export const validateTemplateFields = (pages: any): InvalidField[] => {
347
+ const invalidFields: InvalidField[] = [];
348
+
349
+ pages.forEach((page: any, pageIndex: number) => {
350
+ page.children?.forEach((child: any) => {
351
+ if (child.type === 'text' && child.text) {
352
+ const text = child.text;
353
+
354
+ // ── Step 1 - Find ALL complete brace expressions: anything from the first { to the last } on the same "group", including spaces inside.
355
+ const completePattern = /\{+[^{}]*\}+/g;
356
+ let match: RegExpExecArray | null;
357
+
358
+ // Track which character ranges are already covered so Steps 2 & 3 - don't double-report sub-strings of a complete match.
359
+ const coveredRanges: Array<[number, number]> = [];
360
+
361
+ const completeExec = new RegExp(completePattern.source, 'g');
362
+ while ((match = completeExec.exec(text)) !== null) {
363
+ coveredRanges.push([match.index, match.index + match[0].length]);
364
+ const token = match[0];
365
+ // Valid: EXACTLY {{UPPERCASE_CONTENT}} — no spaces, no lowercase
366
+ const isValidFormat = /^\{\{[A-Z0-9._]+\}\}$/.test(token);
367
+
368
+ if (!isValidFormat) {
369
+ const content = token.replace(/^\{+|\}+$/g, '');
370
+ const isDoubleBrace = /^\{\{[\s\S]+\}\}$/.test(token);
371
+ const hasOnlyValidChars = /^[A-Za-z0-9._]+$/.test(content);
372
+ if (isDoubleBrace && hasOnlyValidChars && content !== content.toUpperCase()) {
373
+ // e.g. {{c.last_name}} — correct braces but lowercase content
374
+ invalidFields.push({ field: token, issue: 'NOT_UPPERCASE', pageIndex });
375
+ } else {
376
+ invalidFields.push({ field: token, issue: 'INVALID_FORMAT', pageIndex });
377
+ }
378
+ }
379
+ }
380
+
381
+ // Helper: returns true if the character at `idx` is inside a covered range
382
+ const isCovered = (idx: number): boolean =>
383
+ coveredRanges.some(([s, e]) => idx >= s && idx < e);
384
+
385
+ // ── Step 2 ─ Find incomplete patterns that have opening brace(s) but NO closing brace.e.g. "{C.LAST_NAME" or "{{C.LAST_NAME" at end of text.
386
+ const missingClosingExec = /\{+[^{}]*/g;
387
+ while ((match = missingClosingExec.exec(text)) !== null) {
388
+ if (!isCovered(match.index) && match[0].trim().length > 0) {
389
+ invalidFields.push({ field: match[0], issue: 'INVALID_FORMAT', pageIndex });
390
+ }
391
+ }
392
+
393
+ // ── Step 3 ─ Find incomplete patterns that have closing brace(s) but NO opening brace. e.g. "C.LAST_NAME}" or "C.LAST_NAME}}".
394
+ const missingOpeningExec = /(?<!\{)[A-Z0-9][A-Z0-9._\s]*\}+/g;
395
+ while ((match = missingOpeningExec.exec(text)) !== null) {
396
+ if (!isCovered(match.index) && match[0].trim().length > 0) {
397
+ invalidFields.push({ field: match[0].trim(), issue: 'INVALID_FORMAT', pageIndex });
398
+ }
399
+ }
400
+ }
401
+ });
402
+ });
403
+
404
+ // Remove duplicates
405
+ const uniqueFields = invalidFields.filter((item: InvalidField, index: number, self: InvalidField[]) =>
406
+ index === self.findIndex((t: InvalidField) => t.field === item.field && t.issue === item.issue)
407
+ );
408
+
409
+ return uniqueFields;
410
+ };
411
+
412
+ /**
413
+ * Validates that all dynamic field tokens used in the template exist in the allowed keys list.
414
+ * Scans the entire serialized template JSON for {{...}} patterns and returns any tokens
415
+ * that are not present in the provided allowedKeys list.
416
+ *
417
+ * @param templateJSON - Full template JSON object from store.toJSON()
418
+ * @param allowedKeys - Array of allowed {{...}} token strings (predefined + custom fields)
419
+ * @returns Array of unrecognized token strings; empty array means all fields are valid
420
+ */
421
+ export const validateAllowedTemplateFields = (
422
+ templateJSON: any,
423
+ allowedKeys: string[]
424
+ ): string[] => {
425
+ const templateStr = JSON.stringify(templateJSON);
426
+ const pattern = /\{\{[^{}]+\}\}/g;
427
+ const matches = templateStr.match(pattern) || [];
428
+ const unique = Array.from(new Set(matches));
429
+ return unique.filter(token => !allowedKeys.includes(token));
430
+ };
@@ -1,5 +1,3 @@
1
- import { DPI } from "../constants";
2
-
3
1
  // Define the type for the element being added
4
2
  interface Element {
5
3
  id: string;
@@ -45,8 +43,6 @@ interface Page {
45
43
 
46
44
  // Define the type for the store
47
45
  interface Store {
48
- width: number;
49
- height: number;
50
46
  pages: Page[];
51
47
  history: {
52
48
  clear: () => void;
@@ -94,30 +90,16 @@ const createTextElement = (
94
90
 
95
91
  export const addIdentifierAreaToTriFold = (store: Store): void => {
96
92
  const page = store.pages[0];
97
- const foldSectionHeight = store.height / 3;
98
- const restrictedAreaHeight = 2.375 * DPI;
99
- const restrictedAreaY = store.height - restrictedAreaHeight - 0.125 * DPI - foldSectionHeight;
100
- const identifierY = restrictedAreaY + restrictedAreaHeight - 12;
101
-
102
- // Keep the original right-side alignment behavior, but make it responsive to page width.
103
- const contPlaceholderX = store.width - 185;
104
- const contIdX = store.width - 177;
105
- const sequencePlaceholderX = store.width - 149;
106
- const sequenceX = store.width - 140;
107
- const templatePlaceholderX = store.width - 114;
108
- const templateIdX = store.width - 101.4;
109
- const orderPlaceholderX = store.width - 72.4;
110
- const orderIdX = store.width - 61.4;
111
93
 
112
94
  const elements: Element[] = [
113
- createTextElement("cont-id-placeholder", "C:", contPlaceholderX, identifierY, 11),
114
- createTextElement("contId", "0000001", contIdX, identifierY, 30),
115
- createTextElement("sequence-id-placeholder", "| S:", sequencePlaceholderX, identifierY, 12),
116
- createTextElement("sequence", "0000001", sequenceX, identifierY, 29),
117
- createTextElement("template-id-placeholder", "| T:", templatePlaceholderX, identifierY, 14),
118
- createTextElement("templateId", "0001034", templateIdX, identifierY, 29),
119
- createTextElement("order-id-placeholder", "| O:", orderPlaceholderX, identifierY, 11),
120
- createTextElement("orderId", "0000127", orderIdX, identifierY, 29),
95
+ createTextElement("cont-id-placeholder", "C:", 679, 739, 11),
96
+ createTextElement("contId", "0000001", 687, 739, 30),
97
+ createTextElement("sequence-id-placeholder", "| S:", 715, 739, 12),
98
+ createTextElement("sequence", "0000001", 724, 739, 29),
99
+ createTextElement("template-id-placeholder", "| T:", 750, 739, 14),
100
+ createTextElement("templateId", "0001034", 762.6, 739, 29),
101
+ createTextElement("order-id-placeholder", "| O:", 791.6, 739, 11),
102
+ createTextElement("orderId", "0000127", 802.6, 739, 29),
121
103
  ];
122
104
 
123
105
  elements.forEach(element => page.addElement(element));
@@ -85,8 +85,6 @@ interface Store {
85
85
 
86
86
  export const addRestrictedAreaToTriFold = (store: Store, size: [number, number], barcodeSrc: string): void => {
87
87
  const page = store.pages[0];
88
- const foldSectionHeight = store.height / 3;
89
- const indiciaLeftShift = 8;
90
88
 
91
89
  const elements: Element[] = [
92
90
  {
@@ -100,7 +98,7 @@ export const addRestrictedAreaToTriFold = (store: Store, size: [number, number],
100
98
  alwaysOnTop: true,
101
99
  showInExport: true,
102
100
  x: store.width - size[0] * DPI - 0.15 * DPI,
103
- y: store.height - size[1] * DPI - 0.125 * DPI - foldSectionHeight,
101
+ y: store.height - size[1] * DPI - 0.125 * DPI - 4 * DPI,
104
102
  width: size[0] * DPI,
105
103
  height: size[1] * DPI,
106
104
  rotation: 0,
@@ -139,7 +137,7 @@ export const addRestrictedAreaToTriFold = (store: Store, size: [number, number],
139
137
  alwaysOnTop: true,
140
138
  showInExport: true,
141
139
  x: store.width - size[0] * DPI - 0.15 * DPI + 5,
142
- y: store.height - size[1] * DPI - 0.125 * DPI + 20 - foldSectionHeight,
140
+ y: store.height - size[1] * DPI - 0.125 * DPI + 20 - 4 * DPI,
143
141
  width: 240,
144
142
  height: 20,
145
143
  rotation: 0,
@@ -190,8 +188,8 @@ export const addRestrictedAreaToTriFold = (store: Store, size: [number, number],
190
188
  removable: false,
191
189
  alwaysOnTop: true,
192
190
  showInExport: true,
193
- x: store.width - 111 - 0.15 * DPI - indiciaLeftShift,
194
- y: store.height - size[1] * DPI + 10 - foldSectionHeight,
191
+ x: store.width - 111 - 0.15 * DPI,
192
+ y: store.height - size[1] * DPI + 10 - 4 * DPI,
195
193
  width: 111,
196
194
  height: 40,
197
195
  rotation: 0,
@@ -242,7 +240,7 @@ export const addRestrictedAreaToTriFold = (store: Store, size: [number, number],
242
240
  removable: false,
243
241
  alwaysOnTop: true,
244
242
  showInExport: true,
245
- y: store.height - 28 - size[1] * DPI + 100 - foldSectionHeight,
243
+ y: store.height - 28 - size[1] * DPI + 100 - 4 * DPI,
246
244
  x: store.width - size[0] * DPI - 0.15 * DPI + 5,
247
245
  width: size[0] * DPI - 23,
248
246
  height: 15,
@@ -284,7 +282,7 @@ export const addRestrictedAreaToTriFold = (store: Store, size: [number, number],
284
282
  alwaysOnTop: true,
285
283
  showInExport: true,
286
284
  x: store.width - size[0] * DPI - 0.15 * DPI + 5,
287
- y: store.height - size[1] * DPI - 0.125 * DPI + 120 - foldSectionHeight,
285
+ y: store.height - size[1] * DPI - 0.125 * DPI + 120 - 4 * DPI,
288
286
  width: 240,
289
287
  height: 20,
290
288
  rotation: 0,