@mohasinac/appkit 4.16.0 → 4.17.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.
Files changed (33) hide show
  1. package/dist/client.d.ts +5 -3
  2. package/dist/client.js +5 -3
  3. package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +18 -6
  4. package/dist/features/admin/components/BrandQuickCreateForm.js +13 -5
  5. package/dist/features/admin/components/CategoryQuickCreateForm.js +13 -5
  6. package/dist/features/admin/schemas/small-forms.d.ts +54 -0
  7. package/dist/features/admin/schemas/small-forms.js +85 -0
  8. package/dist/features/auth/components/ForgotPasswordView.js +2 -1
  9. package/dist/features/auth/components/LoginForm.js +2 -1
  10. package/dist/features/auth/components/RegisterForm.js +2 -1
  11. package/dist/features/auth/components/ResetPasswordView.js +2 -1
  12. package/dist/features/consultation/components/ConsultationForm.js +3 -1
  13. package/dist/features/consultation/schemas/index.d.ts +4 -4
  14. package/dist/features/consultation/schemas/index.js +22 -5
  15. package/dist/features/contact/schemas/inquiry-forms.d.ts +13 -0
  16. package/dist/features/contact/schemas/inquiry-forms.js +34 -0
  17. package/dist/features/corporate/components/CorporateInquiryForm.js +3 -1
  18. package/dist/features/corporate/schemas/index.d.ts +4 -1
  19. package/dist/features/corporate/schemas/index.js +38 -8
  20. package/dist/features/events/components/EventRaffleEntryForm.js +3 -1
  21. package/dist/features/homepage/components/NewsletterBanner.js +3 -1
  22. package/dist/features/products/components/MakeOfferButton.js +1 -1
  23. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +27 -9
  24. package/dist/features/store-extensions/schemas/listing-template-form.d.ts +19 -1
  25. package/dist/features/store-extensions/schemas/listing-template-form.js +42 -1
  26. package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +19 -13
  27. package/dist/features/store-extensions/schemas/shipping-config-form.js +25 -2
  28. package/dist/features/store-extensions/schemas/store-category-form.d.ts +40 -0
  29. package/dist/features/store-extensions/schemas/store-category-form.js +28 -0
  30. package/dist/index.d.ts +3 -1
  31. package/dist/index.js +3 -1
  32. package/dist/schemas/registry.d.ts +8 -5
  33. package/package.json +1 -1
@@ -64,17 +64,19 @@ export declare const submitCorporateInquirySchema: z.ZodObject<{
64
64
  contactPerson: z.ZodString;
65
65
  designation: z.ZodOptional<z.ZodString>;
66
66
  email: z.ZodString;
67
- phone: z.ZodOptional<z.ZodString>;
67
+ phone: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
68
68
  units: z.ZodOptional<z.ZodNumber>;
69
69
  budgetPerUnit: z.ZodOptional<z.ZodNumber>;
70
70
  totalBudget: z.ZodOptional<z.ZodNumber>;
71
71
  deliveryDateRequired: z.ZodOptional<z.ZodString>;
72
72
  customBranding: z.ZodDefault<z.ZodBoolean>;
73
+ message: z.ZodOptional<z.ZodString>;
73
74
  }, "strip", z.ZodTypeAny, {
74
75
  email: string;
75
76
  companyName: string;
76
77
  contactPerson: string;
77
78
  customBranding: boolean;
79
+ message?: string | undefined;
78
80
  phone?: string | undefined;
79
81
  designation?: string | undefined;
80
82
  units?: number | undefined;
@@ -85,6 +87,7 @@ export declare const submitCorporateInquirySchema: z.ZodObject<{
85
87
  email: string;
86
88
  companyName: string;
87
89
  contactPerson: string;
90
+ message?: string | undefined;
88
91
  phone?: string | undefined;
89
92
  designation?: string | undefined;
90
93
  units?: number | undefined;
@@ -37,14 +37,44 @@ export const corporateInquirySchema = z.object({
37
37
  * Form schema for submitting a corporate inquiry.
38
38
  */
39
39
  export const submitCorporateInquirySchema = z.object({
40
- companyName: z.string().min(2),
41
- contactPerson: z.string().min(2),
42
- designation: z.string().optional(),
43
- email: z.string().email(),
44
- phone: z.string().optional(),
45
- units: z.number().int().positive().optional(),
46
- budgetPerUnit: z.number().optional(),
47
- totalBudget: z.number().optional(),
40
+ companyName: z.string().trim().min(2, "A company name is required.").max(160),
41
+ contactPerson: z.string().trim().min(2, "Who should we reply to?").max(120),
42
+ designation: z.string().trim().max(120).optional(),
43
+ email: z
44
+ .string()
45
+ .trim()
46
+ .toLowerCase()
47
+ .min(1, "An email address is required.")
48
+ .email("That doesn't look like an email address."),
49
+ // Indian mobile, tolerant of spacing and a country code. Optional — a
50
+ // reachable email is enough, and demanding a phone number loses enquiries.
51
+ phone: z
52
+ .string()
53
+ .trim()
54
+ .regex(/^(\+?91[-\s]?)?[6-9]\d{9}$/, "Enter a 10-digit Indian mobile number.")
55
+ .optional()
56
+ .or(z.literal("")),
57
+ /*
58
+ * 🛑 COERCED. These were `z.number()`, and this schema's own form sends
59
+ * strings — so every submission carrying a unit count failed to parse the
60
+ * moment anyone actually wired the form to the schema. Bounds are
61
+ * deliberately generous: they catch a mistyped figure, not a large order.
62
+ *
63
+ * A sales reply is priced from these two, and until now both were free text.
64
+ */
65
+ units: z.coerce
66
+ .number({ invalid_type_error: "Enter the number of units as a number." })
67
+ .int("Units must be a whole number.")
68
+ .positive("At least one unit.")
69
+ .max(1000000, "That looks like a typo — contact us directly for orders this large.")
70
+ .optional(),
71
+ budgetPerUnit: z.coerce
72
+ .number({ invalid_type_error: "Enter a per-unit budget as a number." })
73
+ .min(0, "A budget cannot be negative.")
74
+ .max(10000000, "That looks like a typo.")
75
+ .optional(),
76
+ totalBudget: z.coerce.number().min(0).optional(),
48
77
  deliveryDateRequired: z.string().optional(),
49
78
  customBranding: z.boolean().default(false),
79
+ message: z.string().trim().max(4000).optional(),
50
80
  });
@@ -6,6 +6,8 @@ import { Form } from "../../../ui/components/Form";
6
6
  import { FieldTextarea } from "../../../ui/forms/FieldTextarea";
7
7
  import { Div, Heading, Stack, Text } from "../../../ui";
8
8
  import { Badge } from "../../../ui/components/Badge";
9
+ import { raffleEntrySchema } from "../../contact/schemas/inquiry-forms";
10
+ import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
9
11
  const __P = {
10
12
  p6: "p-[var(--appkit-space-6)]",
11
13
  };
@@ -38,5 +40,5 @@ export function EventRaffleEntryForm({ prizeLabel, raffleType = "open_raffle", t
38
40
  : raffleType === "top_n_participants"
39
41
  ? `Earliest ${topN ?? "N"} entries qualify.`
40
42
  : "Spin the wheel to claim your prize.";
41
- return (_jsxs(Stack, { className: `${__P.p6}`, gap: "md", rounded: "2xl", border: "default", children: [prizeLabel ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Badge, { variant: "warning", children: "Prize" }), _jsx(Heading, { level: 2, weight: "bold", size: "2xl", children: prizeLabel })] })) : null, _jsx(Text, { size: "sm", color: "muted", children: eligibilityCopy }), submitted ? (_jsx(Div, { textSize: "sm", color: "success", surface: "success-surface", padding: "inline", rounded: "lg", children: "You're in. Check the leaderboard for results." })) : overLimit ? (_jsxs(Div, { textSize: "sm", color: "warning", surface: "warning-surface", padding: "inline", rounded: "lg", children: ["You've used all ", maxEntriesPerUser, " entry slot", maxEntriesPerUser === 1 ? "" : "s", " for this raffle."] })) : (_jsxs(Form, { onSubmit: handleSubmit, className: "space-y-[0.75rem]", children: [_jsx(FieldTextarea, { name: "message", label: "Message", hint: "optional \u2014 share why you'd like to win", value: message, onChange: setMessage, rows: 3, maxLength: 500, placeholder: "Share why you'd like to win", disabled: busy }), _jsx(Button, { type: "submit", variant: "primary", disabled: busy, children: busy ? "Submitting…" : "Enter raffle" })] }))] }));
43
+ return (_jsxs(Stack, { className: `${__P.p6}`, gap: "md", rounded: "2xl", border: "default", children: [prizeLabel ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Badge, { variant: "warning", children: "Prize" }), _jsx(Heading, { level: 2, weight: "bold", size: "2xl", children: prizeLabel })] })) : null, _jsx(Text, { size: "sm", color: "muted", children: eligibilityCopy }), submitted ? (_jsx(Div, { textSize: "sm", color: "success", surface: "success-surface", padding: "inline", rounded: "lg", children: "You're in. Check the leaderboard for results." })) : overLimit ? (_jsxs(Div, { textSize: "sm", color: "warning", surface: "warning-surface", padding: "inline", rounded: "lg", children: ["You've used all ", maxEntriesPerUser, " entry slot", maxEntriesPerUser === 1 ? "" : "s", " for this raffle."] })) : (_jsxs(Form, { schema: raffleEntrySchema, onSubmit: handleSubmit, className: "space-y-[0.75rem]", children: [_jsx(FormErrorSummary, {}), _jsx(FieldTextarea, { name: "message", label: "Message", hint: "optional \u2014 share why you'd like to win", value: message, onChange: setMessage, rows: 3, maxLength: 500, placeholder: "Share why you'd like to win", disabled: busy }), _jsx(Button, { type: "submit", variant: "primary", disabled: busy, children: busy ? "Submitting…" : "Enter raffle" })] }))] }));
42
44
  }
