@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.d.ts +2229 -30
- package/dist/index.js +426 -226
- package/package.json +2 -2
- package/src/index.ts +0 -0
- package/src/profileBackground.test.ts +116 -0
- package/src/profileLayout.test.ts +158 -0
- package/src/profileLayout.ts +265 -0
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 =
|
|
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 =
|
|
493
|
-
var socialLinksSchema =
|
|
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:
|
|
675
|
+
customUrlLabel: z2.string().max(80).optional().or(z2.literal("")).transform((v) => sanitizeText(v ?? "", 50))
|
|
502
676
|
});
|
|
503
|
-
var overrideUrl =
|
|
504
|
-
var socialOverridesSchema =
|
|
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:
|
|
689
|
+
customUrlLabel: z2.string().max(100).transform((v) => sanitizeText(v, 80)).optional()
|
|
516
690
|
});
|
|
517
|
-
var userDataSchema =
|
|
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:
|
|
522
|
-
lastName:
|
|
523
|
-
position:
|
|
524
|
-
email:
|
|
525
|
-
phone:
|
|
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:
|
|
533
|
-
location:
|
|
534
|
-
company:
|
|
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:
|
|
540
|
-
profilePhotoUrl:
|
|
541
|
-
companyLogoUrl:
|
|
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 =
|
|
545
|
-
showName:
|
|
546
|
-
showPosition:
|
|
547
|
-
showEmail:
|
|
548
|
-
showPhone:
|
|
549
|
-
showCompany:
|
|
550
|
-
showLogo:
|
|
551
|
-
showQr:
|
|
552
|
-
showPhoto:
|
|
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 =
|
|
555
|
-
var comboIdSchema =
|
|
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 =
|
|
568
|
-
showEmail:
|
|
569
|
-
showPhone:
|
|
570
|
-
showLocation:
|
|
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 =
|
|
573
|
-
displayName:
|
|
574
|
-
company:
|
|
575
|
-
position:
|
|
576
|
-
email:
|
|
577
|
-
phone:
|
|
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:
|
|
581
|
-
location:
|
|
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 =
|
|
584
|
-
var cellSidesSchema =
|
|
585
|
-
var chipSidesSchema =
|
|
586
|
-
var chipAxisSchema =
|
|
587
|
-
var cardDesignMaterialSchema =
|
|
588
|
-
var patternFamilySchema =
|
|
589
|
-
var cardDesignPatternSchema =
|
|
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:
|
|
592
|
-
density:
|
|
593
|
-
holeMinMm:
|
|
594
|
-
holeMaxMm:
|
|
595
|
-
intensity:
|
|
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:
|
|
771
|
+
cellRotationDeg: z2.number(),
|
|
598
772
|
chipShape: chipSidesSchema,
|
|
599
|
-
chipRotationDeg:
|
|
773
|
+
chipRotationDeg: z2.number()
|
|
600
774
|
}).refine((p) => p.holeMinMm <= p.holeMaxMm, { message: "holeMinGtMax", path: ["holeMinMm"] });
|
|
601
|
-
var elementTransformSchema =
|
|
602
|
-
xMm:
|
|
603
|
-
yMm:
|
|
604
|
-
rotationDeg:
|
|
605
|
-
scale:
|
|
606
|
-
z:
|
|
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 =
|
|
782
|
+
var elementRoleSchema = z2.enum(["text", "accent"]);
|
|
609
783
|
var baseElementFields = {
|
|
610
|
-
id:
|
|
784
|
+
id: z2.string().min(1),
|
|
611
785
|
transform: elementTransformSchema,
|
|
612
|
-
face:
|
|
613
|
-
locked:
|
|
614
|
-
colour:
|
|
786
|
+
face: z2.enum(["front", "back"]).optional(),
|
|
787
|
+
locked: z2.boolean().optional(),
|
|
788
|
+
colour: z2.string().optional()
|
|
615
789
|
};
|
|
616
|
-
var qrModeSchema =
|
|
617
|
-
var cardElementSchema =
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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 =
|
|
625
|
-
id:
|
|
626
|
-
kind:
|
|
627
|
-
svg:
|
|
628
|
-
rasterDataUrl:
|
|
629
|
-
rasterBgDataUrl:
|
|
630
|
-
rasterAspect:
|
|
631
|
-
svgColours:
|
|
632
|
-
svgFils:
|
|
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 =
|
|
808
|
+
var cardDesignSchema = z2.object({
|
|
635
809
|
material: cardDesignMaterialSchema,
|
|
636
|
-
designType:
|
|
637
|
-
templateId:
|
|
638
|
-
thicknessMm:
|
|
810
|
+
designType: z2.enum(["template", "custom"]),
|
|
811
|
+
templateId: z2.string().optional(),
|
|
812
|
+
thicknessMm: z2.number().positive(),
|
|
639
813
|
pattern: cardDesignPatternSchema.nullable(),
|
|
640
|
-
layout:
|
|
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:
|
|
644
|
-
content:
|
|
645
|
-
lines:
|
|
646
|
-
|
|
647
|
-
text:
|
|
648
|
-
sizeMm:
|
|
649
|
-
weight:
|
|
650
|
-
role:
|
|
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:
|
|
658
|
-
logo:
|
|
659
|
-
wordmark:
|
|
660
|
-
contentFace:
|
|
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:
|
|
663
|
-
assets:
|
|
664
|
-
contractVersion:
|
|
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:
|
|
843
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, message: "elementsCustomOnly", path: ["elements"] });
|
|
670
844
|
}
|
|
671
845
|
if (cd.contractVersion !== 2) {
|
|
672
|
-
ctx.addIssue({ code:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
869
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, message: viol.code, path: ["elements"] });
|
|
696
870
|
}
|
|
697
871
|
}
|
|
698
872
|
});
|
|
699
|
-
var physicalConfigSchema =
|
|
873
|
+
var physicalConfigSchema = z2.object({
|
|
700
874
|
material: materialSchema,
|
|
701
|
-
colour:
|
|
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:
|
|
706
|
-
customDesignRequest:
|
|
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:
|
|
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:
|
|
714
|
-
assets:
|
|
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 =
|
|
729
|
-
showName:
|
|
730
|
-
showPosition:
|
|
731
|
-
showCompany:
|
|
732
|
-
showEmail:
|
|
733
|
-
showPhone:
|
|
734
|
-
showWebsite:
|
|
735
|
-
showLocation:
|
|
736
|
-
showBio:
|
|
737
|
-
showSocialLinks:
|
|
738
|
-
showLogo:
|
|
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 =
|
|
742
|
-
label:
|
|
743
|
-
url:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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 =
|
|
789
|
-
output:
|
|
790
|
-
entryFlow:
|
|
791
|
-
ref:
|
|
792
|
-
path:
|
|
793
|
-
referrer:
|
|
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:
|
|
798
|
-
utm:
|
|
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 =
|
|
801
|
-
sessionId:
|
|
994
|
+
var configurationCreateSchema = z2.object({
|
|
995
|
+
sessionId: z2.string().uuid(),
|
|
802
996
|
userData: userDataSchema,
|
|
803
997
|
physicalConfig: physicalConfigSchema,
|
|
804
998
|
digitalConfig: digitalConfigSchema,
|
|
805
|
-
quantity:
|
|
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:
|
|
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:
|
|
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 =
|
|
1014
|
+
var configurationUpdateSchema = z2.object({
|
|
821
1015
|
userData: userDataSchema.optional(),
|
|
822
1016
|
physicalConfig: physicalConfigSchema.optional(),
|
|
823
1017
|
digitalConfig: digitalConfigSchema.optional(),
|
|
824
|
-
quantity:
|
|
825
|
-
existingProfileId:
|
|
826
|
-
physicalTemplateId:
|
|
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 =
|
|
829
|
-
configurationId:
|
|
830
|
-
discountCode:
|
|
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:
|
|
1028
|
+
designType: z2.enum(["template", "custom"])
|
|
835
1029
|
});
|
|
836
|
-
var cardUpdateSchema =
|
|
1030
|
+
var cardUpdateSchema = z2.object({
|
|
837
1031
|
digitalConfigOverride: digitalConfigSchema.partial().extend({
|
|
838
|
-
profilePhotoUrl:
|
|
839
|
-
companyLogoUrl:
|
|
1032
|
+
profilePhotoUrl: z2.string().url().optional().or(z2.literal("")),
|
|
1033
|
+
companyLogoUrl: z2.string().url().optional().or(z2.literal(""))
|
|
840
1034
|
}).partial().optional(),
|
|
841
|
-
status:
|
|
1035
|
+
status: z2.enum(["active", "suspended", "archived"]).optional()
|
|
842
1036
|
});
|
|
843
|
-
var adminCardCreateSchema =
|
|
844
|
-
targetUserId:
|
|
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 =
|
|
1043
|
+
var profileVisibilitySchema = z2.enum(["public", "private", "link_only"]);
|
|
850
1044
|
var profileUserDataSchema = userDataSchema.extend({
|
|
851
|
-
email:
|
|
1045
|
+
email: z2.string().email("invalidEmail").optional().or(z2.literal(""))
|
|
852
1046
|
});
|
|
853
|
-
var profileCreateSchema =
|
|
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:
|
|
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:
|
|
863
|
-
icon:
|
|
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:
|
|
1061
|
+
accessPin: z2.string().min(4, "pinTooShort").max(8, "pinTooLong").regex(/^\d+$/, "pinDigitsOnly").optional().or(z2.literal(""))
|
|
868
1062
|
});
|
|
869
|
-
var signupWithProfileSchema =
|
|
870
|
-
email:
|
|
871
|
-
password:
|
|
872
|
-
name:
|
|
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:
|
|
875
|
-
marketingOptIn:
|
|
876
|
-
captchaToken:
|
|
877
|
-
preferredLanguage:
|
|
878
|
-
label:
|
|
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 =
|
|
883
|
-
label:
|
|
884
|
-
icon:
|
|
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:
|
|
888
|
-
companyLogoUrl:
|
|
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:
|
|
1085
|
+
accessPin: z2.string().min(4, "pinTooShort").max(8, "pinTooLong").regex(/^\d+$/, "pinDigitsOnly").optional().or(z2.literal(""))
|
|
892
1086
|
});
|
|
893
|
-
var profilePinVerifySchema =
|
|
894
|
-
pin:
|
|
1087
|
+
var profilePinVerifySchema = z2.object({
|
|
1088
|
+
pin: z2.string().min(4).max(8)
|
|
895
1089
|
});
|
|
896
|
-
var profileAccessTokenCreateSchema =
|
|
897
|
-
label:
|
|
898
|
-
expiresAt:
|
|
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 =
|
|
901
|
-
profiles:
|
|
902
|
-
|
|
903
|
-
profileId:
|
|
904
|
-
sortOrder:
|
|
905
|
-
isDefault:
|
|
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 =
|
|
910
|
-
var httpsOnlyUrl =
|
|
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 =
|
|
922
|
-
displayName:
|
|
923
|
-
subtitle:
|
|
924
|
-
avatarMode:
|
|
925
|
-
avatarUrl: httpsOnlyUrl.optional().or(
|
|
926
|
-
monogramText:
|
|
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:
|
|
933
|
-
footerText:
|
|
1126
|
+
fontKey: z2.string().min(1).max(50).optional(),
|
|
1127
|
+
footerText: z2.string().max(140).optional().or(z2.literal(""))
|
|
934
1128
|
});
|
|
935
|
-
var hubProfilesUpdateSchema =
|
|
936
|
-
profiles:
|
|
937
|
-
|
|
938
|
-
profileId:
|
|
939
|
-
sortOrder:
|
|
940
|
-
isDefault:
|
|
941
|
-
hiddenFromHub:
|
|
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 =
|
|
946
|
-
name:
|
|
947
|
-
email:
|
|
948
|
-
phone:
|
|
949
|
-
notes:
|
|
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,
|