@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.
- package/dist/client.d.ts +7 -0
- package/dist/client.js +7 -0
- package/dist/features/admin/components/AdminAdEditorView.js +4 -2
- package/dist/features/admin/components/AdminAddressEditorView.js +4 -2
- package/dist/features/admin/components/AdminEmployeeEditorView.js +3 -1
- package/dist/features/admin/components/AdminFeatureFlagsView.js +4 -2
- package/dist/features/admin/components/AdminOrderEditorView.js +4 -2
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +18 -6
- package/dist/features/admin/components/AdminSiteView.js +4 -2
- package/dist/features/admin/components/AdminStoreEditorView.js +11 -9
- package/dist/features/admin/components/AdminUsersView.js +4 -2
- package/dist/features/admin/components/BrandQuickCreateForm.js +13 -5
- package/dist/features/admin/components/CategoryQuickCreateForm.js +13 -5
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +119 -0
- package/dist/features/admin/schemas/admin-editor-forms.js +141 -0
- package/dist/features/admin/schemas/admin-ops-forms.d.ts +123 -0
- package/dist/features/admin/schemas/admin-ops-forms.js +147 -0
- 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/orders/schemas/buyer-forms.d.ts +46 -0
- package/dist/features/orders/schemas/buyer-forms.js +81 -0
- package/dist/features/products/components/MakeOfferButton.js +1 -1
- package/dist/features/scams/schemas/report-form.d.ts +63 -0
- package/dist/features/scams/schemas/report-form.js +88 -0
- package/dist/features/store-extensions/schemas/sublisting-category-form.d.ts +15 -0
- package/dist/features/store-extensions/schemas/sublisting-category-form.js +41 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -434,3 +434,10 @@ 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";
|
|
439
|
+
export { orderCancelSchema, paymentProofSchema, } from "./features/orders/schemas/buyer-forms";
|
|
440
|
+
export { scamReportFormSchema } from "./features/scams/schemas/report-form";
|
|
441
|
+
export { sublistingCategoryFormSchema } from "./features/store-extensions/schemas/sublisting-category-form";
|
|
442
|
+
export { adminAddressFormSchema, adminStoreUpdateSchema, announcementBarSchema, } from "./features/admin/schemas/admin-editor-forms";
|
|
443
|
+
export { adminOrderUpdateSchema, employeeInviteSchema, hardBanReasonSchema, featureFlagsUpdateSchema, adminAdFormSchema, } from "./features/admin/schemas/admin-ops-forms";
|
package/dist/client.js
CHANGED
|
@@ -420,3 +420,10 @@ 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";
|
|
425
|
+
export { orderCancelSchema, paymentProofSchema, } from "./features/orders/schemas/buyer-forms";
|
|
426
|
+
export { scamReportFormSchema } from "./features/scams/schemas/report-form";
|
|
427
|
+
export { sublistingCategoryFormSchema } from "./features/store-extensions/schemas/sublisting-category-form";
|
|
428
|
+
export { adminAddressFormSchema, adminStoreUpdateSchema, announcementBarSchema, } from "./features/admin/schemas/admin-editor-forms";
|
|
429
|
+
export { adminOrderUpdateSchema, employeeInviteSchema, hardBanReasonSchema, featureFlagsUpdateSchema, adminAdFormSchema, } from "./features/admin/schemas/admin-ops-forms";
|
|
@@ -7,6 +7,8 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
7
7
|
import { Alert, Button, Checkbox, Div, Form, FormActions, Input, Select, Span, Stack, StackedViewShell, Text, Toggle, useToast } from "../../../ui";
|
|
8
8
|
import { apiClient } from "../../../http";
|
|
9
9
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
|
+
import { adminAdFormSchema } from "../schemas/admin-ops-forms";
|
|
11
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
10
12
|
const __P = {
|
|
11
13
|
p3: "p-[var(--appkit-space-3)]",
|
|
12
14
|
};
|
|
@@ -223,14 +225,14 @@ export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, la
|
|
|
223
225
|
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: labels.title ?? (adId ? "Edit Ad" : "Create Ad"), sections: [
|
|
224
226
|
metaQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load ad metadata", children: metaQuery.error instanceof Error ? metaQuery.error.message : "Unknown error" })) : null,
|
|
225
227
|
adQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load ad", children: adQuery.error instanceof Error ? adQuery.error.message : "Unknown error" })) : null,
|
|
226
|
-
_jsxs(Form, { onSubmit: (event) => {
|
|
228
|
+
_jsxs(Form, { schema: adminAdFormSchema, onSubmit: (event) => {
|
|
227
229
|
event.preventDefault();
|
|
228
230
|
if (blockedByPublishValidation) {
|
|
229
231
|
showToast("Cannot save in publish-ready status until validation issues are fixed.", "warning");
|
|
230
232
|
return;
|
|
231
233
|
}
|
|
232
234
|
saveMutation.mutate();
|
|
233
|
-
}, spacing: "md", children: [_jsx(Input, { label: "Ad name", value: name, onChange: (event) => setName(event.target.value), placeholder: "Summer Campaign" }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 md:grid-cols-3", children: [_jsx(Select, { label: "Provider", value: provider, options: [
|
|
235
|
+
}, spacing: "md", children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "Ad name", value: name, onChange: (event) => setName(event.target.value), placeholder: "Summer Campaign" }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 md:grid-cols-3", children: [_jsx(Select, { label: "Provider", value: provider, options: [
|
|
234
236
|
{ label: "Manual", value: "manual" },
|
|
235
237
|
{ label: "AdSense", value: "adsense" },
|
|
236
238
|
{ label: "Third Party", value: "thirdParty" },
|
|
@@ -9,6 +9,8 @@ import { apiClient } from "../../../http";
|
|
|
9
9
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
10
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
11
11
|
import { INDIAN_STATES } from "../../account/constants/addresses";
|
|
12
|
+
import { adminAddressFormSchema } from "../schemas/admin-editor-forms";
|
|
13
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
12
14
|
const STATE_OPTIONS = INDIAN_STATES.map((s) => ({ value: s, label: s }));
|
|
13
15
|
// ── Section heading helper ─────────────────────────────────────────────────────
|
|
14
16
|
function SectionHeading({ children }) {
|
|
@@ -105,10 +107,10 @@ export function AdminAddressEditorView({ addressId, onSaved, onDeleted, ...rest
|
|
|
105
107
|
// ── Action sidebar (desktop) ───────────────────────────────────────────────
|
|
106
108
|
const actionSidebar = (_jsxs(Card, { variant: "outlined", padding: "md", spacing: "sm", children: [_jsx(Button, { type: "submit", form: "address-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? ACTIONS.ADMIN["save-changes"].label : "Create address" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete address" }))] }));
|
|
107
109
|
// ── Form sections ──────────────────────────────────────────────────────────
|
|
108
|
-
const formContent = (_jsxs(Form, { id: "address-editor-form", onSubmit: (e) => {
|
|
110
|
+
const formContent = (_jsxs(Form, { schema: adminAddressFormSchema, id: "address-editor-form", onSubmit: (e) => {
|
|
109
111
|
e.preventDefault();
|
|
110
112
|
saveMutation.mutate();
|
|
111
|
-
}, spacing: "lg", children: [_jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(SectionHeading, { children: "Ownership" }), _jsxs(Stack, { gap: "md", children: [_jsx(Row, { gap: "lg", children: ["user", "store"].map((type) => (_jsxs("label", { className: "flex cursor-pointer items-center gap-[var(--appkit-space-2)]", children: [_jsx(Checkbox, { bare: true, type: "radio", name: "ownerType", value: type, checked: form.ownerType === type, onChange: () => {
|
|
113
|
+
}, spacing: "lg", children: [_jsx(FormErrorSummary, {}), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(SectionHeading, { children: "Ownership" }), _jsxs(Stack, { gap: "md", children: [_jsx(Row, { gap: "lg", children: ["user", "store"].map((type) => (_jsxs("label", { className: "flex cursor-pointer items-center gap-[var(--appkit-space-2)]", children: [_jsx(Checkbox, { bare: true, type: "radio", name: "ownerType", value: type, checked: form.ownerType === type, onChange: () => {
|
|
112
114
|
set("ownerType", type);
|
|
113
115
|
set("ownerId", "");
|
|
114
116
|
}, className: "accent-[var(--appkit-color-primary)]" }), _jsx(Span, { size: "sm", className: "text-[var(--appkit-color-text)]", transform: "capitalize", children: type })] }, type))) }), _jsx(Input, { label: form.ownerType === "user" ? "User ID" : "Store ID (slug)", value: form.ownerId, onChange: (e) => set("ownerId", e.target.value), required: true, placeholder: form.ownerType === "user" ? "user-mohsin-c" : "store-pokemon-palace", helperText: form.ownerType === "user"
|
|
@@ -7,6 +7,8 @@ import { Button, Checkbox, ConfirmDeleteModal, Details, Div, Form, FormActions,
|
|
|
7
7
|
import { apiClient } from "../../../http";
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
9
|
import { PERMISSION_GROUPS, PERMISSION_DOMAINS, getPermissionsForDomain, formatPermLabel, } from "../../auth/permissions/constants";
|
|
10
|
+
import { employeeInviteSchema } from "../schemas/admin-ops-forms";
|
|
11
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
10
12
|
const __O = {
|
|
11
13
|
yAuto: "overflow-y-auto",
|
|
12
14
|
};
|
|
@@ -130,5 +132,5 @@ export function AdminEmployeeEditorView({ open, onClose, mode, userId, displayNa
|
|
|
130
132
|
else
|
|
131
133
|
updateMutation.mutate();
|
|
132
134
|
};
|
|
133
|
-
return (_jsxs(_Fragment, { children: [_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: mode === "invite" ? "Invite Employee" : `Edit — ${displayName ?? "Employee"}`, children: _jsxs(Form, { onSubmit: handleSubmit, spacing: "lg", padding: "md", children: [mode === "invite" && (_jsxs(Stack, { gap: "xs", className: "", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Email address" }), _jsx(Input, { type: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value), placeholder: "employee@example.com" })] })), _jsxs(Stack, { gap: "xs", className: "", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Permission group" }), _jsx(Select, { value: group, onChange: (e) => applyGroupPreset(e.target.value), options: GROUP_OPTIONS }), _jsx(Text, { size: "xs", color: "muted", children: "Selecting a group auto-fills the permissions below. You can still customise individual permissions." })] }), _jsx(PermissionDomainsPanel, { permissions: permissions, togglePerm: togglePerm }), _jsxs(FormActions, { children: [_jsx(Button, { type: "submit", variant: "primary", disabled: isBusy, isLoading: isBusy, children: mode === "invite" ? "Send Invite" : "Save" }), mode === "edit" && userId && (_jsx(Button, { type: "button", variant: "danger", disabled: isBusy, onClick: () => setRevokeOpen(true), children: "Revoke Access" }))] })] }) }), mode === "edit" && (_jsx(ConfirmDeleteModal, { isOpen: revokeOpen, onClose: () => setRevokeOpen(false), onConfirm: () => revokeMutation.mutate(), isDeleting: revokeMutation.isPending, title: "Revoke employee access?", message: `${displayName ?? "This employee"} will lose admin panel access immediately. Their user account remains active — only their role is reset to user.`, confirmText: "Revoke Access", variant: "danger" }))] }));
|
|
135
|
+
return (_jsxs(_Fragment, { children: [_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: mode === "invite" ? "Invite Employee" : `Edit — ${displayName ?? "Employee"}`, children: _jsxs(Form, { schema: employeeInviteSchema, onSubmit: handleSubmit, spacing: "lg", padding: "md", children: [_jsx(FormErrorSummary, {}), mode === "invite" && (_jsxs(Stack, { gap: "xs", className: "", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Email address" }), _jsx(Input, { type: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value), placeholder: "employee@example.com" })] })), _jsxs(Stack, { gap: "xs", className: "", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Permission group" }), _jsx(Select, { value: group, onChange: (e) => applyGroupPreset(e.target.value), options: GROUP_OPTIONS }), _jsx(Text, { size: "xs", color: "muted", children: "Selecting a group auto-fills the permissions below. You can still customise individual permissions." })] }), _jsx(PermissionDomainsPanel, { permissions: permissions, togglePerm: togglePerm }), _jsxs(FormActions, { children: [_jsx(Button, { type: "submit", variant: "primary", disabled: isBusy, isLoading: isBusy, children: mode === "invite" ? "Send Invite" : "Save" }), mode === "edit" && userId && (_jsx(Button, { type: "button", variant: "danger", disabled: isBusy, onClick: () => setRevokeOpen(true), children: "Revoke Access" }))] })] }) }), mode === "edit" && (_jsx(ConfirmDeleteModal, { isOpen: revokeOpen, onClose: () => setRevokeOpen(false), onConfirm: () => revokeMutation.mutate(), isDeleting: revokeMutation.isPending, title: "Revoke employee access?", message: `${displayName ?? "This employee"} will lose admin panel access immediately. Their user account remains active — only their role is reset to user.`, confirmText: "Revoke Access", variant: "danger" }))] }));
|
|
134
136
|
}
|
|
@@ -6,6 +6,8 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
6
6
|
import { Alert, Button, Div, Form, FormActions, Input, Row, Stack, StackedViewShell, Text, Toggle, useToast } from "../../../ui";
|
|
7
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
8
|
import { apiClient } from "../../../http";
|
|
9
|
+
import { featureFlagsUpdateSchema } from "../schemas/admin-ops-forms";
|
|
10
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
9
11
|
const __O = {
|
|
10
12
|
hidden: "overflow-hidden",
|
|
11
13
|
};
|
|
@@ -96,10 +98,10 @@ export function AdminFeatureFlagsView({ labels = {}, renderFlags, ...rest }) {
|
|
|
96
98
|
showToast("Failed to save feature flags.", "error");
|
|
97
99
|
},
|
|
98
100
|
});
|
|
99
|
-
const defaultFlags = () => (_jsxs(Form, { onSubmit: (event) => {
|
|
101
|
+
const defaultFlags = () => (_jsxs(Form, { schema: featureFlagsUpdateSchema, onSubmit: (event) => {
|
|
100
102
|
event.preventDefault();
|
|
101
103
|
saveFlags.mutate();
|
|
102
|
-
}, spacing: "md", children: [_jsx(AccordionSection, { title: "Platform Features", children: PLATFORM_FLAGS.map(({ key, label, desc }) => (_jsx(FlagRow, { label: label, desc: desc, checked: getPlatformFlag(key), onChange: (val) => setPlatformFlag(key, val), rollout: rollouts[key], onRolloutChange: (val) => setRollouts((prev) => ({ ...prev, [key]: val })), showRollout: true }, key))) }), _jsx(AccordionSection, { title: "Listing Types", children: LISTING_TYPE_FLAGS.map(({ key, label, desc }) => (_jsx(FlagRow, { label: label, desc: desc, checked: getListingTypeFlag(key), onChange: (val) => setListingTypeFlag(key, val), showRollout: false }, key))) }), _jsx(AccordionSection, { title: "Category Types", children: CATEGORY_TYPE_FLAGS.map(({ key, label, desc }) => (_jsx(FlagRow, { label: label, desc: desc, checked: getCategoryTypeFlag(key), onChange: (val) => setCategoryTypeFlag(key, val), showRollout: false }, key))) }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => {
|
|
104
|
+
}, spacing: "md", children: [_jsx(FormErrorSummary, {}), _jsx(AccordionSection, { title: "Platform Features", children: PLATFORM_FLAGS.map(({ key, label, desc }) => (_jsx(FlagRow, { label: label, desc: desc, checked: getPlatformFlag(key), onChange: (val) => setPlatformFlag(key, val), rollout: rollouts[key], onRolloutChange: (val) => setRollouts((prev) => ({ ...prev, [key]: val })), showRollout: true }, key))) }), _jsx(AccordionSection, { title: "Listing Types", children: LISTING_TYPE_FLAGS.map(({ key, label, desc }) => (_jsx(FlagRow, { label: label, desc: desc, checked: getListingTypeFlag(key), onChange: (val) => setListingTypeFlag(key, val), showRollout: false }, key))) }), _jsx(AccordionSection, { title: "Category Types", children: CATEGORY_TYPE_FLAGS.map(({ key, label, desc }) => (_jsx(FlagRow, { label: label, desc: desc, checked: getCategoryTypeFlag(key), onChange: (val) => setCategoryTypeFlag(key, val), showRollout: false }, key))) }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => {
|
|
103
105
|
setFlags(data?.flags ?? {});
|
|
104
106
|
setRollouts(data?.rollouts ?? {});
|
|
105
107
|
}, disabled: saveFlags.isPending, children: "Reset" }), _jsx(Button, { type: "submit", isLoading: saveFlags.isPending, disabled: saveFlags.isPending, children: "Save All Flags" })] })] }));
|
|
@@ -12,6 +12,8 @@ import { apiClient } from "../../../http";
|
|
|
12
12
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
13
13
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
14
14
|
import { toCurrency } from "../hooks/useAdminListingData";
|
|
15
|
+
import { adminOrderUpdateSchema } from "../schemas/admin-ops-forms";
|
|
16
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
15
17
|
const STATUS_OPTIONS = [
|
|
16
18
|
{ label: "Pending", value: "pending" },
|
|
17
19
|
{ label: "Processing", value: "processing" },
|
|
@@ -146,8 +148,8 @@ export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, curre
|
|
|
146
148
|
setIsRejectingFraud(false);
|
|
147
149
|
}
|
|
148
150
|
};
|
|
149
|
-
return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: orderLabel ? `Order: ${orderLabel}` : "Update Order", children: _jsxs(Form, { onSubmit: (e) => {
|
|
151
|
+
return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: orderLabel ? `Order: ${orderLabel}` : "Update Order", children: _jsxs(Form, { schema: adminOrderUpdateSchema, onSubmit: (e) => {
|
|
150
152
|
e.preventDefault();
|
|
151
153
|
saveMutation.mutate();
|
|
152
|
-
}, spacing: "md", padding: "md", children: [addons && _jsx(OrderAddonBadges, { order: addons, variant: "detail" }), items && items.length > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Label, { size: "sm", weight: "medium", color: "primary", children: ["Items (", items.length, ")"] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: items.map((item, i) => (_jsxs(Div, { layout: "flex", align: "center", gap: "3", padding: "sm", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) }), _jsxs(Div, { className: "min-w-0 flex-1", children: [_jsx(Text, { size: "sm", className: "truncate", weight: "medium", children: item.title }), _jsxs(Text, { size: "xs", color: "muted", children: ["Qty: ", item.quantity, " \u00D7 ", toCurrency(item.unitPrice)] })] }), _jsx(Text, { size: "sm", className: "shrink-0", weight: "semibold", children: toCurrency(item.totalPrice) })] }, item.productId || i))) })] })), _jsx(Select, { label: "Order status", options: STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsx(Input, { label: "Tracking number (optional)", value: trackingNumber, onChange: (e) => setTrackingNumber(e.target.value), placeholder: "e.g. DEL1234567890IN" }), _jsx(Select, { label: "Carrier (optional)", options: CARRIER_OPTIONS, value: carrier, onValueChange: setCarrier }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Internal note (optional)" }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), rows: 3, placeholder: "Reason for status change, escalation notes\u2026" })] }), (status === "refunded" || status === "return_requested") && (_jsx(Input, { label: "Refund amount \u20B9 (optional)", type: "number", min: "0", step: "0.01", value: refundAmount, onChange: (e) => setRefundAmount(e.target.value), placeholder: "e.g. 499.00" })), isManualPayment && (_jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Payment Proof" }), isRejected && (_jsx(Text, { size: "xs", className: "text-error", weight: "medium", children: "Payment rejected as fraud \u2014 no further action available here." })), isReuploadRequested && !isVerified && (_jsx(Text, { size: "xs", className: "text-warning", weight: "medium", children: "Re-upload requested \u2014 waiting for the buyer to submit a new proof." })), paymentProofUrl ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Div, { border: "default", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: paymentProofUrl, alt: "Payment screenshot", size: "card" }) }), paymentTransactionId && (_jsxs(Text, { size: "xs", color: "muted", children: ["UTR: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: paymentTransactionId })] })), needsVerification && (_jsxs(Stack, { gap: "xs", children: [(displayedUpiId || buyerReportedUpiId) && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Expected UPI: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: displayedUpiId || "—" }), " · ", "Buyer reported: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerReportedUpiId || "—" })] }), paymentUpiMismatch && (_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-error/20", surface: "danger-surface", children: _jsx(Text, { size: "xs", className: "text-error", weight: "semibold", children: "UPI mismatch \u2014 buyer-reported ID doesn't match the one shown for this order." }) }))] })), _jsxs(Text, { size: "xs", color: "muted", children: ["Buyer marked as paid: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerMarkedPaid ? "Yes" : "No" }), " · ", "Fraud agreement accepted: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerFraudAgreementAccepted ? "Yes" : "No" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Review note (required for re-upload / reject)" }), _jsx(Textarea, { value: reviewNote, onChange: (e) => setReviewNote(e.target.value), rows: 2, placeholder: "e.g. Screenshot is blurry \u2014 amount not readable / UPI ID doesn't match order" })] }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["verify-payment"], onClick: handleVerifyPayment, isLoading: isVerifyingPayment, disabled: isVerifyingPayment, variant: "primary", className: "w-full" }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["request-payment-reupload"], onClick: handleRequestReupload, isLoading: isRequestingReupload, disabled: isRequestingReupload || !reviewNote.trim(), variant: "secondary", className: "w-full" }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["reject-payment-fraud"], onClick: handleRejectFraud, isLoading: isRejectingFraud, disabled: isRejectingFraud || !reviewNote.trim(), variant: "danger", className: "w-full" })] })), isVerified && (_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-success/20", surface: "success-surface", children: _jsx(Text, { size: "xs", className: "text-success", weight: "medium", children: "Payment verified" }) }))] })) : (_jsx(Text, { size: "xs", color: "faint", children: "No proof uploaded yet." }))] })), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !orderId || saveMutation.isPending, children: "Save changes" })] })] }) }));
|
|
154
|
+
}, spacing: "md", padding: "md", children: [_jsx(FormErrorSummary, {}), addons && _jsx(OrderAddonBadges, { order: addons, variant: "detail" }), items && items.length > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Label, { size: "sm", weight: "medium", color: "primary", children: ["Items (", items.length, ")"] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: items.map((item, i) => (_jsxs(Div, { layout: "flex", align: "center", gap: "3", padding: "sm", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) }), _jsxs(Div, { className: "min-w-0 flex-1", children: [_jsx(Text, { size: "sm", className: "truncate", weight: "medium", children: item.title }), _jsxs(Text, { size: "xs", color: "muted", children: ["Qty: ", item.quantity, " \u00D7 ", toCurrency(item.unitPrice)] })] }), _jsx(Text, { size: "sm", className: "shrink-0", weight: "semibold", children: toCurrency(item.totalPrice) })] }, item.productId || i))) })] })), _jsx(Select, { label: "Order status", options: STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsx(Input, { label: "Tracking number (optional)", value: trackingNumber, onChange: (e) => setTrackingNumber(e.target.value), placeholder: "e.g. DEL1234567890IN" }), _jsx(Select, { label: "Carrier (optional)", options: CARRIER_OPTIONS, value: carrier, onValueChange: setCarrier }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Internal note (optional)" }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), rows: 3, placeholder: "Reason for status change, escalation notes\u2026" })] }), (status === "refunded" || status === "return_requested") && (_jsx(Input, { label: "Refund amount \u20B9 (optional)", type: "number", min: "0", step: "0.01", value: refundAmount, onChange: (e) => setRefundAmount(e.target.value), placeholder: "e.g. 499.00" })), isManualPayment && (_jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Payment Proof" }), isRejected && (_jsx(Text, { size: "xs", className: "text-error", weight: "medium", children: "Payment rejected as fraud \u2014 no further action available here." })), isReuploadRequested && !isVerified && (_jsx(Text, { size: "xs", className: "text-warning", weight: "medium", children: "Re-upload requested \u2014 waiting for the buyer to submit a new proof." })), paymentProofUrl ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Div, { border: "default", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: paymentProofUrl, alt: "Payment screenshot", size: "card" }) }), paymentTransactionId && (_jsxs(Text, { size: "xs", color: "muted", children: ["UTR: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: paymentTransactionId })] })), needsVerification && (_jsxs(Stack, { gap: "xs", children: [(displayedUpiId || buyerReportedUpiId) && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Expected UPI: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: displayedUpiId || "—" }), " · ", "Buyer reported: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerReportedUpiId || "—" })] }), paymentUpiMismatch && (_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-error/20", surface: "danger-surface", children: _jsx(Text, { size: "xs", className: "text-error", weight: "semibold", children: "UPI mismatch \u2014 buyer-reported ID doesn't match the one shown for this order." }) }))] })), _jsxs(Text, { size: "xs", color: "muted", children: ["Buyer marked as paid: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerMarkedPaid ? "Yes" : "No" }), " · ", "Fraud agreement accepted: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerFraudAgreementAccepted ? "Yes" : "No" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Review note (required for re-upload / reject)" }), _jsx(Textarea, { value: reviewNote, onChange: (e) => setReviewNote(e.target.value), rows: 2, placeholder: "e.g. Screenshot is blurry \u2014 amount not readable / UPI ID doesn't match order" })] }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["verify-payment"], onClick: handleVerifyPayment, isLoading: isVerifyingPayment, disabled: isVerifyingPayment, variant: "primary", className: "w-full" }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["request-payment-reupload"], onClick: handleRequestReupload, isLoading: isRequestingReupload, disabled: isRequestingReupload || !reviewNote.trim(), variant: "secondary", className: "w-full" }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["reject-payment-fraud"], onClick: handleRejectFraud, isLoading: isRejectingFraud, disabled: isRejectingFraud || !reviewNote.trim(), variant: "danger", className: "w-full" })] })), isVerified && (_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-success/20", surface: "success-surface", children: _jsx(Text, { size: "xs", className: "text-success", weight: "medium", children: "Payment verified" }) }))] })) : (_jsx(Text, { size: "xs", color: "faint", children: "No proof uploaded yet." }))] })), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !orderId || saveMutation.isPending, children: "Save changes" })] })] }) }));
|
|
153
155
|
}
|
|
@@ -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
|
}
|
|
@@ -7,6 +7,8 @@ import { Alert, Button, Form, FormActions, Input, StackedViewShell, Toggle, } fr
|
|
|
7
7
|
import { useSiteSettings } from "../../../core/hooks/useSiteSettings";
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
9
|
import { apiClient } from "../../../http";
|
|
10
|
+
import { announcementBarSchema } from "../schemas/admin-editor-forms";
|
|
11
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
10
12
|
export function AdminSiteView({ labels = {}, renderTabs, renderForm, ...rest }) {
|
|
11
13
|
const queryClient = useQueryClient();
|
|
12
14
|
const { data, isLoading, error } = useSiteSettings();
|
|
@@ -40,10 +42,10 @@ export function AdminSiteView({ labels = {}, renderTabs, renderForm, ...rest })
|
|
|
40
42
|
setSaveMessage("Failed to save announcement settings.");
|
|
41
43
|
},
|
|
42
44
|
});
|
|
43
|
-
const defaultForm = () => (_jsxs(Form, { onSubmit: (event) => {
|
|
45
|
+
const defaultForm = () => (_jsxs(Form, { schema: announcementBarSchema, onSubmit: (event) => {
|
|
44
46
|
event.preventDefault();
|
|
45
47
|
saveAnnouncement.mutate();
|
|
46
|
-
}, spacing: "md", children: [_jsx(Toggle, { checked: announcementEnabled, onChange: setAnnouncementEnabled, label: "Show announcement bar on homepage" }), _jsx(Input, { label: "Announcement message", value: announcementMessage, onChange: (event) => setAnnouncementMessage(event.target.value), placeholder: "Enter homepage announcement", disabled: !announcementEnabled, helperText: "This message is shown at the top of the homepage when enabled." }), _jsx(FormActions, { align: "right", children: _jsx(Button, { type: "submit", disabled: saveAnnouncement.isPending || (announcementEnabled && !announcementMessage.trim()), children: saveAnnouncement.isPending ? "Saving..." : "Save announcement" }) }), saveMessage ? (_jsx(Alert, { variant: saveMessage.startsWith("Failed") ? "error" : "success", title: saveMessage.startsWith("Failed") ? "Save failed" : "Saved", children: saveMessage })) : null] }));
|
|
48
|
+
}, spacing: "md", children: [_jsx(FormErrorSummary, {}), _jsx(Toggle, { checked: announcementEnabled, onChange: setAnnouncementEnabled, label: "Show announcement bar on homepage" }), _jsx(Input, { label: "Announcement message", value: announcementMessage, onChange: (event) => setAnnouncementMessage(event.target.value), placeholder: "Enter homepage announcement", disabled: !announcementEnabled, helperText: "This message is shown at the top of the homepage when enabled." }), _jsx(FormActions, { align: "right", children: _jsx(Button, { type: "submit", disabled: saveAnnouncement.isPending || (announcementEnabled && !announcementMessage.trim()), children: saveAnnouncement.isPending ? "Saving..." : "Save announcement" }) }), saveMessage ? (_jsx(Alert, { variant: saveMessage.startsWith("Failed") ? "error" : "success", title: saveMessage.startsWith("Failed") ? "Save failed" : "Saved", children: saveMessage })) : null] }));
|
|
47
49
|
const loadingAlert = isLoading ? (_jsx(Alert, { variant: "info", title: "Loading settings", children: "Fetching site settings..." })) : null;
|
|
48
50
|
const errorAlert = error ? (_jsx(Alert, { variant: "error", title: "Could not load settings", children: error instanceof Error ? error.message : "Unknown error" })) : null;
|
|
49
51
|
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: labels.title ?? "Site Settings", sections: [renderTabs?.(), loadingAlert, errorAlert, renderForm?.() ?? defaultForm()] }));
|
|
@@ -6,6 +6,8 @@ import { useQueryClient } from "@tanstack/react-query";
|
|
|
6
6
|
import { Button, Checkbox, Details, Div, Form, FormActions, Label, Select, SideDrawer, Span, Stack, StackedViewShell, Summary, Textarea, Toggle, useToast } from "../../../ui";
|
|
7
7
|
import { apiClient } from "../../../http";
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { adminStoreUpdateSchema } from "../schemas/admin-editor-forms";
|
|
10
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
9
11
|
const CAPABILITY_GROUPS = [
|
|
10
12
|
{
|
|
11
13
|
label: "Listings",
|
|
@@ -106,15 +108,15 @@ export function AdminStoreEditorView({ open, onClose, storeId, storeName, curren
|
|
|
106
108
|
return (_jsxs(Details, { className: "group", children: [_jsxs(Summary, { paddingX: "x-sm", paddingY: "y-xs", weight: "semibold", color: "muted", layout: "flex", align: "center", justify: "between", className: "uppercase tracking-wide hover:bg-[var(--appkit-color-bg)] transition-colors", children: [_jsx(Span, { size: "xs", children: group.label }), _jsxs(Span, { size: "xs", weight: "normal", className: "normal-case", color: "faint", children: [checked, "/", group.caps.length] })] }), _jsx(Div, { layout: "grid", paddingY: "y-xs-tall", className: "grid-cols-2 gap-x-2 gap-y-1.5", surface: "muted", padding: "x-sm", children: group.caps.map((cap) => (_jsxs("label", { className: "flex items-center gap-[var(--appkit-space-2)] cursor-pointer text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]", children: [_jsx(Checkbox, { bare: true, checked: capabilities.has(cap.key), onChange: () => toggleCapability(cap.key), className: "h-3.5 w-3.5 rounded border-[var(--appkit-color-border)] accent-primary" }), cap.label] }, cap.key))) })] }, group.label));
|
|
107
109
|
}) })] }));
|
|
108
110
|
const renderActionsSection = () => (_jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !storeId || saveMutation.isPending, children: "Save changes" })] }));
|
|
109
|
-
return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: storeName ? `Manage: ${storeName}` : "Manage Store", children:
|
|
111
|
+
return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: storeName ? `Manage: ${storeName}` : "Manage Store", children: _jsxs(Form, { schema: adminStoreUpdateSchema, onSubmit: (e) => {
|
|
110
112
|
e.preventDefault();
|
|
111
113
|
saveMutation.mutate();
|
|
112
|
-
}, padding: "md", children: _jsx(StackedViewShell, { portal: "admin", className: "space-y-4", sections: [
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
}, padding: "md", children: [_jsx(FormErrorSummary, {}), _jsx(StackedViewShell, { portal: "admin", className: "space-y-4", sections: [
|
|
115
|
+
renderStatusSection,
|
|
116
|
+
renderNotesSection,
|
|
117
|
+
renderTogglesSection,
|
|
118
|
+
renderSuspensionSection,
|
|
119
|
+
renderCapabilitiesSection,
|
|
120
|
+
renderActionsSection,
|
|
121
|
+
] })] }) }));
|
|
120
122
|
}
|
|
@@ -18,6 +18,8 @@ import { useBulkAction } from "../../../react";
|
|
|
18
18
|
import { useBulkEvent } from "../../events/hooks/useBulkEvent";
|
|
19
19
|
import { RTDB_PATHS } from "../../../providers/db-firebase/rtdb-paths";
|
|
20
20
|
import { RealtimeEventStatus } from "../../../react/hooks/useRealtimeEvent";
|
|
21
|
+
import { hardBanReasonSchema } from "../schemas/admin-ops-forms";
|
|
22
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
21
23
|
export function AdminUsersView({ children, ...props }) {
|
|
22
24
|
const toast = useToast();
|
|
23
25
|
const queryClient = useQueryClient();
|
|
@@ -247,10 +249,10 @@ export function AdminUsersView({ children, ...props }) {
|
|
|
247
249
|
setBanModalOpen(false);
|
|
248
250
|
setBanTargetId(null);
|
|
249
251
|
setBanReason("");
|
|
250
|
-
}, title: ACTIONS.ADMIN["ban-user"].confirmation.title, children: [_jsx(AppText, { size: "sm", color: "muted", className: "mb-4", children: ACTIONS.ADMIN["ban-user"].confirmation.body }), _jsxs(Form, { onSubmit: (e) => {
|
|
252
|
+
}, title: ACTIONS.ADMIN["ban-user"].confirmation.title, children: [_jsx(AppText, { size: "sm", color: "muted", className: "mb-4", children: ACTIONS.ADMIN["ban-user"].confirmation.body }), _jsxs(Form, { schema: hardBanReasonSchema, onSubmit: (e) => {
|
|
251
253
|
e.preventDefault();
|
|
252
254
|
banUser.mutate();
|
|
253
|
-
}, children: [_jsx(Input, { label: "Reason", value: banReason, onChange: (e) => setBanReason(e.target.value), placeholder: "e.g. Repeated fraud, scam activity\u2026", required: true }), _jsxs(FormActions, { children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => {
|
|
255
|
+
}, children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "Reason", value: banReason, onChange: (e) => setBanReason(e.target.value), placeholder: "e.g. Repeated fraud, scam activity\u2026", required: true }), _jsxs(FormActions, { children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => {
|
|
254
256
|
setBanModalOpen(false);
|
|
255
257
|
setBanTargetId(null);
|
|
256
258
|
setBanReason("");
|
|
@@ -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,119 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* An address written by an admin on someone's behalf.
|
|
4
|
+
*
|
|
5
|
+
* Shapes match `AddressDocument` (`addressLine1`, not `line1`) — deliberately
|
|
6
|
+
* NOT `userAddressSchema`, which is the account-side view of the same data
|
|
7
|
+
* under different field names. Reusing that one here would have meant
|
|
8
|
+
* translating on every write.
|
|
9
|
+
*/
|
|
10
|
+
export declare const adminAddressFormSchema: z.ZodObject<{
|
|
11
|
+
ownerType: z.ZodEnum<["user", "store"]>;
|
|
12
|
+
ownerId: z.ZodString;
|
|
13
|
+
label: z.ZodOptional<z.ZodString>;
|
|
14
|
+
fullName: z.ZodString;
|
|
15
|
+
phone: z.ZodString;
|
|
16
|
+
addressLine1: z.ZodString;
|
|
17
|
+
addressLine2: z.ZodOptional<z.ZodString>;
|
|
18
|
+
landmark: z.ZodOptional<z.ZodString>;
|
|
19
|
+
city: z.ZodString;
|
|
20
|
+
state: z.ZodString;
|
|
21
|
+
postalCode: z.ZodString;
|
|
22
|
+
country: z.ZodString;
|
|
23
|
+
isDefault: z.ZodOptional<z.ZodBoolean>;
|
|
24
|
+
}, "strip", z.ZodTypeAny, {
|
|
25
|
+
country: string;
|
|
26
|
+
ownerId: string;
|
|
27
|
+
ownerType: "user" | "store";
|
|
28
|
+
fullName: string;
|
|
29
|
+
phone: string;
|
|
30
|
+
addressLine1: string;
|
|
31
|
+
city: string;
|
|
32
|
+
state: string;
|
|
33
|
+
postalCode: string;
|
|
34
|
+
label?: string | undefined;
|
|
35
|
+
isDefault?: boolean | undefined;
|
|
36
|
+
addressLine2?: string | undefined;
|
|
37
|
+
landmark?: string | undefined;
|
|
38
|
+
}, {
|
|
39
|
+
country: string;
|
|
40
|
+
ownerId: string;
|
|
41
|
+
ownerType: "user" | "store";
|
|
42
|
+
fullName: string;
|
|
43
|
+
phone: string;
|
|
44
|
+
addressLine1: string;
|
|
45
|
+
city: string;
|
|
46
|
+
state: string;
|
|
47
|
+
postalCode: string;
|
|
48
|
+
label?: string | undefined;
|
|
49
|
+
isDefault?: boolean | undefined;
|
|
50
|
+
addressLine2?: string | undefined;
|
|
51
|
+
landmark?: string | undefined;
|
|
52
|
+
}>;
|
|
53
|
+
export type AdminAddressFormValues = z.infer<typeof adminAddressFormSchema>;
|
|
54
|
+
/**
|
|
55
|
+
* An admin changing a store's standing.
|
|
56
|
+
*
|
|
57
|
+
* `suspensionReason` is required when — and only when — the status is
|
|
58
|
+
* `suspended`: suspending a seller without a recorded reason leaves them, and
|
|
59
|
+
* the next admin, with nothing to appeal or review.
|
|
60
|
+
*/
|
|
61
|
+
export declare const adminStoreUpdateSchema: z.ZodEffects<z.ZodObject<{
|
|
62
|
+
storeStatus: z.ZodEnum<["pending", "active", "suspended", "closed"]>;
|
|
63
|
+
isVerified: z.ZodOptional<z.ZodBoolean>;
|
|
64
|
+
isFeatured: z.ZodOptional<z.ZodBoolean>;
|
|
65
|
+
suspensionReason: z.ZodOptional<z.ZodString>;
|
|
66
|
+
capabilities: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
67
|
+
adminNotes: z.ZodOptional<z.ZodString>;
|
|
68
|
+
}, "strip", z.ZodTypeAny, {
|
|
69
|
+
storeStatus: "closed" | "pending" | "active" | "suspended";
|
|
70
|
+
isVerified?: boolean | undefined;
|
|
71
|
+
isFeatured?: boolean | undefined;
|
|
72
|
+
adminNotes?: string | undefined;
|
|
73
|
+
suspensionReason?: string | undefined;
|
|
74
|
+
capabilities?: string[] | undefined;
|
|
75
|
+
}, {
|
|
76
|
+
storeStatus: "closed" | "pending" | "active" | "suspended";
|
|
77
|
+
isVerified?: boolean | undefined;
|
|
78
|
+
isFeatured?: boolean | undefined;
|
|
79
|
+
adminNotes?: string | undefined;
|
|
80
|
+
suspensionReason?: string | undefined;
|
|
81
|
+
capabilities?: string[] | undefined;
|
|
82
|
+
}>, {
|
|
83
|
+
storeStatus: "closed" | "pending" | "active" | "suspended";
|
|
84
|
+
isVerified?: boolean | undefined;
|
|
85
|
+
isFeatured?: boolean | undefined;
|
|
86
|
+
adminNotes?: string | undefined;
|
|
87
|
+
suspensionReason?: string | undefined;
|
|
88
|
+
capabilities?: string[] | undefined;
|
|
89
|
+
}, {
|
|
90
|
+
storeStatus: "closed" | "pending" | "active" | "suspended";
|
|
91
|
+
isVerified?: boolean | undefined;
|
|
92
|
+
isFeatured?: boolean | undefined;
|
|
93
|
+
adminNotes?: string | undefined;
|
|
94
|
+
suspensionReason?: string | undefined;
|
|
95
|
+
capabilities?: string[] | undefined;
|
|
96
|
+
}>;
|
|
97
|
+
export type AdminStoreUpdateValues = z.infer<typeof adminStoreUpdateSchema>;
|
|
98
|
+
/**
|
|
99
|
+
* The homepage announcement bar.
|
|
100
|
+
*
|
|
101
|
+
* 🛑 `message` is the canonical key. The main admin editor saved this copy
|
|
102
|
+
* under `text` until 2026-08-24 — a key no renderer has ever read — so
|
|
103
|
+
* admin-authored announcements saved successfully and never appeared. Naming
|
|
104
|
+
* it here is what stops that recurring.
|
|
105
|
+
*/
|
|
106
|
+
export declare const announcementBarSchema: z.ZodObject<{
|
|
107
|
+
message: z.ZodOptional<z.ZodString>;
|
|
108
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
109
|
+
link: z.ZodOptional<z.ZodString>;
|
|
110
|
+
}, "strip", z.ZodTypeAny, {
|
|
111
|
+
message?: string | undefined;
|
|
112
|
+
link?: string | undefined;
|
|
113
|
+
enabled?: boolean | undefined;
|
|
114
|
+
}, {
|
|
115
|
+
message?: string | undefined;
|
|
116
|
+
link?: string | undefined;
|
|
117
|
+
enabled?: boolean | undefined;
|
|
118
|
+
}>;
|
|
119
|
+
export type AnnouncementBarValues = z.infer<typeof announcementBarSchema>;
|