@mohasinac/appkit 4.16.1 → 4.18.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 (43) hide show
  1. package/dist/client.d.ts +7 -0
  2. package/dist/client.js +7 -0
  3. package/dist/features/admin/components/AdminAdEditorView.js +4 -2
  4. package/dist/features/admin/components/AdminAddressEditorView.js +4 -2
  5. package/dist/features/admin/components/AdminEmployeeEditorView.js +3 -1
  6. package/dist/features/admin/components/AdminFeatureFlagsView.js +4 -2
  7. package/dist/features/admin/components/AdminOrderEditorView.js +4 -2
  8. package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +18 -6
  9. package/dist/features/admin/components/AdminSiteView.js +4 -2
  10. package/dist/features/admin/components/AdminStoreEditorView.js +11 -9
  11. package/dist/features/admin/components/AdminUsersView.js +4 -2
  12. package/dist/features/admin/components/BrandQuickCreateForm.js +13 -5
  13. package/dist/features/admin/components/CategoryQuickCreateForm.js +13 -5
  14. package/dist/features/admin/schemas/admin-editor-forms.d.ts +119 -0
  15. package/dist/features/admin/schemas/admin-editor-forms.js +141 -0
  16. package/dist/features/admin/schemas/admin-ops-forms.d.ts +123 -0
  17. package/dist/features/admin/schemas/admin-ops-forms.js +147 -0
  18. package/dist/features/admin/schemas/small-forms.d.ts +54 -0
  19. package/dist/features/admin/schemas/small-forms.js +85 -0
  20. package/dist/features/auth/components/ForgotPasswordView.js +2 -1
  21. package/dist/features/auth/components/LoginForm.js +2 -1
  22. package/dist/features/auth/components/RegisterForm.js +2 -1
  23. package/dist/features/auth/components/ResetPasswordView.js +2 -1
  24. package/dist/features/consultation/components/ConsultationForm.js +3 -1
  25. package/dist/features/consultation/schemas/index.d.ts +4 -4
  26. package/dist/features/consultation/schemas/index.js +22 -5
  27. package/dist/features/contact/schemas/inquiry-forms.d.ts +13 -0
  28. package/dist/features/contact/schemas/inquiry-forms.js +34 -0
  29. package/dist/features/corporate/components/CorporateInquiryForm.js +3 -1
  30. package/dist/features/corporate/schemas/index.d.ts +4 -1
  31. package/dist/features/corporate/schemas/index.js +38 -8
  32. package/dist/features/events/components/EventRaffleEntryForm.js +3 -1
  33. package/dist/features/homepage/components/NewsletterBanner.js +3 -1
  34. package/dist/features/orders/schemas/buyer-forms.d.ts +46 -0
  35. package/dist/features/orders/schemas/buyer-forms.js +81 -0
  36. package/dist/features/products/components/MakeOfferButton.js +1 -1
  37. package/dist/features/scams/schemas/report-form.d.ts +63 -0
  38. package/dist/features/scams/schemas/report-form.js +88 -0
  39. package/dist/features/store-extensions/schemas/sublisting-category-form.d.ts +15 -0
  40. package/dist/features/store-extensions/schemas/sublisting-category-form.js +41 -0
  41. package/dist/index.d.ts +7 -0
  42. package/dist/index.js +7 -0
  43. package/package.json +1 -1
@@ -5,6 +5,8 @@ import { Button, Row, Select } from "../../../ui";
5
5
  import { Form } from "../../../ui/components/Form";
6
6
  import { FieldInput } from "../../../ui/forms/FieldInput";
7
7
  import { FieldTextarea } from "../../../ui/forms/FieldTextarea";
