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