@@ -6,6 +6,8 @@ import { Button, Div, Heading, Span, Text } from "../../../ui";
6
6
  import { DynamicBgDiv } from "../../../ui/components/DynamicBgDiv";
7
7
  import { Form } from "../../../ui/components/Form";
8
8
  import { FieldInput } from "../../../ui/forms/FieldInput";
9
+ import { newsletterSubscribeSchema } from "../../admin/schemas/small-forms";
10
+ import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
9
11
  const CLS_ERROR_TEXT = "mt-3 text-[length:var(--appkit-text-sm)] text-error";
10
12
  export function NewsletterBanner({ onSubscribe, eyebrow = "STAY CONNECTED", heading = "Join Our Newsletter", subheading = "Get exclusive offers, new arrivals and more.", placeholder = "Enter your email", ctaLabel = "Subscribe", successMessage = "You're in! Check your inbox.", errorMessage = "Something went wrong. Please try again.", }) {
11
13
  const [email, setEmail] = useState("");
@@ -31,5 +33,5 @@ export function NewsletterBanner({ onSubscribe, eyebrow = "STAY CONNECTED", head
31
33
  setLoading(false);
32
34
  }
33
35
  }
34
- return (_jsxs(DynamicBgDiv, { background: "var(--newsletter-bg, linear-gradient(135deg, var(--appkit-color-primary) 0%, var(--appkit-color-secondary) 100%))", className: "py-[var(--appkit-space-24)] relative overflow-hidden", children: [_jsx(Div, { className: "pointer-events-none absolute -top-20 -right-20 h-60 w-60 border border-white/[0.06]", rounded: "full" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-16 -left-16 h-48 w-48 border border-dashed border-white/[0.08]", rounded: "full" }), _jsxs(Div, { className: "relative mx-auto max-w-2xl text-left sm:px-[1.5rem]", padding: "x-md", children: [_jsx(Span, { color: "inverse", size: "xs", weight: "semibold", className: "mb-4 inline-flex border border-white/20 tracking-widest /70", rounded: "full", padding: "pill-md", transform: "uppercase", children: eyebrow }), _jsx(Heading, { color: "inverse", level: 2, smSize: "4xl", size: "3xl", weight: "bold", children: heading }), _jsx(Text, { color: "inverse", className: "mt-4 /60", children: subheading }), success ? (_jsx(Text, { paddingY: "md", color: "inverse", className: "mt-8 border border-white/20 bg-[rgba(255,255,255,0.1)]", paddingX: "x-lg", size: "sm", weight: "semibold", rounded: "xl", children: successMessage })) : (_jsxs(Form, { onSubmit: handleSubmit, className: "mt-8 flex gap-[0.75rem]", children: [_jsx(FieldInput, { name: "email", type: "email", value: email, onChange: setEmail, placeholder: placeholder, required: true, className: "flex-1" }), _jsx(Button, { rounded: "xl", type: "submit", disabled: loading, variant: "primary", paddingX: "lg", textSize: "sm", className: "shrink-0 py-[0.75rem] font-[700] transition-opacity disabled:opacity-60 bg-[var(--color-yellow)] text-black", children: loading ? "…" : ctaLabel })] })), error && (_jsx(Text, { className: CLS_ERROR_TEXT, children: errorMessage })), _jsx(Text, { color: "inverse", className: "mt-4 /40", size: "xs", children: "No spam, ever. Unsubscribe anytime." })] })] }));
36
+ return (_jsxs(DynamicBgDiv, { background: "var(--newsletter-bg, linear-gradient(135deg, var(--appkit-color-primary) 0%, var(--appkit-color-secondary) 100%))", className: "py-[var(--appkit-space-24)] relative overflow-hidden", children: [_jsx(Div, { className: "pointer-events-none absolute -top-20 -right-20 h-60 w-60 border border-white/[0.06]", rounded: "full" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-16 -left-16 h-48 w-48 border border-dashed border-white/[0.08]", rounded: "full" }), _jsxs(Div, { className: "relative mx-auto max-w-2xl text-left sm:px-[1.5rem]", padding: "x-md", children: [_jsx(Span, { color: "inverse", size: "xs", weight: "semibold", className: "mb-4 inline-flex border border-white/20 tracking-widest /70", rounded: "full", padding: "pill-md", transform: "uppercase", children: eyebrow }), _jsx(Heading, { color: "inverse", level: 2, smSize: "4xl", size: "3xl", weight: "bold", children: heading }), _jsx(Text, { color: "inverse", className: "mt-4 /60", children: subheading }), success ? (_jsx(Text, { paddingY: "md", color: "inverse", className: "mt-8 border border-white/20 bg-[rgba(255,255,255,0.1)]", paddingX: "x-lg", size: "sm", weight: "semibold", rounded: "xl", children: successMessage })) : (_jsxs(Form, { schema: newsletterSubscribeSchema, onSubmit: handleSubmit, className: "mt-8 flex gap-[0.75rem]", children: [_jsx(FormErrorSummary, {}), _jsx(FieldInput, { name: "email", type: "email", value: email, onChange: setEmail, placeholder: placeholder, required: true, className: "flex-1" }), _jsx(Button, { rounded: "xl", type: "submit", disabled: loading, variant: "primary", paddingX: "lg", textSize: "sm", className: "shrink-0 py-[0.75rem] font-[700] transition-opacity disabled:opacity-60 bg-[var(--color-yellow)] text-black", children: loading ? "…" : ctaLabel })] })), error && (_jsx(Text, { className: CLS_ERROR_TEXT, children: errorMessage })), _jsx(Text, { color: "inverse", className: "mt-4 /40", size: "xs", children: "No spam, ever. Unsubscribe anytime." })] })] }));
35
37
  }
@@ -75,7 +75,7 @@ export function MakeOfferButton({ productId, listedPrice, currency, minOfferPerc
75
75
  if (state === "pending") {
76
76
  return (_jsxs(Div, { className: `${CLS_PENDING_BOX} ${className}`, children: [_jsx(Span, { size: "lg", children: "\u23F3" }), _jsx(Text, { className: CLS_PENDING_TITLE, children: "Offer Pending" }), _jsx(Text, { className: CLS_PENDING_BODY, children: "You already have an offer on this item. Check My Offers for updates." })] }));
77
77
  }
78
- return (_jsxs(_Fragment, { children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to make an offer. Please log in or create an account to continue." }), _jsx(Button, { variant: "ghost", size: "md", border: "strong", className: `w-full ${className}`, onClick: () => setState("open"), children: "Make Offer" }), _jsx(Modal, { isOpen: state === "open", onClose: () => setState("idle"), size: "md", title: "Make an offer", children: _jsx(Form, { onSubmit: (e) => e.preventDefault(), children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Listed at ", fmt(listedPrice), " \u00B7 Minimum offer: ", fmt(minOffer)] }), _jsx(FieldInput, { name: "offerAmount", type: "number", label: "Your offer amount", required: true, value: offerAmount, onChange: (value) => {
78
+ return (_jsxs(_Fragment, { children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to make an offer. Please log in or create an account to continue." }), _jsx(Button, { variant: "ghost", size: "md", border: "strong", className: `w-full ${className}`, onClick: () => setState("open"), children: "Make Offer" }), _jsx(Modal, { isOpen: state === "open", onClose: () => setState("idle"), size: "md", title: "Make an offer", children: _jsx(Form, { schema: schema, onSubmit: (e) => e.preventDefault(), children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Listed at ", fmt(listedPrice), " \u00B7 Minimum offer: ", fmt(minOffer)] }), _jsx(FieldInput, { name: "offerAmount", type: "number", label: "Your offer amount", required: true, value: offerAmount, onChange: (value) => {
79
79
  setOfferAmount(value);
80
80
  clearErrors();
81
81
  }, min: minOffer, max: listedPrice - 1, step: 1, "aria-label": "Offer amount" }), _jsxs(Text, { size: "xs", color: "faint", children: ["Must be between ", fmt(minOffer), " and ", fmt(listedPrice - 1)] }), _jsx(FieldInput, { name: "buyerNote", type: "text", label: "Note to seller (optional)", value: buyerNote, onChange: setBuyerNote, placeholder: "E.g. Bundle deal, long-time fan\u2026", maxLength: 300, "aria-label": "Note to seller" }), _jsx(Text, { size: "xs", color: "muted", children: "The seller will accept, decline, or suggest a counter price." }), _jsx(FormErrorSummary, {}), _jsxs(Stack, { gap: "xs", children: [_jsx(Button, { action: ACTIONS.PRODUCT["make-offer"], variant: "primary", size: "sm", type: "button", isLoading: isPending, disabled: isPending, onClick: () => submit(setFieldError, clearErrors), children: isPending ? "Sending…" : `Send offer of ${fmt(Number(offerAmount) || 0)}` }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => setState("idle"), disabled: isPending, children: "Cancel" })] })] })) }) })] }));
@@ -6,6 +6,9 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
6
6
  import { Alert, Badge, Button, Div, Form, FormActions, Heading, Input, Row, Section, Select, Stack, Text, Toggle, useToast } from "../../../ui";
7
7
  import { apiClient } from "../../../http";
8
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { analyticsAlertCreateSchema } from "../../store-extensions/schemas/analytics-forms";
10
+ import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
11
+ import { ValidationError } from "../../../errors/validation-error";
9
12
  const __P = {
10
13
  p5: "p-[var(--appkit-space-5)]",
11
14
  };
@@ -79,17 +82,29 @@ export function SellerAnalyticsAlertsView({ labels = {}, }) {
79
82
  const alerts = data ?? [];
80
83
  const createMutation = useApiMutation({
81
84
  mutationFn: async () => {
82
- const payload = {
85
+ /*
86
+ * Parsed against the SAME schema the route uses, before the request.
87
+ *
88
+ * This form used to hand-build its payload and included `scope: "seller"`
89
+ * — a field the route pins from the session and the schema does not
90
+ * declare. Once that schema became `.strict()`, every "Create Alert"
91
+ * became a 400, and nothing local could have caught it: the client had
92
+ * no schema to check against. Parsing here is the structural fix, not
93
+ * the deletion of one key.
94
+ */
95
+ const parsed = analyticsAlertCreateSchema.safeParse({
83
96
  label: draft.label,
84
97
  metric: draft.metric,
85
98
  operator: draft.operator,
86
- threshold: Number(draft.threshold),
87
- windowHours: Number(draft.windowHours),
99
+ threshold: draft.threshold,
100
+ windowHours: draft.windowHours,
88
101
  notifyChannels: draft.notifyChannels,
89
- scope: "seller",
90
102
  isActive: true,
91
- };
92
- return apiClient.post(SELLER_ENDPOINTS.ANALYTICS_ALERTS, payload);
103
+ });
104
+ if (!parsed.success) {
105
+ throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid alert", parsed.error.issues);
106
+ }
107
+ return apiClient.post(SELLER_ENDPOINTS.ANALYTICS_ALERTS, parsed.data);
93
108
  },
94
109
  onSuccess: () => {
95
110
  showToast("Alert created", "success");
@@ -97,7 +112,7 @@ export function SellerAnalyticsAlertsView({ labels = {}, }) {
97
112
  setShowForm(false);
98
113
  setDraft(EMPTY_DRAFT);
99
114
  },
100
- onError: () => showToast("Failed to create alert", "error"),
115
+ onError: (err) => showToast(err?.message ?? "Failed to create alert", "error"),
101
116
  });
102
117
  const toggleMutation = useApiMutation({
103
118
  mutationFn: async ({ id, isActive }) => apiClient.patch(SELLER_ENDPOINTS.ANALYTICS_ALERT_BY_ID(id), { isActive }),
@@ -122,7 +137,10 @@ export function SellerAnalyticsAlertsView({ labels = {}, }) {
122
137
  : [...d.notifyChannels, ch],
123
138
  }));
124
139
  }, []);
125
- return (_jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { justify: "between", children: [_jsx(Heading, { level: 2, size: "base", weight: "semibold", color: "primary", children: labels.title ?? "Analytics Alerts" }), _jsx(Button, { size: "sm", onClick: () => setShowForm((v) => !v), children: showForm ? "Cancel" : "+ New Alert" })] }), _jsx(Text, { size: "sm", color: "muted", children: "Get notified when a key metric crosses a threshold. Alerts check the metric over the selected window and fire through your chosen channels." }), showForm && (_jsxs(Section, { className: `${__P.p5} space-y-[1rem]`, rounded: "xl", border: "default", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", color: "primary", children: "Create Alert" }), _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); createMutation.mutate(); }, children: [_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Alert label", value: draft.label, onChange: (e) => setDraft((d) => ({ ...d, label: e.target.value })), placeholder: "e.g. Low daily orders", required: true }), _jsx(Select, { label: "Metric", value: draft.metric, onValueChange: (v) => setDraft((d) => ({ ...d, metric: v })), options: METRIC_OPTIONS }), _jsx(Select, { label: "Operator", value: draft.operator, onValueChange: (v) => setDraft((d) => ({ ...d, operator: v })), options: OPERATOR_OPTIONS }), _jsx(Input, { label: "Threshold", type: "number", value: draft.threshold, onChange: (e) => setDraft((d) => ({ ...d, threshold: e.target.value })), placeholder: "e.g. 10", required: true }), _jsx(Select, { label: "Check window", value: draft.windowHours, onValueChange: (v) => setDraft((d) => ({ ...d, windowHours: v })), options: WINDOW_OPTIONS })] }), _jsxs(Div, { className: "mt-3", children: [_jsx(Text, { className: "mb-2", color: "muted", size: "xs", weight: "medium", children: "Notify via" }), _jsx(Row, { gap: "sm", wrap: true, children: CHANNEL_OPTIONS.map((ch) => (_jsx(Button, { variant: "outline", type: "button", onClick: () => toggleChannel(ch.value), rounded: "full", paddingX: "sm", paddingY: "xs", textSize: "xs", className: draft.notifyChannels.includes(ch.value)
140
+ return (_jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { justify: "between", children: [_jsx(Heading, { level: 2, size: "base", weight: "semibold", color: "primary", children: labels.title ?? "Analytics Alerts" }), _jsx(Button, { size: "sm", onClick: () => setShowForm((v) => !v), children: showForm ? "Cancel" : "+ New Alert" })] }), _jsx(Text, { size: "sm", color: "muted", children: "Get notified when a key metric crosses a threshold. Alerts check the metric over the selected window and fire through your chosen channels." }), showForm && (_jsxs(Section, { className: `${__P.p5} space-y-[1rem]`, rounded: "xl", border: "default", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", color: "primary", children: "Create Alert" }), _jsxs(Form, { schema: analyticsAlertCreateSchema, onSubmit: (e) => {
141
+ e.preventDefault();
142
+ createMutation.mutate();
143
+ }, children: [_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Alert label", value: draft.label, onChange: (e) => setDraft((d) => ({ ...d, label: e.target.value })), placeholder: "e.g. Low daily orders", required: true }), _jsx(Select, { label: "Metric", value: draft.metric, onValueChange: (v) => setDraft((d) => ({ ...d, metric: v })), options: METRIC_OPTIONS }), _jsx(Select, { label: "Operator", value: draft.operator, onValueChange: (v) => setDraft((d) => ({ ...d, operator: v })), options: OPERATOR_OPTIONS }), _jsx(Input, { label: "Threshold", type: "number", value: draft.threshold, onChange: (e) => setDraft((d) => ({ ...d, threshold: e.target.value })), placeholder: "e.g. 10", required: true }), _jsx(Select, { label: "Check window", value: draft.windowHours, onValueChange: (v) => setDraft((d) => ({ ...d, windowHours: v })), options: WINDOW_OPTIONS })] }), _jsxs(Div, { className: "mt-3", children: [_jsx(Text, { className: "mb-2", color: "muted", size: "xs", weight: "medium", children: "Notify via" }), _jsx(Row, { gap: "sm", wrap: true, children: CHANNEL_OPTIONS.map((ch) => (_jsx(Button, { variant: "outline", type: "button", onClick: () => toggleChannel(ch.value), rounded: "full", paddingX: "sm", paddingY: "xs", textSize: "xs", className: draft.notifyChannels.includes(ch.value)
126
144
  ? "bg-[var(--appkit-color-primary)] text-white border-transparent"
127
- : "bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text-muted)] border-[var(--appkit-color-border)]", children: ch.label }, ch.value))) })] }), _jsx(FormActions, { align: "right", className: "mt-4", children: _jsx(Button, { type: "submit", isLoading: createMutation.isPending, children: createMutation.isPending ? "Creating…" : "Create Alert" }) })] })] })), isLoading && (_jsx(Stack, { gap: "sm", children: [1, 2, 3].map((i) => (_jsx(Div, { className: "h-16 animate-pulse", surface: "subtle", rounded: "lg" }, i))) })), !isLoading && alerts.length === 0 && !showForm && (_jsx(Alert, { variant: "info", children: "No alerts configured. Create an alert to get notified when a key metric crosses a threshold." })), !isLoading && alerts.length > 0 && (_jsx(Stack, { gap: "3", children: alerts.map((alert) => (_jsx(AlertCard, { alert: alert, onToggle: (id, isActive) => toggleMutation.mutate({ id, isActive }), onDelete: (id) => deleteMutation.mutate(id) }, alert.id))) }))] }));
145
+ : "bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text-muted)] border-[var(--appkit-color-border)]", children: ch.label }, ch.value))) })] }), _jsx(FormErrorSummary, {}), _jsx(FormActions, { align: "right", className: "mt-4", children: _jsx(Button, { type: "submit", isLoading: createMutation.isPending, children: createMutation.isPending ? "Creating…" : "Create Alert" }) })] })] })), isLoading && (_jsx(Stack, { gap: "sm", children: [1, 2, 3].map((i) => (_jsx(Div, { className: "h-16 animate-pulse", surface: "subtle", rounded: "lg" }, i))) })), !isLoading && alerts.length === 0 && !showForm && (_jsx(Alert, { variant: "info", children: "No alerts configured. Create an alert to get notified when a key metric crosses a threshold." })), !isLoading && alerts.length > 0 && (_jsx(Stack, { gap: "3", children: alerts.map((alert) => (_jsx(AlertCard, { alert: alert, onToggle: (id, isActive) => toggleMutation.mutate({ id, isActive }), onDelete: (id) => deleteMutation.mutate(id) }, alert.id))) }))] }));
128
146
  }