8
+ import { bookConsultationSchema } from "../schemas";
9
+ import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
8
10
  const TIME_SLOTS = [
9
11
  "9:00 AM",
10
12
  "10:00 AM",
@@ -41,7 +43,7 @@ export function ConsultationForm({ onSubmit, isPending, concerns = [], }) {
41
43
  e.preventDefault();
42
44
  await onSubmit(form);
43
45
  }
44
- return (_jsxs(Form, { onSubmit: handleSubmit, spacing: "md", children: [_jsx(FieldInput, { name: "name", type: "text", placeholder: "Full name", value: form.name, onChange: (v) => handleValueChange("name", v), required: true }), _jsx(FieldInput, { name: "email", type: "email", placeholder: "Email address", value: form.email, onChange: (v) => handleValueChange("email", v), required: true }), _jsx(FieldInput, { name: "phone", type: "tel", placeholder: "Phone number", value: form.phone, onChange: (v) => handleValueChange("phone", v), required: true }), concerns.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", children: concerns.map((c) => (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => toggleConcern(c), className: `rounded-full border px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] transition ${form.concern.includes(c)
46
+ return (_jsxs(Form, { schema: bookConsultationSchema, onSubmit: handleSubmit, spacing: "md", children: [_jsx(FormErrorSummary, {}), _jsx(FieldInput, { name: "name", type: "text", placeholder: "Full name", value: form.name, onChange: (v) => handleValueChange("name", v), required: true }), _jsx(FieldInput, { name: "email", type: "email", placeholder: "Email address", value: form.email, onChange: (v) => handleValueChange("email", v), required: true }), _jsx(FieldInput, { name: "phone", type: "tel", placeholder: "Phone number", value: form.phone, onChange: (v) => handleValueChange("phone", v), required: true }), concerns.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", children: concerns.map((c) => (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => toggleConcern(c), className: `rounded-full border px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] transition ${form.concern.includes(c)
45
47
  ? "border-primary bg-primary text-white"
46
48
  : "border-neutral-300 text-neutral-700 hover:border-primary"}`, children: c }, c))) })), _jsxs(Row, { gap: "3", children: [_jsx(FieldInput, { name: "preferredDate", type: "date", value: form.preferredDate, onChange: (v) => handleValueChange("preferredDate", v), required: true, className: "flex-1" }), _jsx(Select, { value: form.preferredTime, onValueChange: (value) => handleValueChange("preferredTime", value), options: TIME_SLOTS.map((t) => ({ value: t, label: t })), wrapperClassName: "flex-1" })] }), _jsx(Select, { value: form.mode, onValueChange: (value) => handleValueChange("mode", value), options: [
47
49
  { value: "remote", label: "Remote (Video Call)" },
@@ -60,8 +60,8 @@ export declare const consultationBookingSchema: z.ZodObject<{
60
60
  export declare const bookConsultationSchema: z.ZodObject<{
61
61
  name: z.ZodString;
62
62
  email: z.ZodString;
63
- phone: z.ZodOptional<z.ZodString>;
64
- concern: z.ZodArray<z.ZodString, "many">;
63
+ phone: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
64
+ concern: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
65
65
  preferredDate: z.ZodOptional<z.ZodString>;
66
66
  preferredTime: z.ZodOptional<z.ZodString>;
67
67
  mode: z.ZodOptional<z.ZodEnum<["online", "in_person", "phone"]>>;
@@ -69,19 +69,19 @@ export declare const bookConsultationSchema: z.ZodObject<{
69
69
  }, "strip", z.ZodTypeAny, {
70
70
  email: string;
71
71
  name: string;
72
- concern: string[];
73
72
  message?: string | undefined;
74
73
  phone?: string | undefined;
75
74
  mode?: "phone" | "online" | "in_person" | undefined;
75
+ concern?: string[] | undefined;
76
76
  preferredDate?: string | undefined;
77
77
  preferredTime?: string | undefined;
78
78
  }, {
79
79
  email: string;
80
80
  name: string;
81
- concern: string[];
82
81
  message?: string | undefined;
83
82
  phone?: string | undefined;
84
83
  mode?: "phone" | "online" | "in_person" | undefined;
84
+ concern?: string[] | undefined;
85
85
  preferredDate?: string | undefined;
86
86
  preferredTime?: string | undefined;
87
87
  }>;
@@ -36,12 +36,29 @@ export const consultationBookingSchema = z.object({
36
36
  * Form schema for submitting a new consultation booking.
37
37
  */
38
38
  export const bookConsultationSchema = z.object({
39
- name: z.string().min(2),
40
- email: z.string().email(),
41
- phone: z.string().optional(),
42
- concern: z.array(z.string()).min(1, "Select at least one concern"),
39
+ name: z.string().trim().min(2, "Please tell us your name.").max(120),
40
+ email: z
41
+ .string()
42
+ .trim()
43
+ .toLowerCase()
44
+ .min(1, "An email address is required.")
45
+ .email("That doesn't look like an email address."),
46
+ // Same shape as the corporate enquiry's — see the note there.
47
+ phone: z
48
+ .string()
49
+ .trim()
50
+ .regex(/^(\+?91[-\s]?)?[6-9]\d{9}$/, "Enter a 10-digit Indian mobile number.")
51
+ .optional()
52
+ .or(z.literal("")),
53
+ /*
54
+ * Optional here, though the document treats it as the subject of the
55
+ * booking: the public form does not render a concern picker, so requiring it
56
+ * would reject every submission the form can produce. The admin-side booking
57
+ * path is where it is set.
58
+ */
59
+ concern: z.array(z.string()).optional(),
43
60
  preferredDate: z.string().optional(),
44
61
  preferredTime: z.string().optional(),
45
62
  mode: consultationModeSchema.optional(),
46
- message: z.string().optional(),
63
+ message: z.string().trim().max(4000).optional(),
47
64
  });
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * A raffle entry is one optional message — the entry itself is the act of
4
+ * submitting. Bounded because the text is rendered back on a leaderboard.
5
+ */
6
+ export declare const raffleEntrySchema: z.ZodObject<{
7
+ message: z.ZodOptional<z.ZodString>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ message?: string | undefined;
10
+ }, {
11
+ message?: string | undefined;
12
+ }>;
13
+ export type RaffleEntryValues = z.infer<typeof raffleEntrySchema>;
@@ -0,0 +1,34 @@
1
+ /*
2
+ * WHY: `EventRaffleEntryForm` rendered `<Form>` with no schema, so a raffle
3
+ * entry's message was unbounded — and it is rendered back on a public
4
+ * leaderboard.
5
+ *
6
+ * ## Why this file holds ONE schema and not three
7
+ *
8
+ * It was drafted with three: consultation, corporate and raffle. Two of those
9
+ * were already implemented — `bookConsultationSchema` and
10
+ * `submitCorporateInquirySchema` have existed in their own features all along,
11
+ * and their forms simply never used them. Adding a second copy of each would
12
+ * have been precisely the duplication this rework exists to remove, so those
13
+ * two were improved in place instead and only the raffle schema remains here.
14
+ *
15
+ * EXPORTS: raffleEntrySchema, type RaffleEntryValues
16
+ *
17
+ * @tag domain:events
18
+ * @tag layer:schema
19
+ * @tag pattern:none
20
+ * @tag access:isomorphic
21
+ * @tag consumers:EventRaffleEntryForm
22
+ * @tag sideEffects:none
23
+ */
24
+ import { z } from "zod";
25
+ import { annotate } from "../../shell/field-ui-meta";
26
+ /**
27
+ * A raffle entry is one optional message — the entry itself is the act of
28
+ * submitting. Bounded because the text is rendered back on a leaderboard.
29
+ */
30
+ export const raffleEntrySchema = z.object({
31
+ message: annotate(z.string().trim().max(500, "Keep it under 500 characters.").optional(), {
32
+ section: "entry", sectionLabel: "Your entry", sectionRequired: true, quick: true, order: 1, row: "full", kind: "textarea",
33
+ }),
34
+ });
@@ -6,6 +6,8 @@ import { Form } from "../../../ui/components/Form";
6
6
  import { FieldInput } from "../../../ui/forms/FieldInput";
7
7
  import { FieldTextarea } from "../../../ui/forms/FieldTextarea";
8
8
  import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
9
+ import { submitCorporateInquirySchema } from "../schemas";
10
+ import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
9
11
  export function CorporateInquiryForm({ onSubmit, isPending, }) {
10
12
  const [form, setForm] = React.useState({
11
13
  companyName: "",
@@ -26,5 +28,5 @@ export function CorporateInquiryForm({ onSubmit, isPending, }) {
26
28
  e.preventDefault();
27
29
  await onSubmit(form);
28
30
  }
29
- return (_jsxs(Form, { onSubmit: handleSubmit, spacing: "md", children: [_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-1 sm:grid-cols-2", children: [_jsx(FieldInput, { name: "companyName", type: "text", placeholder: "Company name", value: form.companyName, onChange: (v) => patch("companyName", v), required: true }), _jsx(FieldInput, { name: "contactPerson", type: "text", placeholder: "Contact person", value: form.contactPerson, onChange: (v) => patch("contactPerson", v), required: true }), _jsx(FieldInput, { name: "designation", type: "text", placeholder: "Designation (optional)", value: form.designation ?? "", onChange: (v) => patch("designation", v) }), _jsx(FieldInput, { name: "email", type: "email", placeholder: "Business email", value: form.email, onChange: (v) => patch("email", v), required: true }), _jsx(FieldInput, { name: "phone", type: "tel", placeholder: "Phone number", value: form.phone, onChange: (v) => patch("phone", v), required: true }), _jsx(FieldInput, { name: "units", type: "number", placeholder: "Number of units", value: form.units, onChange: (v) => patch("units", v === "" ? 0 : Number(v)), min: 1, required: true }), _jsx(FieldInput, { name: "budgetPerUnit", type: "number", placeholder: "Budget per unit (optional)", value: form.budgetPerUnit ?? "", onChange: (v) => patch("budgetPerUnit", v === "" ? undefined : Number(v)), min: 0 }), _jsx(FieldInput, { name: "deliveryDateRequired", type: "date", placeholder: "Required by (optional)", value: form.deliveryDateRequired ?? "", onChange: (v) => patch("deliveryDateRequired", v) })] }), _jsx(FieldCheckbox, { name: "customBranding", label: "Custom branding required", checked: form.customBranding, onChange: (c) => patch("customBranding", c) }), _jsx(FieldTextarea, { name: "message", placeholder: "Additional requirements (optional)", value: form.message ?? "", onChange: (v) => patch("message", v), rows: 4 }), _jsx(Button, { type: "submit", isLoading: isPending, disabled: isPending, variant: "primary", className: "w-full", children: isPending ? "Submitting..." : "Submit Inquiry" }), _jsx(Text, { size: "xs", variant: "secondary", children: "We'll respond within 1-2 business days." })] }));
31
+ return (_jsxs(Form, { schema: submitCorporateInquirySchema, onSubmit: handleSubmit, spacing: "md", children: [_jsx(FormErrorSummary, {}), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-1 sm:grid-cols-2", children: [_jsx(FieldInput, { name: "companyName", type: "text", placeholder: "Company name", value: form.companyName, onChange: (v) => patch("companyName", v), required: true }), _jsx(FieldInput, { name: "contactPerson", type: "text", placeholder: "Contact person", value: form.contactPerson, onChange: (v) => patch("contactPerson", v), required: true }), _jsx(FieldInput, { name: "designation", type: "text", placeholder: "Designation (optional)", value: form.designation ?? "", onChange: (v) => patch("designation", v) }), _jsx(FieldInput, { name: "email", type: "email", placeholder: "Business email", value: form.email, onChange: (v) => patch("email", v), required: true }), _jsx(FieldInput, { name: "phone", type: "tel", placeholder: "Phone number", value: form.phone, onChange: (v) => patch("phone", v), required: true }), _jsx(FieldInput, { name: "units", type: "number", placeholder: "Number of units", value: form.units, onChange: (v) => patch("units", v === "" ? 0 : Number(v)), min: 1, required: true }), _jsx(FieldInput, { name: "budgetPerUnit", type: "number", placeholder: "Budget per unit (optional)", value: form.budgetPerUnit ?? "", onChange: (v) => patch("budgetPerUnit", v === "" ? undefined : Number(v)), min: 0 }), _jsx(FieldInput, { name: "deliveryDateRequired", type: "date", placeholder: "Required by (optional)", value: form.deliveryDateRequired ?? "", onChange: (v) => patch("deliveryDateRequired", v) })] }), _jsx(FieldCheckbox, { name: "customBranding", label: "Custom branding required", checked: form.customBranding, onChange: (c) => patch("customBranding", c) }), _jsx(FieldTextarea, { name: "message", placeholder: "Additional requirements (optional)", value: form.message ?? "", onChange: (v) => patch("message", v), rows: 4 }), _jsx(Button, { type: "submit", isLoading: isPending, disabled: isPending, variant: "primary", className: "w-full", children: isPending ? "Submitting..." : "Submit Inquiry" }), _jsx(Text, { size: "xs", variant: "secondary", children: "We'll respond within 1-2 business days." })] }));
30
32
  }
@@ -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
  }
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Cancelling an order.
4
+ *
5
+ * A reason floor of 5 characters, because this text is what the seller reads
6
+ * to decide whether to restock, refund early or dispute — and "x" tells them
7
+ * nothing. Deliberately low: the buyer is cancelling, not writing an essay,
8
+ * and a high floor would push them to type filler.
9
+ */
10
+ export declare const orderCancelSchema: z.ZodObject<{
11
+ reason: z.ZodString;
12
+ }, "strip", z.ZodTypeAny, {
13
+ reason: string;
14
+ }, {
15
+ reason: string;
16
+ }>;
17
+ export type OrderCancelValues = z.infer<typeof orderCancelSchema>;
18
+ /**
19
+ * Uploading proof of a manual (cash / UPI / EMI) payment.
20
+ *
21
+ * ## Every field here is evidence
22
+ *
23
+ * An admin verifies or rejects on this, and a rejection carries a 7-day ban
24
+ * cascade — so the cost of a sloppy submission is real in both directions.
25
+ *
26
+ * `buyerMarkedPaid` and `buyerFraudAgreementAccepted` are `z.literal(true)`
27
+ * rather than `z.boolean()`: an unchecked declaration is not a submission with
28
+ * a `false` in it, it is a form that must not send.
29
+ */
30
+ export declare const paymentProofSchema: z.ZodObject<{
31
+ transactionId: z.ZodString;
32
+ buyerReportedUpiId: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
33
+ buyerMarkedPaid: z.ZodLiteral<true>;
34
+ buyerFraudAgreementAccepted: z.ZodLiteral<true>;
35
+ }, "strip", z.ZodTypeAny, {
36
+ transactionId: string;
37
+ buyerMarkedPaid: true;
38
+ buyerFraudAgreementAccepted: true;
39
+ buyerReportedUpiId?: string | undefined;
40
+ }, {
41
+ transactionId: string;
42
+ buyerMarkedPaid: true;
43
+ buyerFraudAgreementAccepted: true;
44
+ buyerReportedUpiId?: string | undefined;
45
+ }>;
46
+ export type PaymentProofValues = z.infer<typeof paymentProofSchema>;
@@ -0,0 +1,81 @@
1
+ /*
2
+ * WHY: Two buyer-facing order forms rendered `<Form>` with no schema.
3
+ *
4
+ * The manual-payment one is the consequential case. It carries the
5
+ * buyer's own claim that they have paid — a UTR, the UPI ID they sent to,
6
+ * and a fraud declaration — and an admin decides on that evidence whether
7
+ * to release goods. `transactionId` was free text with no floor, so a
8
+ * single character satisfied it and reached the review queue looking like
9
+ * a reference.
10
+ *
11
+ * WHAT: The two schemas, plus a shared `cancellationReason`.
12
+ *
13
+ * EXPORTS:
14
+ * orderCancelSchema, paymentProofSchema, and their inferred value types
15
+ *
16
+ * @tag domain:orders
17
+ * @tag layer:schema
18
+ * @tag pattern:none
19
+ * @tag access:isomorphic
20
+ * @tag consumers:user/orders/[id]/cancel,user/orders/[id]/payment
21
+ * @tag sideEffects:none
22
+ */
23
+ import { z } from "zod";
24
+ import { annotate } from "../../shell/field-ui-meta";
25
+ /**
26
+ * Cancelling an order.
27
+ *
28
+ * A reason floor of 5 characters, because this text is what the seller reads
29
+ * to decide whether to restock, refund early or dispute — and "x" tells them
30
+ * nothing. Deliberately low: the buyer is cancelling, not writing an essay,
31
+ * and a high floor would push them to type filler.
32
+ */
33
+ export const orderCancelSchema = z.object({
34
+ reason: annotate(z
35
+ .string()
36
+ .trim()
37
+ .min(5, "Give a short reason — the seller sees this.")
38
+ .max(500, "Keep it under 500 characters."), {
39
+ section: "cancel",
40
+ sectionLabel: "Why are you cancelling?",
41
+ sectionRequired: true,
42
+ quick: true,
43
+ order: 1,
44
+ row: "full",
45
+ kind: "textarea",
46
+ }),
47
+ });
48
+ /**
49
+ * Uploading proof of a manual (cash / UPI / EMI) payment.
50
+ *
51
+ * ## Every field here is evidence
52
+ *
53
+ * An admin verifies or rejects on this, and a rejection carries a 7-day ban
54
+ * cascade — so the cost of a sloppy submission is real in both directions.
55
+ *
56
+ * `buyerMarkedPaid` and `buyerFraudAgreementAccepted` are `z.literal(true)`
57
+ * rather than `z.boolean()`: an unchecked declaration is not a submission with
58
+ * a `false` in it, it is a form that must not send.
59
+ */
60
+ export const paymentProofSchema = z.object({
61
+ transactionId: annotate(z
62
+ .string()
63
+ .trim()
64
+ .min(4, "Enter the UTR or reference number from your payment app.")
65
+ .max(120, "Keep the reference under 120 characters."), { section: "proof", sectionLabel: "Your payment", sectionRequired: true, quick: true, order: 1, row: "pair" }),
66
+ /*
67
+ * The UPI ID the buyer actually sent to. Compared against the store's
68
+ * displayed one so a mismatch is flagged to the admin — which is why the
69
+ * shape matters, not just its presence.
70
+ */
71
+ buyerReportedUpiId: annotate(z
72
+ .string()
73
+ .trim()
74
+ .regex(/^[\w.\-_]{2,256}@[a-zA-Z]{2,64}$/, "Enter the UPI ID you paid to, e.g. name@bank.")
75
+ .optional()
76
+ .or(z.literal("")), { section: "proof", order: 2, row: "pair" }),
77
+ buyerMarkedPaid: annotate(z.literal(true, { errorMap: () => ({ message: "Confirm that you have sent the payment." }) }), { section: "declare", sectionLabel: "Declaration", sectionRequired: true, order: 1, row: "full" }),
78
+ buyerFraudAgreementAccepted: annotate(z.literal(true, {
79
+ errorMap: () => ({ message: "You must accept the fraud policy to submit proof." }),
80
+ }), { section: "declare", order: 2, row: "full" }),
81
+ });
@@ -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" })] })] })) }) })] }));
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ export declare const scamReportFormSchema: z.ZodEffects<z.ZodObject<{
3
+ displayName: z.ZodString;
4
+ phones: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
5
+ upiIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
6
+ emails: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
7
+ scamType: z.ZodString;
8
+ scamPlatform: z.ZodOptional<z.ZodString>;
9
+ amountLost: z.ZodOptional<z.ZodNumber>;
10
+ itemInvolved: z.ZodOptional<z.ZodString>;
11
+ description: z.ZodString;
12
+ reportedByAnon: z.ZodOptional<z.ZodBoolean>;
13
+ agreed: z.ZodLiteral<true>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ displayName: string;
16
+ description: string;
17
+ scamType: string;
18
+ agreed: true;
19
+ reportedByAnon?: boolean | undefined;
20
+ scamPlatform?: string | undefined;
21
+ amountLost?: number | undefined;
22
+ itemInvolved?: string | undefined;
23
+ phones?: string[] | undefined;
24
+ upiIds?: string[] | undefined;
25
+ emails?: string[] | undefined;
26
+ }, {
27
+ displayName: string;
28
+ description: string;
29
+ scamType: string;
30
+ agreed: true;
31
+ reportedByAnon?: boolean | undefined;
32
+ scamPlatform?: string | undefined;
33
+ amountLost?: number | undefined;
34
+ itemInvolved?: string | undefined;
35
+ phones?: string[] | undefined;
36
+ upiIds?: string[] | undefined;
37
+ emails?: string[] | undefined;
38
+ }>, {
39
+ displayName: string;
40
+ description: string;
41
+ scamType: string;
42
+ agreed: true;
43
+ reportedByAnon?: boolean | undefined;
44
+ scamPlatform?: string | undefined;
45
+ amountLost?: number | undefined;
46
+ itemInvolved?: string | undefined;
47
+ phones?: string[] | undefined;
48
+ upiIds?: string[] | undefined;
49
+ emails?: string[] | undefined;
50
+ }, {
51
+ displayName: string;
52
+ description: string;
53
+ scamType: string;
54
+ agreed: true;
55
+ reportedByAnon?: boolean | undefined;
56
+ scamPlatform?: string | undefined;
57
+ amountLost?: number | undefined;
58
+ itemInvolved?: string | undefined;
59
+ phones?: string[] | undefined;
60
+ upiIds?: string[] | undefined;
61
+ emails?: string[] | undefined;
62
+ }>;
63
+ export type ScamReportFormValues = z.infer<typeof scamReportFormSchema>;
@@ -0,0 +1,88 @@
1
+ /*
2
+ * WHY: The public scam-report form (`/scams/report`) rendered `<Form>` with no
3
+ * schema. It is the highest-consequence unvalidated form in the app: a
4
+ * submission names a real person or business by phone number, UPI ID or
5
+ * email and asks the platform to publish an accusation against them.
6
+ *
7
+ * Everything was free text. `amountLost` in particular was a string with
8
+ * no bound, and it is displayed on the public scammer profile.
9
+ *
10
+ * WHAT: The submit contract.
11
+ *
12
+ * ## At least one identifier is required, and no single one is
13
+ *
14
+ * A report naming nobody is unactionable, but demanding a phone number would
15
+ * reject a report about an email-only scammer. So the requirement is on the
16
+ * SET, checked in a `superRefine` — the same shape the payout-method schema
17
+ * uses for its per-type bank rules.
18
+ *
19
+ * ## `agreed` is `z.literal(true)`
20
+ *
21
+ * An unticked declaration is not a submission carrying `false`; it is a form
22
+ * that must not send. The user is asserting the accusation is truthful, and
23
+ * that assertion is what makes publishing it defensible.
24
+ *
25
+ * EXPORTS: scamReportFormSchema, type ScamReportFormValues
26
+ *
27
+ * @tag domain:scams
28
+ * @tag layer:schema
29
+ * @tag pattern:none
30
+ * @tag access:isomorphic
31
+ * @tag consumers:scams/report page
32
+ * @tag sideEffects:none
33
+ */
34
+ import { z } from "zod";
35
+ import { annotate } from "../../shell/field-ui-meta";
36
+ const UPI_VPA_RE = /^[\w.\-_]{2,256}@[a-zA-Z]{2,64}$/;
37
+ const PHONE_RE = /^(\+?91[-\s]?)?[6-9]\d{9}$/;
38
+ export const scamReportFormSchema = z
39
+ .object({
40
+ displayName: annotate(z.string().trim().min(2, "Give the name or handle they used.").max(160), { section: "who", sectionLabel: "Who", sectionRequired: true, quick: true, order: 1, row: "full" }),
41
+ // Each entry is shape-checked; the SET is checked in the superRefine.
42
+ phones: annotate(z.array(z.string().trim().regex(PHONE_RE, "Enter a 10-digit Indian mobile number.")).max(10).optional(), { section: "who", order: 2, row: "full", kind: "list" }),
43
+ upiIds: annotate(z.array(z.string().trim().regex(UPI_VPA_RE, "Enter a valid UPI ID, e.g. name@bank.")).max(10).optional(), { section: "who", order: 3, row: "full", kind: "list" }),
44
+ emails: annotate(z.array(z.string().trim().toLowerCase().email("Enter a valid email address.")).max(10).optional(), { section: "who", order: 4, row: "full", kind: "list" }),
45
+ scamType: annotate(z.string().trim().min(1, "Choose what kind of scam this was."), {
46
+ section: "what", sectionLabel: "What happened", sectionRequired: true, quick: true, order: 1, row: "pair",
47
+ }),
48
+ scamPlatform: annotate(z.string().trim().max(120).optional(), {
49
+ section: "what", order: 2, row: "pair",
50
+ }),
51
+ /*
52
+ * Coerced and bounded. It arrives from a text input and is rendered on the
53
+ * public profile, so an unbounded string could publish "one MILLION crore"
54
+ * as a loss figure. Empty is allowed — plenty of reports are about an
55
+ * attempt that cost nothing.
56
+ */
57
+ amountLost: annotate(z.coerce
58
+ .number({ invalid_type_error: "Enter the amount as a number." })
59
+ .min(0, "An amount cannot be negative.")
60
+ .max(100000000, "That looks like a typo.")
61
+ .optional(), { section: "what", order: 3, row: "pair", kind: "number" }),
62
+ itemInvolved: annotate(z.string().trim().max(200).optional(), {
63
+ section: "what", order: 4, row: "pair",
64
+ }),
65
+ description: annotate(z
66
+ .string()
67
+ .trim()
68
+ .min(30, "Describe what happened in at least 30 characters — this is the evidence.")
69
+ .max(5000, "Keep the description under 5000 characters."), { section: "what", order: 5, row: "full", kind: "textarea" }),
70
+ reportedByAnon: annotate(z.boolean().optional(), {
71
+ section: "declare", sectionLabel: "Declaration", order: 1, row: "quarter",
72
+ }),
73
+ agreed: annotate(z.literal(true, {
74
+ errorMap: () => ({ message: "You must confirm this report is truthful before submitting." }),
75
+ }), { section: "declare", order: 2, row: "full" }),
76
+ })
77
+ .superRefine((v, ctx) => {
78
+ const count = (v.phones?.length ?? 0) + (v.upiIds?.length ?? 0) + (v.emails?.length ?? 0);
79
+ if (count === 0) {
80
+ // Reported on `phones` so the message has a field to land on; the text
81
+ // names all three, because any one of them satisfies the rule.
82
+ ctx.addIssue({
83
+ code: z.ZodIssueCode.custom,
84
+ path: ["phones"],
85
+ message: "Add at least one phone number, UPI ID or email — a report with no identifier cannot be acted on.",
86
+ });
87
+ }
88
+ });
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ export declare const sublistingCategoryFormSchema: z.ZodObject<{
3
+ name: z.ZodString;
4
+ itemCode: z.ZodOptional<z.ZodString>;
5
+ description: z.ZodOptional<z.ZodString>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ name: string;
8
+ description?: string | undefined;
9
+ itemCode?: string | undefined;
10
+ }, {
11
+ name: string;
12
+ description?: string | undefined;
13
+ itemCode?: string | undefined;
14
+ }>;
15
+ export type SublistingCategoryFormValues = z.infer<typeof sublistingCategoryFormSchema>;