@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.
- package/dist/client.d.ts +5 -3
- package/dist/client.js +5 -3
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +18 -6
- package/dist/features/admin/components/BrandQuickCreateForm.js +13 -5
- package/dist/features/admin/components/CategoryQuickCreateForm.js +13 -5
- package/dist/features/admin/schemas/small-forms.d.ts +54 -0
- package/dist/features/admin/schemas/small-forms.js +85 -0
- package/dist/features/auth/components/ForgotPasswordView.js +2 -1
- package/dist/features/auth/components/LoginForm.js +2 -1
- package/dist/features/auth/components/RegisterForm.js +2 -1
- package/dist/features/auth/components/ResetPasswordView.js +2 -1
- package/dist/features/consultation/components/ConsultationForm.js +3 -1
- package/dist/features/consultation/schemas/index.d.ts +4 -4
- package/dist/features/consultation/schemas/index.js +22 -5
- package/dist/features/contact/schemas/inquiry-forms.d.ts +13 -0
- package/dist/features/contact/schemas/inquiry-forms.js +34 -0
- package/dist/features/corporate/components/CorporateInquiryForm.js +3 -1
- package/dist/features/corporate/schemas/index.d.ts +4 -1
- package/dist/features/corporate/schemas/index.js +38 -8
- package/dist/features/events/components/EventRaffleEntryForm.js +3 -1
- package/dist/features/homepage/components/NewsletterBanner.js +3 -1
- package/dist/features/products/components/MakeOfferButton.js +1 -1
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +27 -9
- package/dist/features/store-extensions/schemas/listing-template-form.d.ts +19 -1
- package/dist/features/store-extensions/schemas/listing-template-form.js +42 -1
- package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +19 -13
- package/dist/features/store-extensions/schemas/shipping-config-form.js +25 -2
- package/dist/features/store-extensions/schemas/store-category-form.d.ts +40 -0
- package/dist/features/store-extensions/schemas/store-category-form.js +28 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/schemas/registry.d.ts +8 -5
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -112,7 +112,7 @@ export { useBulkSelection } from "./react/hooks/useBulkSelection";
|
|
|
112
112
|
export type { UseBulkSelectionOptions, UseBulkSelectionReturn } from "./react/hooks/useBulkSelection";
|
|
113
113
|
export { useEntityDelete } from "./react/hooks/useEntityDelete";
|
|
114
114
|
export type { UseEntityDeleteOptions, UseEntityDeleteReturn } from "./react/hooks/useEntityDelete";
|
|
115
|
-
export { storeCategoryFormSchema, storeCategoryCreateSchema, type StoreCategoryFormValues, } from "./features/store-extensions/schemas/store-category-form";
|
|
115
|
+
export { storeCategoryFormSchema, storeCategoryCreateSchema, storeCategoryUpdateSchema, type StoreCategoryFormValues, } from "./features/store-extensions/schemas/store-category-form";
|
|
116
116
|
export { payoutMethodFormSchema, payoutMethodCreateSchema, payoutMethodUpdateSchema, type PayoutMethodFormValues, } from "./features/store-extensions/schemas/payout-method-form";
|
|
117
117
|
export { groupedListingFormSchema, groupedListingCreateSchema, groupedListingUpdateSchema, type GroupedListingFormValues, } from "./features/grouped/schemas/grouped-listing-form";
|
|
118
118
|
export { navItemFormSchema, navItemCreateSchema, navItemUpdateSchema, type NavItemFormValues, } from "./features/admin/schemas/nav-item-form";
|
|
@@ -121,8 +121,8 @@ export { SCAMMER_STATUS_LABEL, SCAMMER_STATUS_BADGE, SCAMMER_STATUS_OPTIONS, toS
|
|
|
121
121
|
export { ReviewDecisionModal, type ReviewDecisionModalProps, } from "./features/store-extensions/components/ReviewDecisionModal";
|
|
122
122
|
export { moderationReviewFormSchema, moderationReviewUpdateSchema, MODERATION_REVIEWER_STATUSES, type ModerationReviewFormValues, } from "./features/store-extensions/schemas/moderation-review-form";
|
|
123
123
|
export { reportReviewFormSchema, reportReviewUpdateSchema, REPORT_REVIEWER_STATUSES, REPORT_TERMINAL_STATUSES, type ReportReviewFormValues, } from "./features/store-extensions/schemas/report-review-form";
|
|
124
|
-
export { shippingConfigFormSchema, shippingConfigCreateSchema, shippingConfigUpdateSchema, type ShippingConfigFormValues, } from "./features/store-extensions/schemas/shipping-config-form";
|
|
125
|
-
export { listingTemplateFormSchema, listingTemplateCreateSchema, listingTemplateUpdateSchema, type ListingTemplateFormValues, } from "./features/store-extensions/schemas/listing-template-form";
|
|
124
|
+
export { shippingConfigFormSchema, SHIPPING_METHOD_OPTIONS, shippingConfigCreateSchema, shippingConfigUpdateSchema, type ShippingConfigFormValues, } from "./features/store-extensions/schemas/shipping-config-form";
|
|
125
|
+
export { listingTemplateFormSchema, LISTING_TEMPLATE_TYPE_OPTIONS, listingTemplateCreateSchema, listingTemplateUpdateSchema, type ListingTemplateFormValues, } from "./features/store-extensions/schemas/listing-template-form";
|
|
126
126
|
export { ROUTES, PUBLIC_ROUTES, PROTECTED_ROUTES, AUTH_ROUTES } from "./constants/index";
|
|
127
127
|
export { SORT_DIR, sortBy } from "./constants/sort";
|
|
128
128
|
export { SIEVE_OP, sieveFilter, sieveAnd, sieveMultiEq } from "./utils/sieve-builder";
|
|
@@ -434,3 +434,5 @@ export { SELLER_LISTING_TABS, type SellerListingTabId, } from "./features/produc
|
|
|
434
434
|
export { ALL_TAB, EMPTY_TAB, ADMIN_PRODUCT_STATUS_TABS, ADMIN_PRODUCT_LISTING_TYPE_TABS, ADMIN_BLOG_STATUS_TABS, ADMIN_USER_STATUS_TABS, ADMIN_USER_ROLE_TABS, ADMIN_STORE_STATUS_TABS, ADMIN_PAYOUT_STATUS_TABS, ADMIN_ORDER_STATUS_TABS, ADMIN_REVIEW_STATUS_TABS, ADMIN_REVIEW_RATING_TABS, ADMIN_BID_STATUS_TABS, ADMIN_CONTACT_STATUS_TABS, ADMIN_NEWSLETTER_STATUS_TABS, ADMIN_EVENT_ENTRY_STATUS_TABS, ADMIN_EVENT_STATUS_TABS, ADMIN_CART_OWNERSHIP_TABS, ADMIN_COUPON_TYPE_TABS, SELLER_PRODUCT_STATUS_TABS, SELLER_AUCTION_STATUS_TABS, SELLER_ORDER_STATUS_TABS, SELLER_OFFER_STATUS_TABS, SELLER_BID_STATUS_TABS, } from "./features/admin/constants/filter-tabs";
|
|
435
435
|
export { PRODUCT_FIELDS, PRODUCT_STATUS_TRANSITIONS, ORDER_FIELDS, REVIEW_FIELDS, BID_FIELDS, AD_FIELDS, EVENT_FIELDS, EVENT_ENTRY_FIELDS, PAYOUT_FIELDS, STORE_FIELDS, CATEGORY_FIELDS, BLOG_FIELDS, USER_FIELDS, ADDRESS_FIELDS, BRAND_FIELDS, CART_FIELDS, WISHLIST_FIELDS, HISTORY_FIELDS, NOTIFICATION_FIELDS, SESSION_FIELDS, COUPON_USAGE_FIELDS, CONVERSATION_FIELDS, SCAMMER_FIELDS, SUPPORT_TICKET_FIELDS, CAROUSEL_FIELDS, COUPON_FIELDS, FAQ_FIELDS, HOMEPAGE_SECTION_FIELDS, SITE_SETTINGS_FIELDS, COMMON_FIELDS, OAUTH_STATE_VALUES, SCHEMA_DEFAULTS, } from "./constants/field-names";
|
|
436
436
|
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";
|
|
437
|
+
export { newsletterSubscribeSchema, quickCreateTaxonomySchema, payoutMarkPaidSchema, } from "./features/admin/schemas/small-forms";
|
|
438
|
+
export { raffleEntrySchema } from "./features/contact/schemas/inquiry-forms";
|
package/dist/client.js
CHANGED
|
@@ -180,7 +180,7 @@ export { useEntityDelete } from "./react/hooks/useEntityDelete";
|
|
|
180
180
|
// Client-safe constants, UI primitives, and views
|
|
181
181
|
// Storefront-category form schema — isomorphic (the page parses it client-side,
|
|
182
182
|
// the route parses the same one server-side).
|
|
183
|
-
export { storeCategoryFormSchema, storeCategoryCreateSchema, } from "./features/store-extensions/schemas/store-category-form";
|
|
183
|
+
export { storeCategoryFormSchema, storeCategoryCreateSchema, storeCategoryUpdateSchema, } from "./features/store-extensions/schemas/store-category-form";
|
|
184
184
|
// Payout-method + listing-template form schemas — isomorphic: the pages parse
|
|
185
185
|
// them client-side and the routes parse the SAME ones server-side.
|
|
186
186
|
export { payoutMethodFormSchema, payoutMethodCreateSchema, payoutMethodUpdateSchema, } from "./features/store-extensions/schemas/payout-method-form";
|
|
@@ -194,8 +194,8 @@ export { SCAMMER_STATUS_LABEL, SCAMMER_STATUS_BADGE, SCAMMER_STATUS_OPTIONS, toS
|
|
|
194
194
|
export { ReviewDecisionModal, } from "./features/store-extensions/components/ReviewDecisionModal";
|
|
195
195
|
export { moderationReviewFormSchema, moderationReviewUpdateSchema, MODERATION_REVIEWER_STATUSES, } from "./features/store-extensions/schemas/moderation-review-form";
|
|
196
196
|
export { reportReviewFormSchema, reportReviewUpdateSchema, REPORT_REVIEWER_STATUSES, REPORT_TERMINAL_STATUSES, } from "./features/store-extensions/schemas/report-review-form";
|
|
197
|
-
export { shippingConfigFormSchema, shippingConfigCreateSchema, shippingConfigUpdateSchema, } from "./features/store-extensions/schemas/shipping-config-form";
|
|
198
|
-
export { listingTemplateFormSchema, listingTemplateCreateSchema, listingTemplateUpdateSchema, } from "./features/store-extensions/schemas/listing-template-form";
|
|
197
|
+
export { shippingConfigFormSchema, SHIPPING_METHOD_OPTIONS, shippingConfigCreateSchema, shippingConfigUpdateSchema, } from "./features/store-extensions/schemas/shipping-config-form";
|
|
198
|
+
export { listingTemplateFormSchema, LISTING_TEMPLATE_TYPE_OPTIONS, listingTemplateCreateSchema, listingTemplateUpdateSchema, } from "./features/store-extensions/schemas/listing-template-form";
|
|
199
199
|
export { ROUTES, PUBLIC_ROUTES, PROTECTED_ROUTES, AUTH_ROUTES } from "./constants/index";
|
|
200
200
|
export { SORT_DIR, sortBy } from "./constants/sort";
|
|
201
201
|
export { SIEVE_OP, sieveFilter, sieveAnd, sieveMultiEq } from "./utils/sieve-builder";
|
|
@@ -420,3 +420,5 @@ export { PRODUCT_FIELDS, PRODUCT_STATUS_TRANSITIONS, ORDER_FIELDS, REVIEW_FIELDS
|
|
|
420
420
|
// Checkout lanes — the derived auction > offer > standard partition of the
|
|
421
421
|
// cart, and the priority rule that decides which one may be checked out.
|
|
422
422
|
export { CART_LANE, CART_LANE_PRIORITY, CART_LANE_LABELS, laneOf, activeLane, laneItems, laneCounts, isLaneCheckoutable, laneBlockReason, isLockedLane, canAddNewItems, } from "./_internal/shared/checkout/lanes";
|
|
423
|
+
export { newsletterSubscribeSchema, quickCreateTaxonomySchema, payoutMarkPaidSchema, } from "./features/admin/schemas/small-forms";
|
|
424
|
+
export { raffleEntrySchema } from "./features/contact/schemas/inquiry-forms";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
4
|
* AdminPayoutMarkPaidModal — the single "mark payout as paid" form + mutation,
|
|
5
5
|
* shared by the payouts list row action and the standalone payout detail
|
|
@@ -9,9 +9,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
import { useState } from "react";
|
|
10
10
|
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
11
11
|
import { useQueryClient } from "@tanstack/react-query";
|
|
12
|
-
import { Button, Form, FormActions,
|
|
12
|
+
import { Button, Form, FormActions, Modal, useToast } from "../../../ui";
|
|
13
13
|
import { apiClient } from "../../../http";
|
|
14
14
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
15
|
+
import { payoutMarkPaidSchema } from "../schemas/small-forms";
|
|
16
|
+
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
17
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
18
|
+
import { applyZodIssues } from "../../../ui/forms/FormShell";
|
|
15
19
|
export function AdminPayoutMarkPaidModal({ isOpen, payoutId, onClose, onSuccess }) {
|
|
16
20
|
const [transactionId, setTransactionId] = useState("");
|
|
17
21
|
const { showToast } = useToast();
|
|
@@ -40,8 +44,16 @@ export function AdminPayoutMarkPaidModal({ isOpen, payoutId, onClose, onSuccess
|
|
|
40
44
|
setTransactionId("");
|
|
41
45
|
onClose();
|
|
42
46
|
};
|
|
43
|
-
return (_jsx(Modal, { isOpen: isOpen, onClose: handleClose, title: "Mark payout as paid", children:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
return (_jsx(Modal, { isOpen: isOpen, onClose: handleClose, title: "Mark payout as paid", children: _jsx(Form, { schema: payoutMarkPaidSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(FieldInput, { name: "transactionId", label: "Transaction / reference ID", required: true, value: transactionId, onChange: setTransactionId, placeholder: "UTR, UPI ref, or bank transfer ID" }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "outline", onClick: handleClose, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: markPaid.isPending, onClick: () => {
|
|
48
|
+
clearErrors();
|
|
49
|
+
// The reference used to be optional and its placeholder even
|
|
50
|
+
// said so — which left payouts marked paid with nothing to
|
|
51
|
+
// reconcile them against.
|
|
52
|
+
const parsed = payoutMarkPaidSchema.safeParse({ transactionId });
|
|
53
|
+
if (!parsed.success) {
|
|
54
|
+
applyZodIssues(parsed.error.issues, setFieldError);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
markPaid.mutate();
|
|
58
|
+
}, children: markPaid.isPending ? "Saving..." : "Confirm paid" })] })] })) }) }));
|
|
47
59
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { Row } from "@mohasinac/appkit/ui";
|
|
4
4
|
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { Button, Form, Input, Toggle, useToast } from "../../../ui";
|
|
7
7
|
import { apiClient } from "../../../http";
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { quickCreateTaxonomySchema } from "../schemas/small-forms";
|
|
10
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
11
|
+
import { applyZodIssues } from "../../../ui/forms/FormShell";
|
|
9
12
|
function toBrandSlug(str) {
|
|
10
13
|
const base = str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
11
14
|
return base.startsWith("brand-") ? base : `brand-${base}`;
|
|
@@ -30,8 +33,13 @@ export function BrandQuickCreateForm({ onSaved, onCancel }) {
|
|
|
30
33
|
showToast(err?.message ?? "Failed to create brand.", "error");
|
|
31
34
|
},
|
|
32
35
|
});
|
|
33
|
-
return (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
return (_jsx(Form, { schema: quickCreateTaxonomySchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "Brand name", value: name, onChange: (e) => setName(e.target.value), required: true, placeholder: "e.g. Hot Wheels", autoFocus: true }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Optional brief description" }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: mutation.isPending, disabled: mutation.isPending, onClick: () => {
|
|
37
|
+
clearErrors();
|
|
38
|
+
const parsed = quickCreateTaxonomySchema.safeParse({ name, description, isActive });
|
|
39
|
+
if (!parsed.success) {
|
|
40
|
+
applyZodIssues(parsed.error.issues, setFieldError);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
mutation.mutate();
|
|
44
|
+
}, children: "Create brand" }), _jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, children: "Cancel" })] })] })) }));
|
|
37
45
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { Row } from "@mohasinac/appkit/ui";
|
|
4
4
|
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { Button, Form, Input, Toggle, useToast } from "../../../ui";
|
|
7
7
|
import { apiClient } from "../../../http";
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { quickCreateTaxonomySchema } from "../schemas/small-forms";
|
|
10
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
11
|
+
import { applyZodIssues } from "../../../ui/forms/FormShell";
|
|
9
12
|
function toCategorySlug(str) {
|
|
10
13
|
return str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
11
14
|
}
|
|
@@ -29,8 +32,13 @@ export function CategoryQuickCreateForm({ onSaved, onCancel }) {
|
|
|
29
32
|
showToast(err?.message ?? "Failed to create category.", "error");
|
|
30
33
|
},
|
|
31
34
|
});
|
|
32
|
-
return (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
return (_jsx(Form, { schema: quickCreateTaxonomySchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "Category name", value: name, onChange: (e) => setName(e.target.value), required: true, placeholder: "e.g. Trading Cards", autoFocus: true }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Optional brief description" }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: mutation.isPending, disabled: mutation.isPending, onClick: () => {
|
|
36
|
+
clearErrors();
|
|
37
|
+
const parsed = quickCreateTaxonomySchema.safeParse({ name, description, isActive });
|
|
38
|
+
if (!parsed.success) {
|
|
39
|
+
applyZodIssues(parsed.error.issues, setFieldError);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
mutation.mutate();
|
|
43
|
+
}, children: "Create category" }), _jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, children: "Cancel" })] })] })) }));
|
|
36
44
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Newsletter subscription — one field, shared by all three boxes (the homepage
|
|
4
|
+
* banner, the homepage inline form, and the footer slot).
|
|
5
|
+
*
|
|
6
|
+
* Trimmed and lowercased before validation so ` Foo@Bar.com ` and
|
|
7
|
+
* `foo@bar.com` cannot become two subscriptions for one person.
|
|
8
|
+
*/
|
|
9
|
+
export declare const newsletterSubscribeSchema: z.ZodObject<{
|
|
10
|
+
email: z.ZodString;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
email: string;
|
|
13
|
+
}, {
|
|
14
|
+
email: string;
|
|
15
|
+
}>;
|
|
16
|
+
export type NewsletterSubscribeValues = z.infer<typeof newsletterSubscribeSchema>;
|
|
17
|
+
/**
|
|
18
|
+
* The quick-create drawer shared in shape by brands and categories.
|
|
19
|
+
*
|
|
20
|
+
* Both drawers post `{ name, description, isActive }` to their own endpoint
|
|
21
|
+
* and differ in nothing else, so one schema serves both. `slug` is absent
|
|
22
|
+
* deliberately — it is derived from the name on the server, and this codebase
|
|
23
|
+
* treats a slug as create-only and non-editable everywhere else.
|
|
24
|
+
*/
|
|
25
|
+
export declare const quickCreateTaxonomySchema: z.ZodObject<{
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
description: z.ZodOptional<z.ZodString>;
|
|
28
|
+
isActive: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
name: string;
|
|
31
|
+
description?: string | undefined;
|
|
32
|
+
isActive?: boolean | undefined;
|
|
33
|
+
}, {
|
|
34
|
+
name: string;
|
|
35
|
+
description?: string | undefined;
|
|
36
|
+
isActive?: boolean | undefined;
|
|
37
|
+
}>;
|
|
38
|
+
export type QuickCreateTaxonomyValues = z.infer<typeof quickCreateTaxonomySchema>;
|
|
39
|
+
/**
|
|
40
|
+
* Marking a payout paid.
|
|
41
|
+
*
|
|
42
|
+
* `transactionId` is REQUIRED, which it was not before. This is the record
|
|
43
|
+
* that money left the building — an empty one leaves a payout marked paid with
|
|
44
|
+
* nothing to reconcile it against, and no way to answer a seller asking where
|
|
45
|
+
* their money went.
|
|
46
|
+
*/
|
|
47
|
+
export declare const payoutMarkPaidSchema: z.ZodObject<{
|
|
48
|
+
transactionId: z.ZodString;
|
|
49
|
+
}, "strip", z.ZodTypeAny, {
|
|
50
|
+
transactionId: string;
|
|
51
|
+
}, {
|
|
52
|
+
transactionId: string;
|
|
53
|
+
}>;
|
|
54
|
+
export type PayoutMarkPaidValues = z.infer<typeof payoutMarkPaidSchema>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: A cluster of small forms — three newsletter boxes, two quick-create
|
|
3
|
+
* drawers, and the payout mark-paid modal — each rendered `<Form>` with
|
|
4
|
+
* no schema. Individually each is two or three fields; together they are
|
|
5
|
+
* six surfaces where the only validation was whatever the server said
|
|
6
|
+
* after the round-trip, reported as a toast.
|
|
7
|
+
*
|
|
8
|
+
* The newsletter trio matters most despite being one field: an email box
|
|
9
|
+
* that accepts anything sends a subscription request for `"asdf"` and
|
|
10
|
+
* shows a success message.
|
|
11
|
+
*
|
|
12
|
+
* WHAT: One module for the small shared shapes, rather than six schema files
|
|
13
|
+
* of three lines each.
|
|
14
|
+
*
|
|
15
|
+
* ## Why these live together and the bigger ones do not
|
|
16
|
+
*
|
|
17
|
+
* These have no per-entity semantics worth a file of their own — an email is
|
|
18
|
+
* an email. An entity with real rules (a payout method's per-type bank
|
|
19
|
+
* requirements, a listing template's defaults tree) keeps its own module,
|
|
20
|
+
* because that is where the reasoning belongs. Grouping by *size* rather than
|
|
21
|
+
* by domain is the exception here, and it stops at the point where a schema
|
|
22
|
+
* needs a paragraph to explain itself.
|
|
23
|
+
*
|
|
24
|
+
* EXPORTS:
|
|
25
|
+
* newsletterSubscribeSchema, quickCreateTaxonomySchema,
|
|
26
|
+
* payoutMarkPaidSchema, and their inferred value types
|
|
27
|
+
*
|
|
28
|
+
* @tag domain:admin,homepage,payments
|
|
29
|
+
* @tag layer:schema
|
|
30
|
+
* @tag pattern:none
|
|
31
|
+
* @tag access:isomorphic
|
|
32
|
+
* @tag consumers:newsletter forms,quick-create drawers,AdminPayoutMarkPaidModal
|
|
33
|
+
* @tag sideEffects:none
|
|
34
|
+
*/
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
import { annotate } from "../../shell/field-ui-meta";
|
|
37
|
+
/**
|
|
38
|
+
* Newsletter subscription — one field, shared by all three boxes (the homepage
|
|
39
|
+
* banner, the homepage inline form, and the footer slot).
|
|
40
|
+
*
|
|
41
|
+
* Trimmed and lowercased before validation so ` Foo@Bar.com ` and
|
|
42
|
+
* `foo@bar.com` cannot become two subscriptions for one person.
|
|
43
|
+
*/
|
|
44
|
+
export const newsletterSubscribeSchema = z.object({
|
|
45
|
+
email: annotate(z
|
|
46
|
+
.string()
|
|
47
|
+
.trim()
|
|
48
|
+
.toLowerCase()
|
|
49
|
+
.min(1, "Enter your email address.")
|
|
50
|
+
.email("That doesn't look like an email address."), { section: "subscribe", sectionLabel: "Newsletter", sectionRequired: true, quick: true, order: 1, row: "full" }),
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* The quick-create drawer shared in shape by brands and categories.
|
|
54
|
+
*
|
|
55
|
+
* Both drawers post `{ name, description, isActive }` to their own endpoint
|
|
56
|
+
* and differ in nothing else, so one schema serves both. `slug` is absent
|
|
57
|
+
* deliberately — it is derived from the name on the server, and this codebase
|
|
58
|
+
* treats a slug as create-only and non-editable everywhere else.
|
|
59
|
+
*/
|
|
60
|
+
export const quickCreateTaxonomySchema = z.object({
|
|
61
|
+
name: annotate(z.string().trim().min(1, "A name is required.").max(80, "Keep the name under 80 characters."), {
|
|
62
|
+
section: "basics", sectionLabel: "Details", sectionRequired: true, quick: true, order: 1, row: "full",
|
|
63
|
+
}),
|
|
64
|
+
description: annotate(z.string().max(500, "Keep the description under 500 characters.").optional(), {
|
|
65
|
+
section: "basics", order: 2, row: "full", kind: "textarea",
|
|
66
|
+
}),
|
|
67
|
+
isActive: annotate(z.boolean().optional(), {
|
|
68
|
+
section: "basics", order: 3, row: "quarter",
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* Marking a payout paid.
|
|
73
|
+
*
|
|
74
|
+
* `transactionId` is REQUIRED, which it was not before. This is the record
|
|
75
|
+
* that money left the building — an empty one leaves a payout marked paid with
|
|
76
|
+
* nothing to reconcile it against, and no way to answer a seller asking where
|
|
77
|
+
* their money went.
|
|
78
|
+
*/
|
|
79
|
+
export const payoutMarkPaidSchema = z.object({
|
|
80
|
+
transactionId: annotate(z
|
|
81
|
+
.string()
|
|
82
|
+
.trim()
|
|
83
|
+
.min(3, "Enter the bank or UPI reference for this transfer.")
|
|
84
|
+
.max(120, "Keep the reference under 120 characters."), { section: "payment", sectionLabel: "Payment", sectionRequired: true, quick: true, order: 1, row: "full" }),
|
|
85
|
+
});
|
|
@@ -7,6 +7,7 @@ import { Form } from "../../../ui/components/Form";
|
|
|
7
7
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
8
8
|
import { applyZodIssues } from "../../../ui/forms/FormShell";
|
|
9
9
|
import { forgotPasswordSchema } from "../schemas";
|
|
10
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
10
11
|
export function ForgotPasswordView({ onSubmit, isLoading = false, error, success, labels = {}, renderBackLink, className = "", }) {
|
|
11
12
|
const [email, setEmail] = useState("");
|
|
12
13
|
const { showToast } = useToast();
|
|
@@ -26,7 +27,7 @@ export function ForgotPasswordView({ onSubmit, isLoading = false, error, success
|
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Forgot Password" }), _jsx(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: labels.description ??
|
|
29
|
-
"Enter your email and we'll send you a reset link." })] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), success && (_jsx(Alert, { variant: "success", compact: true, children: success })), !success && (_jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: email, onChange: setEmail }), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: buildClickHandler(setFieldError, clearErrors), children: isLoading
|
|
30
|
+
"Enter your email and we'll send you a reset link." })] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), success && (_jsx(Alert, { variant: "success", compact: true, children: success })), !success && (_jsx(Form, { schema: forgotPasswordSchema, noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: email, onChange: setEmail }), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: buildClickHandler(setFieldError, clearErrors), children: isLoading
|
|
30
31
|
? (labels.submittingLabel ?? "Sending…")
|
|
31
32
|
: (labels.submitLabel ?? "Send reset link") })] })) })), renderBackLink && (_jsx(Div, { textSize: "sm", className: "text-center", children: renderBackLink() }))] }) }));
|
|
32
33
|
}
|
|
@@ -9,6 +9,7 @@ import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
|
|
|
9
9
|
import { applyZodIssues } from "../../../ui/forms/FormShell";
|
|
10
10
|
import { loginSchema } from "../schemas";
|
|
11
11
|
import { SocialAuthButtons } from "./SocialAuthButtons";
|
|
12
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
12
13
|
export function LoginForm({ onSubmit, onGoogleLogin, isLoading = false, error, labels = {}, renderSocialButtons, renderCreateAccountLink, renderForgotPasswordLink, className = "", }) {
|
|
13
14
|
const { showToast } = useToast();
|
|
14
15
|
const [values, setValues] = useState({
|
|
@@ -16,7 +17,7 @@ export function LoginForm({ onSubmit, onGoogleLogin, isLoading = false, error, l
|
|
|
16
17
|
password: "",
|
|
17
18
|
rememberMe: false,
|
|
18
19
|
});
|
|
19
|
-
return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Row, { justify: "center", className: "mb-3", children: _jsx(SiteMark, { size: "lg" }) }), _jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Sign In" }), (labels.subtitle || renderCreateAccountLink) && (_jsxs(Text, { className: "mt-2", color: "muted", size: "sm", children: [labels.subtitle ?? "Or", " ", renderCreateAccountLink?.()] }))] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "current-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), _jsxs(Row, { justify: "between", children: [_jsx(FieldCheckbox, { name: "rememberMe", label: labels.rememberMe ?? "Remember me", checked: values.rememberMe ?? false, onChange: (c) => setValues({ ...values, rememberMe: c }) }), renderForgotPasswordLink?.()] }), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
|
|
20
|
+
return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Row, { justify: "center", className: "mb-3", children: _jsx(SiteMark, { size: "lg" }) }), _jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Sign In" }), (labels.subtitle || renderCreateAccountLink) && (_jsxs(Text, { className: "mt-2", color: "muted", size: "sm", children: [labels.subtitle ?? "Or", " ", renderCreateAccountLink?.()] }))] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { schema: loginSchema, noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "current-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), _jsxs(Row, { justify: "between", children: [_jsx(FieldCheckbox, { name: "rememberMe", label: labels.rememberMe ?? "Remember me", checked: values.rememberMe ?? false, onChange: (c) => setValues({ ...values, rememberMe: c }) }), renderForgotPasswordLink?.()] }), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
|
|
20
21
|
clearErrors();
|
|
21
22
|
const parsed = loginSchema.safeParse(values);
|
|
22
23
|
if (!parsed.success)
|
|
@@ -9,6 +9,7 @@ import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
|
9
9
|
import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
|
|
10
10
|
import { applyZodIssues } from "../../../ui/forms/FormShell";
|
|
11
11
|
import { registerSchema, registerPasswordSchema } from "../schemas";
|
|
12
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
12
13
|
const registerClientSchema = registerSchema.extend({
|
|
13
14
|
displayName: z.string().min(1, "Enter your name"),
|
|
14
15
|
confirmPassword: registerPasswordSchema,
|
|
@@ -28,7 +29,7 @@ export function RegisterForm({ onSubmit, isLoading = false, error, success, labe
|
|
|
28
29
|
displayName: "",
|
|
29
30
|
acceptTerms: false,
|
|
30
31
|
});
|
|
31
|
-
return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Row, { justify: "center", className: "mb-3", children: _jsx(SiteMark, { size: "lg" }) }), _jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Create Account" }), (labels.subtitle || renderLoginLink) && (_jsxs(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: [labels.subtitle ?? "Already have an account?", " ", renderLoginLink?.()] }))] }), success && (_jsx(Alert, { variant: "success", compact: true, children: success })), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "displayName", label: labels.displayNameLabel ?? "Full name", type: "text", autoComplete: "name", required: true, placeholder: labels.displayNamePlaceholder ?? "Your name", value: values.displayName, onChange: (v) => setValues({ ...values, displayName: v }) }), _jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsxs(Div, { children: [_jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), renderPasswordStrength?.(values.password)] }), _jsx(FieldInput, { name: "confirmPassword", label: labels.confirmPasswordLabel ?? "Confirm password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.confirmPasswordPlaceholder ?? "••••••••", value: values.confirmPassword, onChange: (v) => setValues({ ...values, confirmPassword: v }) }), _jsx(FieldCheckbox, { name: "acceptTerms", label: `${labels.acceptTermsLabel ?? "I accept the terms"}`, checked: values.acceptTerms, onChange: (c) => setValues({ ...values, acceptTerms: c }) }), renderTermsLink && (_jsx(Text, { size: "xs", variant: "secondary", children: renderTermsLink() })), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
|
|
32
|
+
return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Row, { justify: "center", className: "mb-3", children: _jsx(SiteMark, { size: "lg" }) }), _jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Create Account" }), (labels.subtitle || renderLoginLink) && (_jsxs(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: [labels.subtitle ?? "Already have an account?", " ", renderLoginLink?.()] }))] }), success && (_jsx(Alert, { variant: "success", compact: true, children: success })), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { schema: registerClientSchema, noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(FieldInput, { name: "displayName", label: labels.displayNameLabel ?? "Full name", type: "text", autoComplete: "name", required: true, placeholder: labels.displayNamePlaceholder ?? "Your name", value: values.displayName, onChange: (v) => setValues({ ...values, displayName: v }) }), _jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsxs(Div, { children: [_jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), renderPasswordStrength?.(values.password)] }), _jsx(FieldInput, { name: "confirmPassword", label: labels.confirmPasswordLabel ?? "Confirm password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.confirmPasswordPlaceholder ?? "••••••••", value: values.confirmPassword, onChange: (v) => setValues({ ...values, confirmPassword: v }) }), _jsx(FieldCheckbox, { name: "acceptTerms", label: `${labels.acceptTermsLabel ?? "I accept the terms"}`, checked: values.acceptTerms, onChange: (c) => setValues({ ...values, acceptTerms: c }) }), renderTermsLink && (_jsx(Text, { size: "xs", variant: "secondary", children: renderTermsLink() })), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
|
|
32
33
|
clearErrors();
|
|
33
34
|
const parsed = registerClientSchema.safeParse(values);
|
|
34
35
|
if (!parsed.success)
|
|
@@ -7,6 +7,7 @@ import { Alert, Button, Div, Heading, Row, Stack, Text, useToast } from "../../.
|
|
|
7
7
|
import { Form } from "../../../ui/components/Form";
|
|
8
8
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
9
9
|
import { applyZodIssues } from "../../../ui/forms/FormShell";
|
|
10
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
10
11
|
const resetClientSchema = z.object({
|
|
11
12
|
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
12
13
|
confirmPassword: z.string().min(6),
|
|
@@ -33,7 +34,7 @@ export function ResetPasswordView({ oobCode, onSubmit, isLoading = false, error,
|
|
|
33
34
|
}
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
|
-
return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Reset Password" }), labels.description && (_jsx(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: labels.description }))] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), success ? (_jsxs(Alert, { variant: "success", compact: true, children: [success, renderLoginLink && _jsx(Div, { className: "mt-2", children: renderLoginLink() })] })) : (_jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "New password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: password, onChange: setPassword }), _jsx(FieldInput, { name: "confirmPassword", label: labels.confirmPasswordLabel ?? "Confirm new password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.confirmPasswordPlaceholder ?? "••••••••", value: confirmPassword, onChange: setConfirmPassword }), _jsx(Button, { type: "submit", variant: "primary", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: buildClickHandler(setFieldError, clearErrors), children: isLoading
|
|
37
|
+
return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Reset Password" }), labels.description && (_jsx(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: labels.description }))] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), success ? (_jsxs(Alert, { variant: "success", compact: true, children: [success, renderLoginLink && _jsx(Div, { className: "mt-2", children: renderLoginLink() })] })) : (_jsx(Form, { schema: resetClientSchema, noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "New password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: password, onChange: setPassword }), _jsx(FieldInput, { name: "confirmPassword", label: labels.confirmPasswordLabel ?? "Confirm new password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.confirmPasswordPlaceholder ?? "••••••••", value: confirmPassword, onChange: setConfirmPassword }), _jsx(Button, { type: "submit", variant: "primary", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: buildClickHandler(setFieldError, clearErrors), children: isLoading
|
|
37
38
|
? (labels.submittingLabel ?? "Resetting…")
|
|
38
39
|
: (labels.submitLabel ?? "Reset password") })] })) }))] }) }));
|
|
39
40
|
}
|
|
@@ -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
|
|
41
|
-
|
|
42
|
-
|
|
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
|
}
|