@@ -1,4 +1,8 @@
1
1
  import { z } from "zod";
2
+ export declare const LISTING_TEMPLATE_TYPE_OPTIONS: {
3
+ value: "art" | "stickers" | "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live" | "bundle";
4
+ label: string;
5
+ }[];
2
6
  /**
3
7
  * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
4
8
  * by schema instance and every zod wrapper returns a new one.
@@ -54,6 +58,20 @@ export declare const listingTemplateCreateSchema: z.ZodObject<{
54
58
  defaults?: Record<string, import("../../..").FirestoreValue> | undefined;
55
59
  }>;
56
60
  /** Update contract — every field optional, and the same ownership fields absent. */
61
+ /**
62
+ * Update contract.
63
+ *
64
+ * `.strict()` as well as `.partial()`: the edit page used to PATCH the whole
65
+ * loaded document — `id`, `storeId`, `createdAt`, `updatedAt`, `usageCount`
66
+ * and all — and a non-strict schema silently stripped them, which reads as a
67
+ * clean save while quietly discarding half the payload. The page now sends
68
+ * only the editable fields, so a stray key here is a real mistake and should
69
+ * be a 400.
70
+ *
71
+ * `listingType` remains accepted-but-optional rather than removed: it is not
72
+ * editable in the UI (changing a template's type would invalidate every
73
+ * default stored against it), but the create path shares this shape.
74
+ */
57
75
  export declare const listingTemplateUpdateSchema: z.ZodObject<{
58
76
  name: z.ZodOptional<z.ZodString>;
59
77
  description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -61,7 +79,7 @@ export declare const listingTemplateUpdateSchema: z.ZodObject<{
61
79
  defaults: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<import("../../..").FirestoreValue, z.ZodTypeDef, import("../../..").FirestoreValue>>>;
62
80
  isShared: z.ZodOptional<z.ZodBoolean>;
63
81
  isActive: z.ZodOptional<z.ZodBoolean>;
64
- }, "strip", z.ZodTypeAny, {
82
+ }, "strict", z.ZodTypeAny, {
65
83
  name?: string | undefined;
66
84
  listingType?: "art" | "stickers" | "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live" | "bundle" | undefined;
67
85
  description?: string | undefined;
@@ -41,6 +41,33 @@ import { firestoreValueSchema } from "../../../schemas/firestore-value";
41
41
  * templates while `bundle` lingered.
42
42
  */
43
43
  const listingTemplateTypeSchema = z.enum(LISTING_TEMPLATE_TYPES);
44
+ /**
45
+ * Dropdown options, derived from the same runtime array the schema validates
46
+ * against.
47
+ *
48
+ * 🛑 The New Template page carried its own hand-written list of EIGHT of these
49
+ * ten. `art` and `stickers` were added to the union but not to that literal,
50
+ * so a seller could not create a template for either — the same defect the
51
+ * homepage-section create route had (21 values against a union of 25), one
52
+ * domain over. Labels are keyed `Record<ListingTemplateType, string>`, so a
53
+ * new type is a compile error here rather than a missing dropdown entry.
54
+ */
55
+ const LISTING_TEMPLATE_TYPE_LABEL = {
56
+ standard: "Standard",
57
+ auction: "Auction",
58
+ "pre-order": "Pre-order",
59
+ "prize-draw": "Prize draw",
60
+ bundle: "Bundle",
61
+ classified: "Classified",
62
+ "digital-code": "Digital code",
63
+ live: "Live item",
64
+ art: "Art print",
65
+ stickers: "Sticker sheet",
66
+ };
67
+ export const LISTING_TEMPLATE_TYPE_OPTIONS = LISTING_TEMPLATE_TYPES.map((value) => ({
68
+ value,
69
+ label: LISTING_TEMPLATE_TYPE_LABEL[value],
70
+ }));
44
71
  /**
45
72
  * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
46
73
  * by schema instance and every zod wrapper returns a new one.
@@ -84,4 +111,18 @@ export const listingTemplateCreateSchema = listingTemplateFormSchema.partial({
84
111
  defaults: true,
85
112
  });
86
113
  /** Update contract — every field optional, and the same ownership fields absent. */
87
- export const listingTemplateUpdateSchema = listingTemplateFormSchema.partial();
114
+ /**
115
+ * Update contract.
116
+ *
117
+ * `.strict()` as well as `.partial()`: the edit page used to PATCH the whole
118
+ * loaded document — `id`, `storeId`, `createdAt`, `updatedAt`, `usageCount`
119
+ * and all — and a non-strict schema silently stripped them, which reads as a
120
+ * clean save while quietly discarding half the payload. The page now sends
121
+ * only the editable fields, so a stray key here is a real mistake and should
122
+ * be a 400.
123
+ *
124
+ * `listingType` remains accepted-but-optional rather than removed: it is not
125
+ * editable in the UI (changing a template's type would invalidate every
126
+ * default stored against it), but the create path shares this shape.
127
+ */
128
+ export const listingTemplateUpdateSchema = listingTemplateFormSchema.partial().strict();
@@ -1,11 +1,17 @@
1
1
  import { z } from "zod";
2
+ import type { ShippingMethod } from "./firestore";
3
+ /** Dropdown options for any surface that picks a shipping method. */
4
+ export declare const SHIPPING_METHOD_OPTIONS: {
5
+ value: ShippingMethod;
6
+ label: string;
7
+ }[];
2
8
  /**
3
9
  * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
4
10
  * by schema instance and every zod wrapper returns a new one.
5
11
  */
6
12
  export declare const shippingConfigFormSchema: z.ZodEffects<z.ZodObject<{
7
13
  label: z.ZodString;
8
- method: z.ZodEnum<["free", "flat", "weight", "express", "pickup", "custom"]>;
14
+ method: z.ZodEnum<[ShippingMethod, ...ShippingMethod[]]>;
9
15
  flatRate: z.ZodOptional<z.ZodNumber>;
10
16
  pricePerKg: z.ZodOptional<z.ZodNumber>;
11
17
  freeAbove: z.ZodOptional<z.ZodNumber>;
@@ -17,7 +23,7 @@ export declare const shippingConfigFormSchema: z.ZodEffects<z.ZodObject<{
17
23
  label: string;
18
24
  isDefault: boolean;
19
25
  isActive: boolean;
20
- method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
26
+ method: ShippingMethod;
21
27
  estimatedDays?: number | undefined;
22
28
  freeAbove?: number | undefined;
23
29
  flatRate?: number | undefined;
@@ -27,7 +33,7 @@ export declare const shippingConfigFormSchema: z.ZodEffects<z.ZodObject<{
27
33
  label: string;
28
34
  isDefault: boolean;
29
35
  isActive: boolean;
30
- method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
36
+ method: ShippingMethod;
31
37
  estimatedDays?: number | undefined;
32
38
  freeAbove?: number | undefined;
33
39
  flatRate?: number | undefined;
@@ -37,7 +43,7 @@ export declare const shippingConfigFormSchema: z.ZodEffects<z.ZodObject<{
37
43
  label: string;
38
44
  isDefault: boolean;
39
45
  isActive: boolean;
40
- method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
46
+ method: ShippingMethod;
41
47
  estimatedDays?: number | undefined;
42
48
  freeAbove?: number | undefined;
43
49
  flatRate?: number | undefined;
@@ -47,7 +53,7 @@ export declare const shippingConfigFormSchema: z.ZodEffects<z.ZodObject<{
47
53
  label: string;
48
54
  isDefault: boolean;
49
55
  isActive: boolean;
50
- method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
56
+ method: ShippingMethod;
51
57
  estimatedDays?: number | undefined;
52
58
  freeAbove?: number | undefined;
53
59
  flatRate?: number | undefined;
@@ -58,7 +64,7 @@ export type ShippingConfigFormValues = z.infer<typeof shippingConfigFormSchema>;
58
64
  /** `storeId` is absent — the route sets it from the session, never the body. */
59
65
  export declare const shippingConfigCreateSchema: z.ZodEffects<z.ZodObject<{
60
66
  label: z.ZodString;
61
- method: z.ZodEnum<["free", "flat", "weight", "express", "pickup", "custom"]>;
67
+ method: z.ZodEnum<[ShippingMethod, ...ShippingMethod[]]>;
62
68
  flatRate: z.ZodOptional<z.ZodNumber>;
63
69
  pricePerKg: z.ZodOptional<z.ZodNumber>;
64
70
  freeAbove: z.ZodOptional<z.ZodNumber>;
@@ -70,7 +76,7 @@ export declare const shippingConfigCreateSchema: z.ZodEffects<z.ZodObject<{
70
76
  label: string;
71
77
  isDefault: boolean;
72
78
  isActive: boolean;
73
- method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
79
+ method: ShippingMethod;
74
80
  estimatedDays?: number | undefined;
75
81
  freeAbove?: number | undefined;
76
82
  flatRate?: number | undefined;
@@ -80,7 +86,7 @@ export declare const shippingConfigCreateSchema: z.ZodEffects<z.ZodObject<{
80
86
  label: string;
81
87
  isDefault: boolean;
82
88
  isActive: boolean;
83
- method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
89
+ method: ShippingMethod;
84
90
  estimatedDays?: number | undefined;
85
91
  freeAbove?: number | undefined;
86
92
  flatRate?: number | undefined;
@@ -90,7 +96,7 @@ export declare const shippingConfigCreateSchema: z.ZodEffects<z.ZodObject<{
90
96
  label: string;
91
97
  isDefault: boolean;
92
98
  isActive: boolean;
93
- method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
99
+ method: ShippingMethod;
94
100
  estimatedDays?: number | undefined;
95
101
  freeAbove?: number | undefined;
96
102
  flatRate?: number | undefined;
@@ -100,7 +106,7 @@ export declare const shippingConfigCreateSchema: z.ZodEffects<z.ZodObject<{
100
106
  label: string;
101
107
  isDefault: boolean;
102
108
  isActive: boolean;
103
- method: "flat" | "weight" | "custom" | "free" | "express" | "pickup";
109
+ method: ShippingMethod;
104
110
  estimatedDays?: number | undefined;
105
111
  freeAbove?: number | undefined;
106
112
  flatRate?: number | undefined;
@@ -114,7 +120,7 @@ export declare const shippingConfigCreateSchema: z.ZodEffects<z.ZodObject<{
114
120
  */
115
121
  export declare const shippingConfigUpdateSchema: z.ZodObject<{
116
122
  label: z.ZodOptional<z.ZodString>;
117
- method: z.ZodOptional<z.ZodEnum<["free", "flat", "weight", "express", "pickup", "custom"]>>;
123
+ method: z.ZodOptional<z.ZodEnum<[ShippingMethod, ...ShippingMethod[]]>>;
118
124
  flatRate: z.ZodOptional<z.ZodNumber>;
119
125
  pricePerKg: z.ZodOptional<z.ZodNumber>;
120
126
  freeAbove: z.ZodOptional<z.ZodNumber>;
@@ -127,7 +133,7 @@ export declare const shippingConfigUpdateSchema: z.ZodObject<{
127
133
  label?: string | undefined;
128
134
  isDefault?: boolean | undefined;
129
135
  isActive?: boolean | undefined;
130
- method?: "flat" | "weight" | "custom" | "free" | "express" | "pickup" | undefined;
136
+ method?: ShippingMethod | undefined;
131
137
  estimatedDays?: number | undefined;
132
138
  freeAbove?: number | undefined;
133
139
  flatRate?: number | undefined;
@@ -138,7 +144,7 @@ export declare const shippingConfigUpdateSchema: z.ZodObject<{
138
144
  label?: string | undefined;
139
145
  isDefault?: boolean | undefined;
140
146
  isActive?: boolean | undefined;
141
- method?: "flat" | "weight" | "custom" | "free" | "express" | "pickup" | undefined;
147
+ method?: ShippingMethod | undefined;
142
148
  estimatedDays?: number | undefined;
143
149
  freeAbove?: number | undefined;
144
150
  flatRate?: number | undefined;
@@ -26,8 +26,31 @@
26
26
  */
27
27
  import { z } from "zod";
28
28
  import { annotate } from "../../shell/field-ui-meta";
29
- /** Mirrors `ShippingMethod` in ./firestore. */
30
- const shippingMethodSchema = z.enum(["free", "flat", "weight", "express", "pickup", "custom"]);
29
+ /*
30
+ * Method labels, keyed on the real union.
31
+ *
32
+ * This was a hand-written `z.enum([...])` with a comment saying it "mirrors
33
+ * ShippingMethod" — the shape that always eventually stops mirroring. Keyed
34
+ * `Record<ShippingMethod, string>`, a value added to the union is now a
35
+ * compile error here, and the enum and the dropdown options both derive from
36
+ * this one map rather than from two separate literals (the edit page carried
37
+ * its own copy of the six options inline).
38
+ */
39
+ const SHIPPING_METHOD_LABEL = {
40
+ free: "Free",
41
+ flat: "Flat rate",
42
+ weight: "By weight",
43
+ express: "Express",
44
+ pickup: "Pickup",
45
+ custom: "Custom",
46
+ };
47
+ const SHIPPING_METHODS = Object.keys(SHIPPING_METHOD_LABEL);
48
+ const shippingMethodSchema = z.enum(SHIPPING_METHODS);
49
+ /** Dropdown options for any surface that picks a shipping method. */
50
+ export const SHIPPING_METHOD_OPTIONS = SHIPPING_METHODS.map((value) => ({
51
+ value,
52
+ label: SHIPPING_METHOD_LABEL[value],
53
+ }));
31
54
  /** A non-negative money/threshold amount, coerced from the form's string. */
32
55
  const amount = (label) => z.coerce.number({ invalid_type_error: `${label} must be a number.` }).min(0, `${label} cannot be negative.`);
33
56
  /**
@@ -10,17 +10,20 @@ export declare const storeCategoryFormSchema: z.ZodObject<{
10
10
  description: z.ZodOptional<z.ZodString>;
11
11
  coverImageUrl: z.ZodOptional<z.ZodString>;
12
12
  displayOrder: z.ZodNumber;
13
+ isActive: z.ZodOptional<z.ZodBoolean>;
13
14
  }, "strip", z.ZodTypeAny, {
14
15
  label: string;
15
16
  displayOrder: number;
16
17
  slug?: string | undefined;
17
18
  description?: string | undefined;
19
+ isActive?: boolean | undefined;
18
20
  coverImageUrl?: string | undefined;
19
21
  }, {
20
22
  label: string;
21
23
  displayOrder: number;
22
24
  slug?: string | undefined;
23
25
  description?: string | undefined;
26
+ isActive?: boolean | undefined;
24
27
  coverImageUrl?: string | undefined;
25
28
  }>;
26
29
  export type StoreCategoryFormValues = z.infer<typeof storeCategoryFormSchema>;
@@ -37,6 +40,7 @@ export declare const storeCategoryCreateSchema: z.ZodObject<{
37
40
  description: z.ZodOptional<z.ZodString>;
38
41
  coverImageUrl: z.ZodOptional<z.ZodString>;
39
42
  displayOrder: z.ZodNumber;
43
+ isActive: z.ZodOptional<z.ZodBoolean>;
40
44
  } & {
41
45
  slug: z.ZodString;
42
46
  }, "strip", z.ZodTypeAny, {
@@ -44,11 +48,47 @@ export declare const storeCategoryCreateSchema: z.ZodObject<{
44
48
  slug: string;
45
49
  displayOrder: number;
46
50
  description?: string | undefined;
51
+ isActive?: boolean | undefined;
47
52
  coverImageUrl?: string | undefined;
48
53
  }, {
49
54
  label: string;
50
55
  slug: string;
51
56
  displayOrder: number;
52
57
  description?: string | undefined;
58
+ isActive?: boolean | undefined;
59
+ coverImageUrl?: string | undefined;
60
+ }>;
61
+ /**
62
+ * Update contract.
63
+ *
64
+ * `.partial()` so a PATCH may carry one field, and `.strict()` so an unknown
65
+ * key is a 400 rather than a silent write. `slug` is deliberately still
66
+ * accepted here, unlike on categories/brands/bundles: a STOREFRONT category is
67
+ * seller-local organisation with no public permalink of its own, so renaming
68
+ * one breaks no inbound link.
69
+ *
70
+ * `storeId` is absent — the route sets it from the session, so a seller cannot
71
+ * move a category into another store.
72
+ */
73
+ export declare const storeCategoryUpdateSchema: z.ZodObject<{
74
+ label: z.ZodOptional<z.ZodString>;
75
+ slug: z.ZodOptional<z.ZodOptional<z.ZodString>>;
76
+ description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
77
+ coverImageUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
78
+ displayOrder: z.ZodOptional<z.ZodNumber>;
79
+ isActive: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
80
+ }, "strict", z.ZodTypeAny, {
81
+ label?: string | undefined;
82
+ slug?: string | undefined;
83
+ description?: string | undefined;
84
+ isActive?: boolean | undefined;
85
+ displayOrder?: number | undefined;
86
+ coverImageUrl?: string | undefined;
87
+ }, {
88
+ label?: string | undefined;
89
+ slug?: string | undefined;
90
+ description?: string | undefined;
91
+ isActive?: boolean | undefined;
92
+ displayOrder?: number | undefined;
53
93
  coverImageUrl?: string | undefined;
54
94
  }>;
@@ -59,6 +59,21 @@ export const storeCategoryFormSchema = z.object({
59
59
  section: "media", sectionLabel: "Media", order: 1, row: "full", kind: "media",
60
60
  }),
61
61
  displayOrder: annotate(z.coerce.number().int("Display order must be a whole number.").min(0, "Display order cannot be negative."), { section: "visibility", sectionLabel: "Visibility", order: 1, row: "pair" }),
62
+ /*
63
+ * 🛑 This field was MISSING, and `StoreCategoryDocument.isActive` is
64
+ * required.
65
+ *
66
+ * The edit page has always rendered an "Active" toggle and sent its value in
67
+ * the body; the schema simply did not name it. It surfaced the moment that
68
+ * page was typed against this schema — which is the argument for wiring a
69
+ * schema to its form rather than only to its route.
70
+ *
71
+ * Optional here (not `.min`) because a PATCH may legitimately omit it; the
72
+ * document default is `true`.
73
+ */
74
+ isActive: annotate(z.boolean().optional(), {
75
+ section: "visibility", order: 2, row: "quarter",
76
+ }),
62
77
  });
63
78
  /**
64
79
  * The server's create contract.
@@ -71,3 +86,16 @@ export const storeCategoryFormSchema = z.object({
71
86
  export const storeCategoryCreateSchema = storeCategoryFormSchema.extend({
72
87
  slug: z.string().min(1, "Slug is required").max(80),
73
88
  });
89
+ /**
90
+ * Update contract.
91
+ *
92
+ * `.partial()` so a PATCH may carry one field, and `.strict()` so an unknown
93
+ * key is a 400 rather than a silent write. `slug` is deliberately still
94
+ * accepted here, unlike on categories/brands/bundles: a STOREFRONT category is
95
+ * seller-local organisation with no public permalink of its own, so renaming
96
+ * one breaks no inbound link.
97
+ *
98
+ * `storeId` is absent — the route sets it from the session, so a seller cannot
99
+ * move a category into another store.
100
+ */
101
+ export const storeCategoryUpdateSchema = storeCategoryFormSchema.partial().strict();
package/dist/index.d.ts CHANGED
@@ -493,7 +493,7 @@ export type { ResolvedUser } from "./security/index";
493
493
  export type { RoleDefinition } from "./security/index";
494
494
  export type { UserRole } from "./security/index";
495
495
  export { AddressesRepository } from "./repositories/index";
496
- export { type PayoutMethodType, type PayoutMethodDocument, PAYOUT_METHODS_COLLECTION, PAYOUT_METHOD_INDEXED_FIELDS, DEFAULT_PAYOUT_METHOD_DATA, type ShippingMethod as ShippingConfigMethod, type ShippingConfigDocument, SHIPPING_CONFIGS_COLLECTION, SHIPPING_CONFIG_INDEXED_FIELDS, DEFAULT_SHIPPING_CONFIG_DATA, type AnalyticsScope, type AnalyticsCardType, type AnalyticsCardDocument, ANALYTICS_CARDS_COLLECTION, ANALYTICS_CARD_INDEXED_FIELDS, DEFAULT_ANALYTICS_CARD_DATA, type AlertOperator, type AnalyticsAlertDocument, ANALYTICS_ALERTS_COLLECTION, ANALYTICS_ALERT_INDEXED_FIELDS, DEFAULT_ANALYTICS_ALERT_DATA, type StoreCategoryDocument, storeCategoryFormSchema, storeCategoryCreateSchema, type StoreCategoryFormValues, listingTemplateFormSchema, listingTemplateCreateSchema, listingTemplateUpdateSchema, type ListingTemplateFormValues, LISTING_TEMPLATE_TYPES, payoutMethodFormSchema, payoutMethodCreateSchema, payoutMethodUpdateSchema, type PayoutMethodFormValues, shippingConfigFormSchema, shippingConfigCreateSchema, shippingConfigUpdateSchema, type ShippingConfigFormValues, customRoleFormSchema, customRoleCreateSchema, customRoleUpdateSchema, isKnownPermission, type CustomRoleFormValues, moderationReviewFormSchema, moderationReviewUpdateSchema, MODERATION_REVIEWER_STATUSES, type ModerationReviewFormValues, reportReviewFormSchema, reportReviewUpdateSchema, REPORT_REVIEWER_STATUSES, REPORT_TERMINAL_STATUSES, type ReportReviewFormValues, reportCreateSchema, type ReportCreateValues, itemRequestCreateSchema, type ItemRequestCreateValues, REPORT_ENTITY_TYPES, REPORT_REASONS, analyticsAlertCreateSchema, type AnalyticsAlertCreateValues, analyticsCardCreateSchema, type AnalyticsCardCreateValues, ANALYTICS_ALERT_OPERATORS, ANALYTICS_CARD_TYPES, storeGoogleConfigUpdateSchema, type StoreGoogleConfigFormValues, toSellerGoogleConfig, type SellerGoogleConfig, adminNotificationCreateSchema, type AdminNotificationCreateValues, ADMIN_NOTIFICATION_CATEGORIES, ADMIN_NOTIFICATION_SEVERITIES, STORE_CATEGORIES_COLLECTION, STORE_CATEGORY_INDEXED_FIELDS, DEFAULT_STORE_CATEGORY_DATA, type ListingTemplateType, type ListingTemplateDocument, LISTING_TEMPLATES_COLLECTION, LISTING_TEMPLATE_INDEXED_FIELDS, DEFAULT_LISTING_TEMPLATE_DATA, type ModerationStatus, type ModerationMediaType, type ModerationQueueDocument, MODERATION_QUEUE_COLLECTION, MODERATION_QUEUE_INDEXED_FIELDS, DEFAULT_MODERATION_QUEUE_DATA, type ReportEntityType, type ReportStatus, type ReportReason, type ReportDocument, REPORTS_COLLECTION, REPORT_INDEXED_FIELDS, DEFAULT_REPORT_DATA, type AdminNotificationCategory, type AdminNotificationDocument, ADMIN_NOTIFICATIONS_COLLECTION, ADMIN_NOTIFICATION_INDEXED_FIELDS, DEFAULT_ADMIN_NOTIFICATION_DATA, type ItemRequestStatus, type ItemRequestReply, type ItemRequestDocument, ITEM_REQUESTS_COLLECTION, ITEM_REQUEST_INDEXED_FIELDS, DEFAULT_ITEM_REQUEST_DATA, type StoreGoogleConfigDocument, STORE_GOOGLE_CONFIG_COLLECTION, STORE_GOOGLE_CONFIG_INDEXED_FIELDS, DEFAULT_STORE_GOOGLE_CONFIG_DATA, type StoreWhatsAppConfigDocument, STORE_WHATSAPP_CONFIG_COLLECTION, STORE_WHATSAPP_CONFIG_INDEXED_FIELDS, DEFAULT_STORE_WHATSAPP_CONFIG_DATA, type CustomRoleDocument, CUSTOM_ROLES_COLLECTION, CUSTOM_ROLE_INDEXED_FIELDS, DEFAULT_CUSTOM_ROLE_DATA, type RoleOverrideDocument, ROLE_OVERRIDES_COLLECTION, ROLE_OVERRIDE_INDEXED_FIELDS, DEFAULT_ROLE_OVERRIDE_DATA, PayoutMethodsRepository, ShippingConfigsRepository, AnalyticsCardsRepository, AnalyticsAlertsRepository, StoreCategoriesRepository, ListingTemplatesRepository, ModerationQueueRepository, ReportsRepository, AdminNotificationsRepository, ItemRequestsRepository, StoreGoogleConfigRepository, StoreWhatsAppConfigRepository, CustomRolesRepository, RoleOverridesRepository, payoutMethodsRepository, shippingConfigsRepository, analyticsCardsRepository, analyticsAlertsRepository, storeCategoriesRepository, listingTemplatesRepository, moderationQueueRepository, reportsRepository, adminNotificationsRepository, itemRequestsRepository, storeGoogleConfigRepository, storeWhatsAppConfigRepository, customRolesRepository, roleOverridesRepository, } from "./features/store-extensions/index";
496
+ export { type PayoutMethodType, type PayoutMethodDocument, PAYOUT_METHODS_COLLECTION, PAYOUT_METHOD_INDEXED_FIELDS, DEFAULT_PAYOUT_METHOD_DATA, type ShippingMethod as ShippingConfigMethod, type ShippingConfigDocument, SHIPPING_CONFIGS_COLLECTION, SHIPPING_CONFIG_INDEXED_FIELDS, DEFAULT_SHIPPING_CONFIG_DATA, type AnalyticsScope, type AnalyticsCardType, type AnalyticsCardDocument, ANALYTICS_CARDS_COLLECTION, ANALYTICS_CARD_INDEXED_FIELDS, DEFAULT_ANALYTICS_CARD_DATA, type AlertOperator, type AnalyticsAlertDocument, ANALYTICS_ALERTS_COLLECTION, ANALYTICS_ALERT_INDEXED_FIELDS, DEFAULT_ANALYTICS_ALERT_DATA, type StoreCategoryDocument, storeCategoryFormSchema, storeCategoryCreateSchema, storeCategoryUpdateSchema, type StoreCategoryFormValues, listingTemplateFormSchema, listingTemplateCreateSchema, listingTemplateUpdateSchema, type ListingTemplateFormValues, LISTING_TEMPLATE_TYPES, LISTING_TEMPLATE_TYPE_OPTIONS, payoutMethodFormSchema, payoutMethodCreateSchema, payoutMethodUpdateSchema, type PayoutMethodFormValues, shippingConfigFormSchema, SHIPPING_METHOD_OPTIONS, shippingConfigCreateSchema, shippingConfigUpdateSchema, type ShippingConfigFormValues, customRoleFormSchema, customRoleCreateSchema, customRoleUpdateSchema, isKnownPermission, type CustomRoleFormValues, moderationReviewFormSchema, moderationReviewUpdateSchema, MODERATION_REVIEWER_STATUSES, type ModerationReviewFormValues, reportReviewFormSchema, reportReviewUpdateSchema, REPORT_REVIEWER_STATUSES, REPORT_TERMINAL_STATUSES, type ReportReviewFormValues, reportCreateSchema, type ReportCreateValues, itemRequestCreateSchema, type ItemRequestCreateValues, REPORT_ENTITY_TYPES, REPORT_REASONS, analyticsAlertCreateSchema, type AnalyticsAlertCreateValues, analyticsCardCreateSchema, type AnalyticsCardCreateValues, ANALYTICS_ALERT_OPERATORS, ANALYTICS_CARD_TYPES, storeGoogleConfigUpdateSchema, type StoreGoogleConfigFormValues, toSellerGoogleConfig, type SellerGoogleConfig, adminNotificationCreateSchema, type AdminNotificationCreateValues, ADMIN_NOTIFICATION_CATEGORIES, ADMIN_NOTIFICATION_SEVERITIES, STORE_CATEGORIES_COLLECTION, STORE_CATEGORY_INDEXED_FIELDS, DEFAULT_STORE_CATEGORY_DATA, type ListingTemplateType, type ListingTemplateDocument, LISTING_TEMPLATES_COLLECTION, LISTING_TEMPLATE_INDEXED_FIELDS, DEFAULT_LISTING_TEMPLATE_DATA, type ModerationStatus, type ModerationMediaType, type ModerationQueueDocument, MODERATION_QUEUE_COLLECTION, MODERATION_QUEUE_INDEXED_FIELDS, DEFAULT_MODERATION_QUEUE_DATA, type ReportEntityType, type ReportStatus, type ReportReason, type ReportDocument, REPORTS_COLLECTION, REPORT_INDEXED_FIELDS, DEFAULT_REPORT_DATA, type AdminNotificationCategory, type AdminNotificationDocument, ADMIN_NOTIFICATIONS_COLLECTION, ADMIN_NOTIFICATION_INDEXED_FIELDS, DEFAULT_ADMIN_NOTIFICATION_DATA, type ItemRequestStatus, type ItemRequestReply, type ItemRequestDocument, ITEM_REQUESTS_COLLECTION, ITEM_REQUEST_INDEXED_FIELDS, DEFAULT_ITEM_REQUEST_DATA, type StoreGoogleConfigDocument, STORE_GOOGLE_CONFIG_COLLECTION, STORE_GOOGLE_CONFIG_INDEXED_FIELDS, DEFAULT_STORE_GOOGLE_CONFIG_DATA, type StoreWhatsAppConfigDocument, STORE_WHATSAPP_CONFIG_COLLECTION, STORE_WHATSAPP_CONFIG_INDEXED_FIELDS, DEFAULT_STORE_WHATSAPP_CONFIG_DATA, type CustomRoleDocument, CUSTOM_ROLES_COLLECTION, CUSTOM_ROLE_INDEXED_FIELDS, DEFAULT_CUSTOM_ROLE_DATA, type RoleOverrideDocument, ROLE_OVERRIDES_COLLECTION, ROLE_OVERRIDE_INDEXED_FIELDS, DEFAULT_ROLE_OVERRIDE_DATA, PayoutMethodsRepository, ShippingConfigsRepository, AnalyticsCardsRepository, AnalyticsAlertsRepository, StoreCategoriesRepository, ListingTemplatesRepository, ModerationQueueRepository, ReportsRepository, AdminNotificationsRepository, ItemRequestsRepository, StoreGoogleConfigRepository, StoreWhatsAppConfigRepository, CustomRolesRepository, RoleOverridesRepository, payoutMethodsRepository, shippingConfigsRepository, analyticsCardsRepository, analyticsAlertsRepository, storeCategoriesRepository, listingTemplatesRepository, moderationQueueRepository, reportsRepository, adminNotificationsRepository, itemRequestsRepository, storeGoogleConfigRepository, storeWhatsAppConfigRepository, customRolesRepository, roleOverridesRepository, } from "./features/store-extensions/index";
497
497
  export { BaseRepository } from "./repositories/index";
498
498
  export { BlogRepository } from "./repositories/index";
499
499
  export { CartRepository } from "./repositories/index";
@@ -3284,3 +3284,5 @@ export { withRetry } from "./http/retry";
3284
3284
  export { normalizeError, getErrorMessage, isApiNormalized, isAppNormalized, isFirebaseAuthNormalized, isFirebaseFirestoreNormalized, isFirebaseStorageNormalized, isNativeNormalized, isNetworkNormalized, isUnknownNormalized, isZodNormalized, } from "./errors/normalize";
3285
3285
  export type { NormalizedError, NormalizedApiError, NormalizedAppError, NormalizedFirebaseAuthError, NormalizedFirebaseFirestoreError, NormalizedFirebaseStorageError, NormalizedNativeError, NormalizedNetworkError, NormalizedUnknownThrownValue, NormalizedZodError, } from "./errors/normalize";
3286
3286
  export { CART_LANE, CART_LANE_PRIORITY, CART_LANE_LABELS, laneOf, activeLane, laneItems, laneCounts, isLaneCheckoutable, laneBlockReason, isLockedLane, canAddNewItems, type CartLane, type LaneAssignable, } from "./_internal/shared/checkout/lanes";
3287
+ export { newsletterSubscribeSchema, quickCreateTaxonomySchema, payoutMarkPaidSchema, } from "./features/admin/schemas/small-forms";
3288
+ export { raffleEntrySchema } from "./features/contact/schemas/inquiry-forms";