@nfcard/validation 0.22.2 → 0.24.0

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { z } from "zod";
2
+ import { z as z2 } from "zod";
3
3
  import {
4
4
  isTipProviderId,
5
5
  normalizeTipHandle,
@@ -448,6 +448,180 @@ function elementPrintability(el, ctx) {
448
448
  return out;
449
449
  }
450
450
 
451
+ // src/profileLayout.ts
452
+ import { z } from "zod";
453
+ import {
454
+ MAX_DISTINCT_OBJECT_FONTS,
455
+ MAX_FREE_TEXT_LENGTH,
456
+ MAX_LAYOUT_OBJECT_OVERRIDES,
457
+ MAX_OBJECT_ORDER_LENGTH,
458
+ MAX_OWNED_OBJECTS,
459
+ FREE_TEXT_ID_RE,
460
+ PROFILE_SECTIONS,
461
+ PROFILE_SECTION_IDS,
462
+ objectKindOf
463
+ } from "@nfcard/types";
464
+ var HEX_RE = /^#[0-9A-Fa-f]{6}$/;
465
+ var OBJECT_ID_SHAPE_RE = /^[A-Za-z0-9_-]{1,32}$/;
466
+ var textObjectStyleSchema = z.object({
467
+ color: z.string().regex(HEX_RE).optional(),
468
+ sizeStep: z.enum(["sm", "md", "lg"]).optional(),
469
+ align: z.enum(["start", "center"]).optional(),
470
+ weight: z.enum(["normal", "bold"]).optional(),
471
+ italic: z.boolean().optional(),
472
+ underline: z.boolean().optional(),
473
+ // Resolved through the api's font map at render; an unknown key falls
474
+ // back to the template pairing, so membership is not validated here.
475
+ fontKey: z.string().min(1).max(64).regex(/^[A-Za-z0-9-]+$/).optional()
476
+ }).strict();
477
+ var imageObjectStyleSchema = z.object({
478
+ crop: z.object({
479
+ x: z.number().min(0).max(1),
480
+ y: z.number().min(0).max(1),
481
+ zoom: z.number().min(1).max(3)
482
+ }).strict().optional(),
483
+ borderColor: z.string().regex(HEX_RE).optional(),
484
+ borderPx: z.number().int().min(0).max(8).optional(),
485
+ shape: z.enum(["circle", "rounded", "square"]).optional()
486
+ }).strict();
487
+ var buttonObjectStyleSchema = z.object({
488
+ bg: z.string().regex(HEX_RE).optional(),
489
+ variant: z.enum(["solid", "soft", "outline"]).optional()
490
+ }).strict();
491
+ var STYLE_SCHEMA_FOR_KIND = {
492
+ text: textObjectStyleSchema,
493
+ freeText: textObjectStyleSchema,
494
+ image: imageObjectStyleSchema,
495
+ ctaButton: buttonObjectStyleSchema,
496
+ linkOut: buttonObjectStyleSchema,
497
+ action: buttonObjectStyleSchema
498
+ };
499
+ var orderListSchema = z.array(z.string().regex(OBJECT_ID_SHAPE_RE)).max(MAX_OBJECT_ORDER_LENGTH);
500
+ var freeTextObjectSchema = z.object({
501
+ id: z.string().regex(FREE_TEXT_ID_RE),
502
+ kind: z.literal("freeText"),
503
+ section: z.enum(PROFILE_SECTION_IDS),
504
+ text: z.string().max(2e3).transform((v) => sanitizeText(v, MAX_FREE_TEXT_LENGTH))
505
+ }).strict();
506
+ var profileLayoutSchema = z.object({
507
+ v: z.literal(1),
508
+ sectionOrder: z.array(z.enum(PROFILE_SECTION_IDS)).max(PROFILE_SECTION_IDS.length).optional(),
509
+ objectOrder: z.object(
510
+ Object.fromEntries(
511
+ PROFILE_SECTION_IDS.map((id) => [id, orderListSchema.optional()])
512
+ )
513
+ ).strict().partial().optional(),
514
+ objects: z.record(z.string().regex(OBJECT_ID_SHAPE_RE), z.unknown()).optional(),
515
+ ownedObjects: z.array(freeTextObjectSchema).max(MAX_OWNED_OBJECTS).optional()
516
+ }).strict().superRefine((layout, ctx) => {
517
+ if (layout.sectionOrder) {
518
+ const seen = new Set(layout.sectionOrder);
519
+ if (seen.size !== layout.sectionOrder.length) {
520
+ ctx.addIssue({
521
+ code: z.ZodIssueCode.custom,
522
+ path: ["sectionOrder"],
523
+ message: "duplicate section id"
524
+ });
525
+ }
526
+ }
527
+ if (layout.objects) {
528
+ const keys = Object.keys(layout.objects);
529
+ if (keys.length > MAX_LAYOUT_OBJECT_OVERRIDES) {
530
+ ctx.addIssue({
531
+ code: z.ZodIssueCode.custom,
532
+ path: ["objects"],
533
+ message: `more than ${MAX_LAYOUT_OBJECT_OVERRIDES} overrides`
534
+ });
535
+ return;
536
+ }
537
+ const fonts = /* @__PURE__ */ new Set();
538
+ for (const key of keys) {
539
+ const kind = objectKindOf(key);
540
+ if (!kind) {
541
+ ctx.addIssue({
542
+ code: z.ZodIssueCode.custom,
543
+ path: ["objects", key],
544
+ message: "unknown object id"
545
+ });
546
+ continue;
547
+ }
548
+ const styleSchema = STYLE_SCHEMA_FOR_KIND[kind];
549
+ const override = layout.objects[key];
550
+ const overrideParsed = z.object({ style: z.unknown().optional() }).strict().safeParse(override);
551
+ if (!overrideParsed.success) {
552
+ ctx.addIssue({
553
+ code: z.ZodIssueCode.custom,
554
+ path: ["objects", key],
555
+ message: "override must be { style? }"
556
+ });
557
+ continue;
558
+ }
559
+ const style = overrideParsed.data.style;
560
+ if (style === void 0) continue;
561
+ if (!styleSchema) {
562
+ ctx.addIssue({
563
+ code: z.ZodIssueCode.custom,
564
+ path: ["objects", key, "style"],
565
+ message: `kind '${kind}' is not stylable`
566
+ });
567
+ continue;
568
+ }
569
+ const parsed = styleSchema.safeParse(style);
570
+ if (!parsed.success) {
571
+ for (const issue of parsed.error.issues) {
572
+ ctx.addIssue({
573
+ code: z.ZodIssueCode.custom,
574
+ path: ["objects", key, "style", ...issue.path],
575
+ message: issue.message
576
+ });
577
+ }
578
+ continue;
579
+ }
580
+ const fontKey = parsed.data.fontKey;
581
+ if (fontKey) fonts.add(fontKey);
582
+ }
583
+ if (fonts.size > MAX_DISTINCT_OBJECT_FONTS) {
584
+ ctx.addIssue({
585
+ code: z.ZodIssueCode.custom,
586
+ path: ["objects"],
587
+ message: `more than ${MAX_DISTINCT_OBJECT_FONTS} distinct fonts`
588
+ });
589
+ }
590
+ }
591
+ if (layout.ownedObjects) {
592
+ const ids = /* @__PURE__ */ new Set();
593
+ layout.ownedObjects.forEach((obj, i) => {
594
+ if (ids.has(obj.id)) {
595
+ ctx.addIssue({
596
+ code: z.ZodIssueCode.custom,
597
+ path: ["ownedObjects", i, "id"],
598
+ message: "duplicate freeText id"
599
+ });
600
+ }
601
+ ids.add(obj.id);
602
+ if (!PROFILE_SECTIONS[obj.section].hosts.includes("freeText")) {
603
+ ctx.addIssue({
604
+ code: z.ZodIssueCode.custom,
605
+ path: ["ownedObjects", i, "section"],
606
+ message: `section '${obj.section}' does not host freeText`
607
+ });
608
+ }
609
+ if (obj.text.length === 0) {
610
+ ctx.addIssue({
611
+ code: z.ZodIssueCode.custom,
612
+ path: ["ownedObjects", i, "text"],
613
+ message: "empty after sanitisation"
614
+ });
615
+ }
616
+ });
617
+ }
618
+ });
619
+ function safeProfileLayout(value) {
620
+ if (value === void 0 || value === null) return null;
621
+ const parsed = profileLayoutSchema.safeParse(value);
622
+ return parsed.success ? parsed.data : null;
623
+ }
624
+
451
625
  // src/index.ts
452
626
  var WEB_HOSTNAME_RE = /^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i;
453
627
  function isWebAddress(value) {
@@ -464,7 +638,7 @@ function isWebAddress(value) {
464
638
  const hostPart = value.split(/[/?#]/, 1)[0];
465
639
  return WEB_HOSTNAME_RE.test(hostPart);
466
640
  }
467
- var webAddressSchema = z.string().refine(isWebAddress, "invalidUrl").optional().or(z.literal(""));
641
+ var webAddressSchema = z2.string().refine(isWebAddress, "invalidUrl").optional().or(z2.literal(""));
468
642
  function stripUnsafeChars(s) {
469
643
  return s.replace(/[-<>"'`\\]/g, "").trim();
470
644
  }
@@ -489,8 +663,8 @@ function sanitizeText(raw, max = 50) {
489
663
  const noTags = raw.replace(/<[^>]*>/g, "").replace(/[-]/g, "");
490
664
  return noTags.trim().slice(0, max);
491
665
  }
492
- var secureUrl = z.string().max(2048).optional().or(z.literal("")).transform((v) => sanitizeHttpUrl(v));
493
- var socialLinksSchema = z.object({
666
+ var secureUrl = z2.string().max(2048).optional().or(z2.literal("")).transform((v) => sanitizeHttpUrl(v));
667
+ var socialLinksSchema = z2.object({
494
668
  facebook: secureUrl,
495
669
  linkedin: secureUrl,
496
670
  instagram: secureUrl,
@@ -498,10 +672,10 @@ var socialLinksSchema = z.object({
498
672
  youtube: secureUrl,
499
673
  tiktok: secureUrl,
500
674
  customUrl: secureUrl,
501
- customUrlLabel: z.string().max(80).optional().or(z.literal("")).transform((v) => sanitizeText(v ?? "", 50))
675
+ customUrlLabel: z2.string().max(80).optional().or(z2.literal("")).transform((v) => sanitizeText(v ?? "", 50))
502
676
  });
503
- var overrideUrl = z.string().max(2048).transform((v) => sanitizeHttpUrl(v)).optional();
504
- var socialOverridesSchema = z.object({
677
+ var overrideUrl = z2.string().max(2048).transform((v) => sanitizeHttpUrl(v)).optional();
678
+ var socialOverridesSchema = z2.object({
505
679
  facebook: overrideUrl,
506
680
  linkedin: overrideUrl,
507
681
  instagram: overrideUrl,
@@ -512,47 +686,47 @@ var socialOverridesSchema = z.object({
512
686
  youtube: overrideUrl,
513
687
  tiktok: overrideUrl,
514
688
  customUrl: overrideUrl,
515
- customUrlLabel: z.string().max(100).transform((v) => sanitizeText(v, 80)).optional()
689
+ customUrlLabel: z2.string().max(100).transform((v) => sanitizeText(v, 80)).optional()
516
690
  });
517
- var userDataSchema = z.object({
691
+ var userDataSchema = z2.object({
518
692
  // First/last name became optional — visitors can save with just an
519
693
  // email and fill names in later. Format guards still apply once
520
694
  // filled.
521
- firstName: z.string().max(100).optional().or(z.literal("")),
522
- lastName: z.string().max(100).optional().or(z.literal("")),
523
- position: z.string().max(100).optional().or(z.literal("")),
524
- email: z.string().email("invalidEmail"),
525
- phone: z.string().regex(/^[+]?[\d\s\-().]{6,20}$/, "invalidPhone").optional().or(z.literal("")),
695
+ firstName: z2.string().max(100).optional().or(z2.literal("")),
696
+ lastName: z2.string().max(100).optional().or(z2.literal("")),
697
+ position: z2.string().max(100).optional().or(z2.literal("")),
698
+ email: z2.string().email("invalidEmail"),
699
+ phone: z2.string().regex(/^[+]?[\d\s\-().]{6,20}$/, "invalidPhone").optional().or(z2.literal("")),
526
700
  // ISO 3166-1 alpha-2 country code paired with `phone`. Persisted alongside
527
701
  // the local number so the digital profile renderer (and the VCF generator)
528
702
  // can prepend the correct E.164 calling prefix, e.g. `+36 20 123 4567`.
529
703
  // Must be part of the base schema — Zod strips unknown keys in strict mode,
530
704
  // which would otherwise silently drop the ISO code on wizard submit and
531
705
  // leave every brand-new profile without a country prefix.
532
- phoneCountryCode: z.string().max(5).optional().or(z.literal("")),
533
- location: z.string().max(200).optional().or(z.literal("")),
534
- company: z.string().max(100).optional().or(z.literal("")),
706
+ phoneCountryCode: z2.string().max(5).optional().or(z2.literal("")),
707
+ location: z2.string().max(200).optional().or(z2.literal("")),
708
+ company: z2.string().max(100).optional().or(z2.literal("")),
535
709
  // Stored scheme-less (`www.foxhome.hu`); see webAddressSchema above.
536
710
  website: webAddressSchema,
537
711
  // FOXHOLE — promoted from digitalConfig.contentOverrides.bio when
538
712
  // the digital text-override layer was retired.
539
- bio: z.string().max(500).optional().or(z.literal("")),
540
- profilePhotoUrl: z.string().url().optional().or(z.literal("")),
541
- companyLogoUrl: z.string().url().optional().or(z.literal("")),
713
+ bio: z2.string().max(500).optional().or(z2.literal("")),
714
+ profilePhotoUrl: z2.string().url().optional().or(z2.literal("")),
715
+ companyLogoUrl: z2.string().url().optional().or(z2.literal("")),
542
716
  socialLinks: socialLinksSchema.optional()
543
717
  });
544
- var cardElementsSchema = z.object({
545
- showName: z.boolean(),
546
- showPosition: z.boolean(),
547
- showEmail: z.boolean(),
548
- showPhone: z.boolean(),
549
- showCompany: z.boolean(),
550
- showLogo: z.boolean(),
551
- showQr: z.boolean(),
552
- showPhoto: z.boolean()
718
+ var cardElementsSchema = z2.object({
719
+ showName: z2.boolean(),
720
+ showPosition: z2.boolean(),
721
+ showEmail: z2.boolean(),
722
+ showPhone: z2.boolean(),
723
+ showCompany: z2.boolean(),
724
+ showLogo: z2.boolean(),
725
+ showQr: z2.boolean(),
726
+ showPhoto: z2.boolean()
553
727
  });
554
- var materialSchema = z.enum(["plastic", "3dprint_standard", "metal", "bamboo"]);
555
- var comboIdSchema = z.enum([
728
+ var materialSchema = z2.enum(["plastic", "3dprint_standard", "metal", "bamboo"]);
729
+ var comboIdSchema = z2.enum([
556
730
  "A1",
557
731
  "A2",
558
732
  "B1",
@@ -564,154 +738,154 @@ var comboIdSchema = z.enum([
564
738
  "E1",
565
739
  "E2"
566
740
  ]);
567
- var contactDetailTogglesSchema = z.object({
568
- showEmail: z.boolean().optional(),
569
- showPhone: z.boolean().optional(),
570
- showLocation: z.boolean().optional()
741
+ var contactDetailTogglesSchema = z2.object({
742
+ showEmail: z2.boolean().optional(),
743
+ showPhone: z2.boolean().optional(),
744
+ showLocation: z2.boolean().optional()
571
745
  });
572
- var physicalContentOverridesSchema = z.object({
573
- displayName: z.string().max(100).optional().or(z.literal("")),
574
- company: z.string().max(100).optional().or(z.literal("")),
575
- position: z.string().max(200).optional().or(z.literal("")),
576
- email: z.string().email().optional().or(z.literal("")),
577
- phone: z.string().max(30).optional().or(z.literal("")),
746
+ var physicalContentOverridesSchema = z2.object({
747
+ displayName: z2.string().max(100).optional().or(z2.literal("")),
748
+ company: z2.string().max(100).optional().or(z2.literal("")),
749
+ position: z2.string().max(200).optional().or(z2.literal("")),
750
+ email: z2.string().email().optional().or(z2.literal("")),
751
+ phone: z2.string().max(30).optional().or(z2.literal("")),
578
752
  // Paired ISO code so the override phone can still get a `+XX ` prefix.
579
753
  // See ContentOverrides.phoneCountryCode in @nfcard/types.
580
- phoneCountryCode: z.string().max(5).optional().or(z.literal("")),
581
- location: z.string().max(200).optional().or(z.literal(""))
754
+ phoneCountryCode: z2.string().max(5).optional().or(z2.literal("")),
755
+ location: z2.string().max(200).optional().or(z2.literal(""))
582
756
  });
583
- var hexColorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/, "invalidHexColour");
584
- var cellSidesSchema = z.number().int().refine((s) => s === 0 || s >= 3 && s <= 8, { message: "cellSidesRange" });
585
- var chipSidesSchema = z.number().int().refine((s) => s === -1 || s === 0 || s >= 3 && s <= 8, { message: "cellSidesRange" });
586
- var chipAxisSchema = z.union([z.literal(0), z.literal(1), z.literal(2)]);
587
- var cardDesignMaterialSchema = z.enum(["3dprint_standard", "metal", "bamboo"]);
588
- var patternFamilySchema = z.enum(["radial", "gradient", "wave", "spiral"]);
589
- var cardDesignPatternSchema = z.object({
757
+ var hexColorSchema = z2.string().regex(/^#[0-9A-Fa-f]{6}$/, "invalidHexColour");
758
+ var cellSidesSchema = z2.number().int().refine((s) => s === 0 || s >= 3 && s <= 8, { message: "cellSidesRange" });
759
+ var chipSidesSchema = z2.number().int().refine((s) => s === -1 || s === 0 || s >= 3 && s <= 8, { message: "cellSidesRange" });
760
+ var chipAxisSchema = z2.union([z2.literal(0), z2.literal(1), z2.literal(2)]);
761
+ var cardDesignMaterialSchema = z2.enum(["3dprint_standard", "metal", "bamboo"]);
762
+ var patternFamilySchema = z2.enum(["radial", "gradient", "wave", "spiral"]);
763
+ var cardDesignPatternSchema = z2.object({
590
764
  family: patternFamilySchema,
591
- variant: z.enum(["pulse", "bell"]),
592
- density: z.number().min(0).max(1),
593
- holeMinMm: z.number().positive(),
594
- holeMaxMm: z.number().positive(),
595
- intensity: z.number().min(0).max(1),
765
+ variant: z2.enum(["pulse", "bell"]),
766
+ density: z2.number().min(0).max(1),
767
+ holeMinMm: z2.number().positive(),
768
+ holeMaxMm: z2.number().positive(),
769
+ intensity: z2.number().min(0).max(1),
596
770
  cellShape: cellSidesSchema,
597
- cellRotationDeg: z.number(),
771
+ cellRotationDeg: z2.number(),
598
772
  chipShape: chipSidesSchema,
599
- chipRotationDeg: z.number()
773
+ chipRotationDeg: z2.number()
600
774
  }).refine((p) => p.holeMinMm <= p.holeMaxMm, { message: "holeMinGtMax", path: ["holeMinMm"] });
601
- var elementTransformSchema = z.object({
602
- xMm: z.number(),
603
- yMm: z.number(),
604
- rotationDeg: z.number(),
605
- scale: z.number().positive(),
606
- z: z.number()
775
+ var elementTransformSchema = z2.object({
776
+ xMm: z2.number(),
777
+ yMm: z2.number(),
778
+ rotationDeg: z2.number(),
779
+ scale: z2.number().positive(),
780
+ z: z2.number()
607
781
  });
608
- var elementRoleSchema = z.enum(["text", "accent"]);
782
+ var elementRoleSchema = z2.enum(["text", "accent"]);
609
783
  var baseElementFields = {
610
- id: z.string().min(1),
784
+ id: z2.string().min(1),
611
785
  transform: elementTransformSchema,
612
- face: z.enum(["front", "back"]).optional(),
613
- locked: z.boolean().optional(),
614
- colour: z.string().optional()
786
+ face: z2.enum(["front", "back"]).optional(),
787
+ locked: z2.boolean().optional(),
788
+ colour: z2.string().optional()
615
789
  };
616
- var qrModeSchema = z.enum(["redirect", "raw"]).optional();
617
- var cardElementSchema = z.discriminatedUnion("type", [
618
- z.object({ ...baseElementFields, type: z.literal("chip"), shape: chipSidesSchema }),
619
- z.object({ ...baseElementFields, type: z.literal("text"), text: z.string(), sizeMm: z.number().positive(), weight: z.number(), role: elementRoleSchema, fontKey: z.string().optional(), italic: z.boolean().optional(), underline: z.boolean().optional() }),
620
- z.object({ ...baseElementFields, type: z.literal("qr"), url: z.string().url("invalidUrl").or(z.literal("")).optional(), mode: qrModeSchema, sizeMm: z.number().positive(), frameRole: elementRoleSchema, moduleRole: elementRoleSchema }),
621
- z.object({ ...baseElementFields, type: z.literal("image"), assetRef: z.string().min(1), widthMm: z.number().positive(), heightMm: z.number().positive(), role: elementRoleSchema, bgRemoved: z.boolean().optional(), svgOverrides: z.array(z.string().optional()).optional() }),
622
- z.object({ ...baseElementFields, type: z.literal("logo"), assetRef: z.string().min(1), widthMm: z.number().positive(), heightMm: z.number().positive(), role: elementRoleSchema })
790
+ var qrModeSchema = z2.enum(["redirect", "raw"]).optional();
791
+ var cardElementSchema = z2.discriminatedUnion("type", [
792
+ z2.object({ ...baseElementFields, type: z2.literal("chip"), shape: chipSidesSchema }),
793
+ z2.object({ ...baseElementFields, type: z2.literal("text"), text: z2.string(), sizeMm: z2.number().positive(), weight: z2.number(), role: elementRoleSchema, fontKey: z2.string().optional(), italic: z2.boolean().optional(), underline: z2.boolean().optional() }),
794
+ z2.object({ ...baseElementFields, type: z2.literal("qr"), url: z2.string().url("invalidUrl").or(z2.literal("")).optional(), mode: qrModeSchema, sizeMm: z2.number().positive(), frameRole: elementRoleSchema, moduleRole: elementRoleSchema }),
795
+ z2.object({ ...baseElementFields, type: z2.literal("image"), assetRef: z2.string().min(1), widthMm: z2.number().positive(), heightMm: z2.number().positive(), role: elementRoleSchema, bgRemoved: z2.boolean().optional(), svgOverrides: z2.array(z2.string().optional()).optional() }),
796
+ z2.object({ ...baseElementFields, type: z2.literal("logo"), assetRef: z2.string().min(1), widthMm: z2.number().positive(), heightMm: z2.number().positive(), role: elementRoleSchema })
623
797
  ]);
624
- var cardAssetSchema = z.object({
625
- id: z.string().min(1),
626
- kind: z.enum(["vector", "raster"]),
627
- svg: z.string().optional(),
628
- rasterDataUrl: z.string().optional(),
629
- rasterBgDataUrl: z.string().optional(),
630
- rasterAspect: z.number().optional(),
631
- svgColours: z.array(z.string()).optional(),
632
- svgFils: z.array(z.string()).optional()
798
+ var cardAssetSchema = z2.object({
799
+ id: z2.string().min(1),
800
+ kind: z2.enum(["vector", "raster"]),
801
+ svg: z2.string().optional(),
802
+ rasterDataUrl: z2.string().optional(),
803
+ rasterBgDataUrl: z2.string().optional(),
804
+ rasterAspect: z2.number().optional(),
805
+ svgColours: z2.array(z2.string()).optional(),
806
+ svgFils: z2.array(z2.string()).optional()
633
807
  });
634
- var cardDesignSchema = z.object({
808
+ var cardDesignSchema = z2.object({
635
809
  material: cardDesignMaterialSchema,
636
- designType: z.enum(["template", "custom"]),
637
- templateId: z.string().optional(),
638
- thicknessMm: z.number().positive(),
810
+ designType: z2.enum(["template", "custom"]),
811
+ templateId: z2.string().optional(),
812
+ thicknessMm: z2.number().positive(),
639
813
  pattern: cardDesignPatternSchema.nullable(),
640
- layout: z.object({ chipAnchor: z.object({ col: chipAxisSchema, row: chipAxisSchema }) }),
814
+ layout: z2.object({ chipAnchor: z2.object({ col: chipAxisSchema, row: chipAxisSchema }) }),
641
815
  // Exactly three role-keyed bodies (≤3 distinct is therefore guaranteed —
642
816
  // cardgen's max-3-print-bodies rule).
643
- filaments: z.object({ background: hexColorSchema, text: hexColorSchema, accent: hexColorSchema }),
644
- content: z.object({
645
- lines: z.array(
646
- z.object({
647
- text: z.string(),
648
- sizeMm: z.number().positive(),
649
- weight: z.number(),
650
- role: z.enum(["text", "accent"])
817
+ filaments: z2.object({ background: hexColorSchema, text: hexColorSchema, accent: hexColorSchema }),
818
+ content: z2.object({
819
+ lines: z2.array(
820
+ z2.object({
821
+ text: z2.string(),
822
+ sizeMm: z2.number().positive(),
823
+ weight: z2.number(),
824
+ role: z2.enum(["text", "accent"])
651
825
  })
652
826
  )
653
827
  }).optional(),
654
828
  // NFCARD-561 -- `mode` makes "point to profile" DATA, not a string match; when it is
655
829
  // 'redirect' the url is absent and the API substitutes the card's own /r/{code}?src=qr.
656
830
  // Optional throughout: prod holds persisted designs with neither field.
657
- qr: z.object({ enabled: z.boolean(), url: z.string().url("invalidUrl").or(z.literal("")).optional(), mode: qrModeSchema }).optional(),
658
- logo: z.object({ svg: z.string().optional(), svgPath: z.string().optional() }).optional(),
659
- wordmark: z.boolean().optional(),
660
- contentFace: z.enum(["top", "bottom"]).optional(),
831
+ qr: z2.object({ enabled: z2.boolean(), url: z2.string().url("invalidUrl").or(z2.literal("")).optional(), mode: qrModeSchema }).optional(),
832
+ logo: z2.object({ svg: z2.string().optional(), svgPath: z2.string().optional() }).optional(),
833
+ wordmark: z2.boolean().optional(),
834
+ contentFace: z2.enum(["top", "bottom"]).optional(),
661
835
  // NFCARD-197/198 -- additive free-transform layer (optional; absent = legacy grid).
662
- elements: z.array(cardElementSchema).optional(),
663
- assets: z.array(cardAssetSchema).optional(),
664
- contractVersion: z.number().int().positive().optional()
836
+ elements: z2.array(cardElementSchema).optional(),
837
+ assets: z2.array(cardAssetSchema).optional(),
838
+ contractVersion: z2.number().int().positive().optional()
665
839
  }).superRefine((cd, ctx) => {
666
840
  const els = cd.elements;
667
841
  if (!els || els.length === 0) return;
668
842
  if (cd.designType !== "custom") {
669
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "elementsCustomOnly", path: ["elements"] });
843
+ ctx.addIssue({ code: z2.ZodIssueCode.custom, message: "elementsCustomOnly", path: ["elements"] });
670
844
  }
671
845
  if (cd.contractVersion !== 2) {
672
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "contractVersionRequired", path: ["contractVersion"] });
846
+ ctx.addIssue({ code: z2.ZodIssueCode.custom, message: "contractVersionRequired", path: ["contractVersion"] });
673
847
  }
674
848
  const seen = /* @__PURE__ */ new Set();
675
849
  for (const el of els) {
676
850
  if (seen.has(el.id)) {
677
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "duplicateElementId", path: ["elements"] });
851
+ ctx.addIssue({ code: z2.ZodIssueCode.custom, message: "duplicateElementId", path: ["elements"] });
678
852
  }
679
853
  seen.add(el.id);
680
854
  }
681
855
  const chips = els.filter((e) => e.type === "chip");
682
856
  if (chips.length !== 1) {
683
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "exactlyOneChip", path: ["elements"] });
857
+ ctx.addIssue({ code: z2.ZodIssueCode.custom, message: "exactlyOneChip", path: ["elements"] });
684
858
  }
685
859
  if (chips.length === 1) {
686
860
  const chip = chips[0];
687
861
  const near = mmToNearestChipAnchor({ x: chip.transform.xMm, y: chip.transform.yMm });
688
862
  if (near.col !== cd.layout.chipAnchor.col || near.row !== cd.layout.chipAnchor.row) {
689
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "chipAnchorDualWriteMismatch", path: ["layout", "chipAnchor"] });
863
+ ctx.addIssue({ code: z2.ZodIssueCode.custom, message: "chipAnchorDualWriteMismatch", path: ["layout", "chipAnchor"] });
690
864
  }
691
865
  }
692
866
  const assetsById = new Map((cd.assets ?? []).map((a) => [a.id, a]));
693
867
  for (const el of els) {
694
868
  for (const viol of elementPrintability(el, { assetsById })) {
695
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: viol.code, path: ["elements"] });
869
+ ctx.addIssue({ code: z2.ZodIssueCode.custom, message: viol.code, path: ["elements"] });
696
870
  }
697
871
  }
698
872
  });
699
- var physicalConfigSchema = z.object({
873
+ var physicalConfigSchema = z2.object({
700
874
  material: materialSchema,
701
- colour: z.string().regex(/^#[0-9A-Fa-f]{6}$/, "invalidHexColour").optional(),
875
+ colour: z2.string().regex(/^#[0-9A-Fa-f]{6}$/, "invalidHexColour").optional(),
702
876
  comboId: comboIdSchema,
703
877
  frontElements: cardElementsSchema,
704
878
  backElements: cardElementsSchema,
705
- qrTargetUrl: z.string().url("invalidUrl").optional().or(z.literal("")),
706
- customDesignRequest: z.string().max(2e3).optional().or(z.literal("")),
879
+ qrTargetUrl: z2.string().url("invalidUrl").optional().or(z2.literal("")),
880
+ customDesignRequest: z2.string().max(2e3).optional().or(z2.literal("")),
707
881
  frontContactToggles: contactDetailTogglesSchema.optional(),
708
882
  backContactToggles: contactDetailTogglesSchema.optional(),
709
883
  contentOverrides: physicalContentOverridesSchema.optional(),
710
- companyLogoUrl: z.string().url().optional().or(z.literal("")),
884
+ companyLogoUrl: z2.string().url().optional().or(z2.literal("")),
711
885
  // NFCARD PVC 2D pipeline: free-transform elements + image assets ride
712
886
  // top-level (PVC has no cardDesign). Whitelisted so they aren't stripped.
713
- elements: z.array(cardElementSchema).optional(),
714
- assets: z.array(cardAssetSchema).optional(),
887
+ elements: z2.array(cardElementSchema).optional(),
888
+ assets: z2.array(cardAssetSchema).optional(),
715
889
  // Nested so it isn't stripped (this object has no .passthrough()). Optional
716
890
  // → legacy configs + the PVC path persist with no cardDesign block.
717
891
  cardDesign: cardDesignSchema.optional()
@@ -725,27 +899,27 @@ var physicalConfigSchema = z.object({
725
899
  message: "cardDesignMaterialMismatch",
726
900
  path: ["cardDesign", "material"]
727
901
  });
728
- var digitalVisibilitySchema = z.object({
729
- showName: z.boolean().optional(),
730
- showPosition: z.boolean().optional(),
731
- showCompany: z.boolean().optional(),
732
- showEmail: z.boolean().optional(),
733
- showPhone: z.boolean().optional(),
734
- showWebsite: z.boolean().optional(),
735
- showLocation: z.boolean().optional(),
736
- showBio: z.boolean().optional(),
737
- showSocialLinks: z.boolean().optional(),
738
- showLogo: z.boolean().optional()
902
+ var digitalVisibilitySchema = z2.object({
903
+ showName: z2.boolean().optional(),
904
+ showPosition: z2.boolean().optional(),
905
+ showCompany: z2.boolean().optional(),
906
+ showEmail: z2.boolean().optional(),
907
+ showPhone: z2.boolean().optional(),
908
+ showWebsite: z2.boolean().optional(),
909
+ showLocation: z2.boolean().optional(),
910
+ showBio: z2.boolean().optional(),
911
+ showSocialLinks: z2.boolean().optional(),
912
+ showLogo: z2.boolean().optional()
739
913
  });
740
914
  var MAX_CTA_BUTTONS = 5;
741
- var ctaButtonSchema = z.object({
742
- label: z.string().max(2048).optional().or(z.literal("")).transform((v) => sanitizeText(v ?? "", 40)),
743
- url: z.string().max(2048).optional().or(z.literal("")).transform((v) => sanitizeHttpUrl(v))
915
+ var ctaButtonSchema = z2.object({
916
+ label: z2.string().max(2048).optional().or(z2.literal("")).transform((v) => sanitizeText(v ?? "", 40)),
917
+ url: z2.string().max(2048).optional().or(z2.literal("")).transform((v) => sanitizeHttpUrl(v))
744
918
  });
745
- var ctaButtonsSchema = z.array(ctaButtonSchema).max(50).transform(
919
+ var ctaButtonsSchema = z2.array(ctaButtonSchema).max(50).transform(
746
920
  (arr) => arr.filter((b) => b.url !== "" && b.label !== "").slice(0, MAX_CTA_BUTTONS)
747
921
  );
748
- var tipJarSchema = z.unknown().transform((raw) => {
922
+ var tipJarSchema = z2.unknown().transform((raw) => {
749
923
  if (!raw || typeof raw !== "object") return void 0;
750
924
  const rec = raw;
751
925
  if (!isTipProviderId(rec.provider)) return void 0;
@@ -754,7 +928,7 @@ var tipJarSchema = z.unknown().transform((raw) => {
754
928
  const label = typeof rec.label === "string" ? sanitizeText(rec.label, 40) : "";
755
929
  return label ? { provider: rec.provider, handle, label } : { provider: rec.provider, handle };
756
930
  });
757
- var bookingSchema = z.unknown().transform((raw) => {
931
+ var bookingSchema = z2.unknown().transform((raw) => {
758
932
  if (!raw || typeof raw !== "object") return void 0;
759
933
  const rec = raw;
760
934
  if (!isBookingProviderId(rec.provider)) return void 0;
@@ -763,15 +937,27 @@ var bookingSchema = z.unknown().transform((raw) => {
763
937
  const label = typeof rec.label === "string" ? sanitizeText(rec.label, 40) : "";
764
938
  return label ? { provider: rec.provider, handle, label } : { provider: rec.provider, handle };
765
939
  });
766
- var digitalConfigSchema = z.object({
767
- templateId: z.enum(["minimal", "bold", "card", "aurora", "classic", "spotlight", "marquee", "riso"]),
768
- accentColor: z.string().regex(/^#[0-9A-Fa-f]{6}$/),
769
- primaryColor: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
770
- secondaryColor: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
771
- textColor: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
772
- fontKey: z.string().min(1),
773
- showAvatar: z.boolean(),
774
- avatarMode: z.enum(["photo", "monogram", "none"]).optional(),
940
+ var profileBackgroundSchema = z2.unknown().transform((raw) => {
941
+ if (!raw || typeof raw !== "object") return void 0;
942
+ const rec = raw;
943
+ if (rec.kind === "solid") return { kind: "solid" };
944
+ if (rec.kind !== "gradient") return void 0;
945
+ const hex = /^#[0-9A-Fa-f]{6}$/;
946
+ const from = typeof rec.from === "string" && hex.test(rec.from) ? rec.from : null;
947
+ const to = typeof rec.to === "string" && hex.test(rec.to) ? rec.to : null;
948
+ if (!from || !to) return void 0;
949
+ const angleDeg = typeof rec.angleDeg === "number" && Number.isFinite(rec.angleDeg) ? (Math.round(rec.angleDeg) % 360 + 360) % 360 : void 0;
950
+ return angleDeg === void 0 ? { kind: "gradient", from, to } : { kind: "gradient", from, to, angleDeg };
951
+ });
952
+ var digitalConfigSchema = z2.object({
953
+ templateId: z2.enum(["minimal", "bold", "card", "aurora", "classic", "spotlight", "marquee", "riso"]),
954
+ accentColor: z2.string().regex(/^#[0-9A-Fa-f]{6}$/),
955
+ primaryColor: z2.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
956
+ secondaryColor: z2.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
957
+ textColor: z2.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
958
+ fontKey: z2.string().min(1),
959
+ showAvatar: z2.boolean(),
960
+ avatarMode: z2.enum(["photo", "monogram", "none"]).optional(),
775
961
  visibility: digitalVisibilitySchema.optional(),
776
962
  // FOXHOLE — `contentOverrides` was removed when the wizard started
777
963
  // editing userData directly inside the digital step. The bio that
@@ -783,131 +969,139 @@ var digitalConfigSchema = z.object({
783
969
  // none). Optional, so existing profiles/configs validate unchanged.
784
970
  ctaButtons: ctaButtonsSchema.optional(),
785
971
  tipJar: tipJarSchema.optional(),
786
- booking: bookingSchema.optional()
972
+ booking: bookingSchema.optional(),
973
+ // NFCARD-641 — absent means solid `secondaryColor`, so every profile that
974
+ // exists today validates unchanged and no backfill is needed.
975
+ background: profileBackgroundSchema.optional(),
976
+ // NFCARD-656 — arrangement + per-object style intent. Absent for every
977
+ // profile that predates the object model; absence and { v: 1 } paint the
978
+ // same page (deriveProfileLayout), so no backfill and nothing existing
979
+ // revalidates differently.
980
+ layout: profileLayoutSchema.optional()
787
981
  });
788
- var entrySourceSchema = z.object({
789
- output: z.enum(["with-card", "digital-only"]).optional(),
790
- entryFlow: z.enum(["order", "try"]).optional(),
791
- ref: z.string().max(64).optional(),
792
- path: z.string().max(256).optional(),
793
- referrer: z.string().max(256).optional(),
982
+ var entrySourceSchema = z2.object({
983
+ output: z2.enum(["with-card", "digital-only"]).optional(),
984
+ entryFlow: z2.enum(["order", "try"]).optional(),
985
+ ref: z2.string().max(64).optional(),
986
+ path: z2.string().max(256).optional(),
987
+ referrer: z2.string().max(256).optional(),
794
988
  // NFCARD-393 - the design deep-link preselects (material implies the
795
989
  // with-card entry; template additionally names a TEMPLATE_DESIGNS id).
796
990
  material: materialSchema.optional(),
797
- template: z.string().max(64).optional(),
798
- utm: z.record(z.string().max(64), z.string().max(256)).refine((o) => Object.keys(o).length <= 10, "too many utm params").optional()
991
+ template: z2.string().max(64).optional(),
992
+ utm: z2.record(z2.string().max(64), z2.string().max(256)).refine((o) => Object.keys(o).length <= 10, "too many utm params").optional()
799
993
  }).strict();
800
- var configurationCreateSchema = z.object({
801
- sessionId: z.string().uuid(),
994
+ var configurationCreateSchema = z2.object({
995
+ sessionId: z2.string().uuid(),
802
996
  userData: userDataSchema,
803
997
  physicalConfig: physicalConfigSchema,
804
998
  digitalConfig: digitalConfigSchema,
805
- quantity: z.number().int().min(1).max(1e3).default(1),
999
+ quantity: z2.number().int().min(1).max(1e3).default(1),
806
1000
  // FOXHOLE-684: free→card upgrade. When set, fulfilment attaches this
807
1001
  // existing (free-tier) profile to the new card instead of creating a
808
1002
  // fresh default one. Optional — the normal order flow omits it.
809
- existingProfileId: z.string().uuid().optional(),
1003
+ existingProfileId: z2.string().uuid().optional(),
810
1004
  // NFCARD-3: on the Template design path, the chosen TEMPLATE_DESIGNS id
811
1005
  // (a slug, e.g. "tpl-pvc-white"). Carries the explicit template reference
812
1006
  // for fulfilment alongside the comboId already baked into physicalConfig.
813
1007
  // Optional — Custom orders omit it.
814
- physicalTemplateId: z.string().min(1).max(64).optional(),
1008
+ physicalTemplateId: z2.string().min(1).max(64).optional(),
815
1009
  // NFCARD-392 - optional entry-attribution blob (see entrySourceSchema).
816
1010
  // Create-only by design: first touch wins, so the update schema does
817
1011
  // not accept it.
818
1012
  entrySource: entrySourceSchema.optional()
819
1013
  });
820
- var configurationUpdateSchema = z.object({
1014
+ var configurationUpdateSchema = z2.object({
821
1015
  userData: userDataSchema.optional(),
822
1016
  physicalConfig: physicalConfigSchema.optional(),
823
1017
  digitalConfig: digitalConfigSchema.optional(),
824
- quantity: z.number().int().min(1).max(1e3).optional(),
825
- existingProfileId: z.string().uuid().optional(),
826
- physicalTemplateId: z.string().min(1).max(64).optional()
1018
+ quantity: z2.number().int().min(1).max(1e3).optional(),
1019
+ existingProfileId: z2.string().uuid().optional(),
1020
+ physicalTemplateId: z2.string().min(1).max(64).optional()
827
1021
  });
828
- var createOrderSchema = z.object({
829
- configurationId: z.string().uuid(),
830
- discountCode: z.string().min(1).max(50).optional(),
1022
+ var createOrderSchema = z2.object({
1023
+ configurationId: z2.string().uuid(),
1024
+ discountCode: z2.string().min(1).max(50).optional(),
831
1025
  // FOXHOLE-714 — Template or Custom design path. Picks which side
832
1026
  // of the Price.templatePriceCents / customPriceCents split applies
833
1027
  // and which Stripe Price ID the Checkout line item references.
834
- designType: z.enum(["template", "custom"])
1028
+ designType: z2.enum(["template", "custom"])
835
1029
  });
836
- var cardUpdateSchema = z.object({
1030
+ var cardUpdateSchema = z2.object({
837
1031
  digitalConfigOverride: digitalConfigSchema.partial().extend({
838
- profilePhotoUrl: z.string().url().optional().or(z.literal("")),
839
- companyLogoUrl: z.string().url().optional().or(z.literal(""))
1032
+ profilePhotoUrl: z2.string().url().optional().or(z2.literal("")),
1033
+ companyLogoUrl: z2.string().url().optional().or(z2.literal(""))
840
1034
  }).partial().optional(),
841
- status: z.enum(["active", "suspended", "archived"]).optional()
1035
+ status: z2.enum(["active", "suspended", "archived"]).optional()
842
1036
  });
843
- var adminCardCreateSchema = z.object({
844
- targetUserId: z.string().min(1),
1037
+ var adminCardCreateSchema = z2.object({
1038
+ targetUserId: z2.string().min(1),
845
1039
  userData: userDataSchema,
846
1040
  digitalConfig: digitalConfigSchema,
847
1041
  physicalConfig: physicalConfigSchema.optional()
848
1042
  });
849
- var profileVisibilitySchema = z.enum(["public", "private", "link_only"]);
1043
+ var profileVisibilitySchema = z2.enum(["public", "private", "link_only"]);
850
1044
  var profileUserDataSchema = userDataSchema.extend({
851
- email: z.string().email("invalidEmail").optional().or(z.literal(""))
1045
+ email: z2.string().email("invalidEmail").optional().or(z2.literal(""))
852
1046
  });
853
- var profileCreateSchema = z.object({
1047
+ var profileCreateSchema = z2.object({
854
1048
  // Optional (FOXHOLE-684). When present, the card gets a CardProfile slot
855
1049
  // pointing to the new profile (subject to the 3-slot cap). When absent,
856
1050
  // a standalone (cardless) free-tier profile is created — used by the
857
1051
  // free signup path; no slot is allocated.
858
- cardId: z.string().uuid("required").optional(),
1052
+ cardId: z2.string().uuid("required").optional(),
859
1053
  // Optional (NFCARD-128): a free profile can be created label-less — the owner
860
1054
  // sets the label later in the editor (label/icon are paid-only). Empty/absent
861
1055
  // is stored as null, so the hub tile falls back to the profile's displayName.
862
- label: z.string().max(50).optional().or(z.literal("")),
863
- icon: z.string().max(10).optional().or(z.literal("")),
1056
+ label: z2.string().max(50).optional().or(z2.literal("")),
1057
+ icon: z2.string().max(10).optional().or(z2.literal("")),
864
1058
  userData: profileUserDataSchema,
865
1059
  digitalConfig: digitalConfigSchema,
866
1060
  visibility: profileVisibilitySchema.default("public"),
867
- accessPin: z.string().min(4, "pinTooShort").max(8, "pinTooLong").regex(/^\d+$/, "pinDigitsOnly").optional().or(z.literal(""))
1061
+ accessPin: z2.string().min(4, "pinTooShort").max(8, "pinTooLong").regex(/^\d+$/, "pinDigitsOnly").optional().or(z2.literal(""))
868
1062
  });
869
- var signupWithProfileSchema = z.object({
870
- email: z.string().email("invalidEmail"),
871
- password: z.string().min(8, "passwordTooShort").max(128, "passwordTooLong"),
872
- name: z.string().min(1).max(120).optional().or(z.literal("")),
1063
+ var signupWithProfileSchema = z2.object({
1064
+ email: z2.string().email("invalidEmail"),
1065
+ password: z2.string().min(8, "passwordTooShort").max(128, "passwordTooLong"),
1066
+ name: z2.string().min(1).max(120).optional().or(z2.literal("")),
873
1067
  // Must be explicitly true — the ÁSZF checkbox is mandatory.
874
- acceptedTerms: z.literal(true),
875
- marketingOptIn: z.boolean().optional().default(false),
876
- captchaToken: z.string().max(4096).optional().or(z.literal("")),
877
- preferredLanguage: z.enum(["hu", "en"]).optional().default("hu"),
878
- label: z.string().min(1, "required").max(50).optional().or(z.literal("")),
1068
+ acceptedTerms: z2.literal(true),
1069
+ marketingOptIn: z2.boolean().optional().default(false),
1070
+ captchaToken: z2.string().max(4096).optional().or(z2.literal("")),
1071
+ preferredLanguage: z2.enum(["hu", "en"]).optional().default("hu"),
1072
+ label: z2.string().min(1, "required").max(50).optional().or(z2.literal("")),
879
1073
  userData: profileUserDataSchema,
880
1074
  digitalConfig: digitalConfigSchema
881
1075
  });
882
- var profileUpdateSchema = z.object({
883
- label: z.string().min(1).max(50).optional(),
884
- icon: z.string().max(10).optional().or(z.literal("")),
1076
+ var profileUpdateSchema = z2.object({
1077
+ label: z2.string().min(1).max(50).optional(),
1078
+ icon: z2.string().max(10).optional().or(z2.literal("")),
885
1079
  userData: profileUserDataSchema.optional(),
886
1080
  digitalConfig: digitalConfigSchema.partial().extend({
887
- profilePhotoUrl: z.string().url().optional().or(z.literal("")),
888
- companyLogoUrl: z.string().url().optional().or(z.literal(""))
1081
+ profilePhotoUrl: z2.string().url().optional().or(z2.literal("")),
1082
+ companyLogoUrl: z2.string().url().optional().or(z2.literal(""))
889
1083
  }).partial().optional(),
890
1084
  visibility: profileVisibilitySchema.optional(),
891
- accessPin: z.string().min(4, "pinTooShort").max(8, "pinTooLong").regex(/^\d+$/, "pinDigitsOnly").optional().or(z.literal(""))
1085
+ accessPin: z2.string().min(4, "pinTooShort").max(8, "pinTooLong").regex(/^\d+$/, "pinDigitsOnly").optional().or(z2.literal(""))
892
1086
  });
893
- var profilePinVerifySchema = z.object({
894
- pin: z.string().min(4).max(8)
1087
+ var profilePinVerifySchema = z2.object({
1088
+ pin: z2.string().min(4).max(8)
895
1089
  });
896
- var profileAccessTokenCreateSchema = z.object({
897
- label: z.string().max(100).optional().or(z.literal("")),
898
- expiresAt: z.string().datetime().optional().or(z.literal(""))
1090
+ var profileAccessTokenCreateSchema = z2.object({
1091
+ label: z2.string().max(100).optional().or(z2.literal("")),
1092
+ expiresAt: z2.string().datetime().optional().or(z2.literal(""))
899
1093
  });
900
- var cardProfileUpdateSchema = z.object({
901
- profiles: z.array(
902
- z.object({
903
- profileId: z.string().uuid(),
904
- sortOrder: z.number().int().min(0),
905
- isDefault: z.boolean()
1094
+ var cardProfileUpdateSchema = z2.object({
1095
+ profiles: z2.array(
1096
+ z2.object({
1097
+ profileId: z2.string().uuid(),
1098
+ sortOrder: z2.number().int().min(0),
1099
+ isDefault: z2.boolean()
906
1100
  })
907
1101
  )
908
1102
  });
909
- var hexColor = z.string().regex(/^#[0-9A-Fa-f]{6}$/, "invalidHexColour");
910
- var httpsOnlyUrl = z.string().url("invalidUrl").refine(
1103
+ var hexColor = z2.string().regex(/^#[0-9A-Fa-f]{6}$/, "invalidHexColour");
1104
+ var httpsOnlyUrl = z2.string().url("invalidUrl").refine(
911
1105
  (v) => {
912
1106
  try {
913
1107
  const u = new URL(v);
@@ -918,35 +1112,35 @@ var httpsOnlyUrl = z.string().url("invalidUrl").refine(
918
1112
  },
919
1113
  { message: "invalidUrl" }
920
1114
  );
921
- var hubConfigSchema = z.object({
922
- displayName: z.string().max(80).optional().or(z.literal("")),
923
- subtitle: z.string().max(120).optional().or(z.literal("")),
924
- avatarMode: z.enum(["auto", "image", "monogram"]).optional(),
925
- avatarUrl: httpsOnlyUrl.optional().or(z.literal("")),
926
- monogramText: z.string().max(2).optional().or(z.literal("")),
1115
+ var hubConfigSchema = z2.object({
1116
+ displayName: z2.string().max(80).optional().or(z2.literal("")),
1117
+ subtitle: z2.string().max(120).optional().or(z2.literal("")),
1118
+ avatarMode: z2.enum(["auto", "image", "monogram"]).optional(),
1119
+ avatarUrl: httpsOnlyUrl.optional().or(z2.literal("")),
1120
+ monogramText: z2.string().max(2).optional().or(z2.literal("")),
927
1121
  accentColor: hexColor.optional(),
928
1122
  backgroundColor: hexColor.optional(),
929
1123
  textColor: hexColor.optional(),
930
1124
  tileBackgroundColor: hexColor.optional(),
931
1125
  tileBorderColor: hexColor.optional(),
932
- fontKey: z.string().min(1).max(50).optional(),
933
- footerText: z.string().max(140).optional().or(z.literal(""))
1126
+ fontKey: z2.string().min(1).max(50).optional(),
1127
+ footerText: z2.string().max(140).optional().or(z2.literal(""))
934
1128
  });
935
- var hubProfilesUpdateSchema = z.object({
936
- profiles: z.array(
937
- z.object({
938
- profileId: z.string().uuid(),
939
- sortOrder: z.number().int().min(0),
940
- isDefault: z.boolean().optional(),
941
- hiddenFromHub: z.boolean().optional()
1129
+ var hubProfilesUpdateSchema = z2.object({
1130
+ profiles: z2.array(
1131
+ z2.object({
1132
+ profileId: z2.string().uuid(),
1133
+ sortOrder: z2.number().int().min(0),
1134
+ isDefault: z2.boolean().optional(),
1135
+ hiddenFromHub: z2.boolean().optional()
942
1136
  })
943
1137
  )
944
1138
  });
945
- var contactExchangeSchema = z.object({
946
- name: z.string().min(1, "required").max(100),
947
- email: z.string().email("invalidEmail").optional().or(z.literal("")),
948
- phone: z.string().regex(/^[+]?[\d\s\-().]{6,20}$/, "invalidPhone").optional().or(z.literal("")),
949
- notes: z.string().max(500).optional().or(z.literal(""))
1139
+ var contactExchangeSchema = z2.object({
1140
+ name: z2.string().min(1, "required").max(100),
1141
+ email: z2.string().email("invalidEmail").optional().or(z2.literal("")),
1142
+ phone: z2.string().regex(/^[+]?[\d\s\-().]{6,20}$/, "invalidPhone").optional().or(z2.literal("")),
1143
+ notes: z2.string().max(500).optional().or(z2.literal(""))
950
1144
  });
951
1145
  function formatZodErrors(error) {
952
1146
  const errors = {};
@@ -996,6 +1190,7 @@ export {
996
1190
  TEXT_MIN_EM_MM,
997
1191
  adminCardCreateSchema,
998
1192
  bookingSchema,
1193
+ buttonObjectStyleSchema,
999
1194
  cardAssetSchema,
1000
1195
  cardDesignMaterialSchema,
1001
1196
  cardDesignPatternSchema,
@@ -1023,6 +1218,7 @@ export {
1023
1218
  footprintOf,
1024
1219
  hubConfigSchema,
1025
1220
  hubProfilesUpdateSchema,
1221
+ imageObjectStyleSchema,
1026
1222
  isFiniteTransform,
1027
1223
  materialSchema,
1028
1224
  mmToNearestChipAnchor,
@@ -1031,18 +1227,22 @@ export {
1031
1227
  physicalConfigSchema,
1032
1228
  physicalContentOverridesSchema,
1033
1229
  profileAccessTokenCreateSchema,
1230
+ profileBackgroundSchema,
1034
1231
  profileCreateSchema,
1232
+ profileLayoutSchema,
1035
1233
  profilePinVerifySchema,
1036
1234
  profileUpdateSchema,
1037
1235
  profileUserDataSchema,
1038
1236
  profileVisibilitySchema,
1039
1237
  qrModeSchema,
1238
+ safeProfileLayout,
1040
1239
  sanitizeHttpUrl,
1041
1240
  sanitizeText,
1042
1241
  signupWithProfileSchema,
1043
1242
  socialLinksSchema,
1044
1243
  socialOverridesSchema,
1045
1244
  textAdvanceEm,
1245
+ textObjectStyleSchema,
1046
1246
  tipJarSchema,
1047
1247
  userDataSchema,
1048
1248
  validateConfiguration,