@mohasinac/appkit 2.7.45 → 2.7.46

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 CHANGED
@@ -57,8 +57,8 @@ export { OtpInput } from "./ui/components/OtpInput";
57
57
  export type { OtpInputProps } from "./ui/components/OtpInput";
58
58
  export { DateInput, DateRangeInput } from "./ui/components/DateInput";
59
59
  export type { DateInputProps, DateRangeInputProps } from "./ui/components/DateInput";
60
- export type { FormShellProps, FormShellStep, FormShellContextValue } from "./ui/forms";
61
- export { FormShell, useFormShell } from "./ui/forms";
60
+ export type { FormShellProps, FormShellProviderProps, FormShellStep, FormShellContextValue, UseFormShellStateResult } from "./ui/forms";
61
+ export { FormShell, FormShellProvider, FormShellContext, useFormShell, useFormShellState } from "./ui/forms";
62
62
  export type { FieldInputProps } from "./ui/forms";
63
63
  export { FieldInput } from "./ui/forms";
64
64
  export type { FieldSelectProps } from "./ui/forms";
package/dist/client.js CHANGED
@@ -123,7 +123,7 @@ export { Checkbox } from "./ui/components/Checkbox";
123
123
  export { Input } from "./ui/components/Input";
124
124
  export { OtpInput } from "./ui/components/OtpInput";
125
125
  export { DateInput, DateRangeInput } from "./ui/components/DateInput";
126
- export { FormShell, useFormShell } from "./ui/forms";
126
+ export { FormShell, FormShellProvider, FormShellContext, useFormShell, useFormShellState } from "./ui/forms";
127
127
  export { FieldInput } from "./ui/forms";
128
128
  export { FieldSelect } from "./ui/forms";
129
129
  export { FieldCheckbox } from "./ui/forms";
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { Button, Checkbox, FormField, FormGroup } from "../../../ui";
5
+ import { useFormShellState } from "../../../ui/forms";
5
6
  import { THEME_CONSTANTS } from "../../../tokens";
6
7
  const DEFAULT_LABELS = {
7
8
  label: "Address Label",
@@ -33,6 +34,7 @@ export function AddressForm({ initialData, onSubmit, onCancel, isLoading = false
33
34
  const mergedLabels = { ...DEFAULT_LABELS, ...labels };
34
35
  const mergedPlaceholders = { ...DEFAULT_PLACEHOLDERS, ...placeholders };
35
36
  const effectiveSubmitLabel = submitLabel ?? mergedLabels.save;
37
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState();
36
38
  const [formData, setFormData] = useState({
37
39
  label: initialData?.label || "",
38
40
  fullName: initialData?.fullName || "",
@@ -47,10 +49,30 @@ export function AddressForm({ initialData, onSubmit, onCancel, isLoading = false
47
49
  });
48
50
  const handleChange = (field, value) => {
49
51
  setFormData((prev) => ({ ...prev, [field]: value }));
52
+ if (typeof value === "string" && value.trim())
53
+ setFieldError(field, null);
50
54
  };
51
55
  const handleSubmit = async (e) => {
52
56
  e.preventDefault();
57
+ clearErrors();
58
+ const errs = {};
59
+ if (!formData.fullName.trim())
60
+ errs["fullName"] = "Full name is required";
61
+ if (!formData.phone.trim())
62
+ errs["phone"] = "Phone number is required";
63
+ if (!formData.addressLine1.trim())
64
+ errs["addressLine1"] = "Address is required";
65
+ if (!formData.city.trim())
66
+ errs["city"] = "City is required";
67
+ if (!formData.state.trim())
68
+ errs["state"] = "State is required";
69
+ if (!formData.postalCode.trim())
70
+ errs["postalCode"] = "Postal code is required";
71
+ if (Object.keys(errs).length > 0) {
72
+ Object.entries(errs).forEach(([k, v]) => setFieldError(k, v));
73
+ return;
74
+ }
53
75
  await onSubmit(formData);
54
76
  };
55
- return (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsx(FormField, { label: mergedLabels.label, name: "label", type: "text", value: formData.label, onChange: (value) => handleChange("label", value), placeholder: mergedPlaceholders.label, required: true }), _jsx(FormField, { label: mergedLabels.fullName, name: "fullName", type: "text", value: formData.fullName, onChange: (value) => handleChange("fullName", value), placeholder: mergedPlaceholders.fullName, required: true }), _jsx(FormField, { label: mergedLabels.phone, name: "phone", type: "tel", value: formData.phone, onChange: (value) => handleChange("phone", value), placeholder: mergedPlaceholders.phone, required: true }), _jsx(FormField, { label: mergedLabels.addressLine1, name: "addressLine1", type: "text", value: formData.addressLine1, onChange: (value) => handleChange("addressLine1", value), placeholder: mergedPlaceholders.addressLine1, required: true }), _jsx(FormField, { label: mergedLabels.addressLine2, name: "addressLine2", type: "text", value: formData.addressLine2, onChange: (value) => handleChange("addressLine2", value), placeholder: mergedPlaceholders.addressLine2 }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { label: mergedLabels.city, name: "city", type: "text", value: formData.city, onChange: (value) => handleChange("city", value), placeholder: mergedPlaceholders.city, required: true }), _jsx(FormField, { label: mergedLabels.state, name: "state", type: "text", value: formData.state, onChange: (value) => handleChange("state", value), placeholder: mergedPlaceholders.state, required: true }), _jsx(FormField, { label: mergedLabels.postalCode, name: "postalCode", type: "text", value: formData.postalCode, onChange: (value) => handleChange("postalCode", value), placeholder: mergedPlaceholders.postalCode, required: true })] }), _jsx(FormField, { label: mergedLabels.country, name: "country", type: "text", value: formData.country, onChange: (value) => handleChange("country", value), placeholder: mergedPlaceholders.country, required: true }), _jsx(Checkbox, { checked: formData.isDefault, onChange: (e) => handleChange("isDefault", e.target.checked), label: mergedLabels.setDefault }), _jsxs("div", { className: `flex items-center justify-start ${THEME_CONSTANTS.spacing.gap.xs} pt-2`, "data-section": "addressform-div-223", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isLoading, children: mergedLabels.cancel }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading, children: isLoading ? mergedLabels.loading : effectiveSubmitLabel })] })] }));
77
+ return (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsx(FormField, { label: mergedLabels.label, name: "label", type: "text", value: formData.label, onChange: (value) => handleChange("label", value), placeholder: mergedPlaceholders.label, required: true }), _jsx(FormField, { label: mergedLabels.fullName, name: "fullName", type: "text", value: formData.fullName, onChange: (value) => handleChange("fullName", value), placeholder: mergedPlaceholders.fullName, required: true, error: shellCtx.errors["fullName"] }), _jsx(FormField, { label: mergedLabels.phone, name: "phone", type: "tel", value: formData.phone, onChange: (value) => handleChange("phone", value), placeholder: mergedPlaceholders.phone, required: true, error: shellCtx.errors["phone"] }), _jsx(FormField, { label: mergedLabels.addressLine1, name: "addressLine1", type: "text", value: formData.addressLine1, onChange: (value) => handleChange("addressLine1", value), placeholder: mergedPlaceholders.addressLine1, required: true, error: shellCtx.errors["addressLine1"] }), _jsx(FormField, { label: mergedLabels.addressLine2, name: "addressLine2", type: "text", value: formData.addressLine2, onChange: (value) => handleChange("addressLine2", value), placeholder: mergedPlaceholders.addressLine2 }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { label: mergedLabels.city, name: "city", type: "text", value: formData.city, onChange: (value) => handleChange("city", value), placeholder: mergedPlaceholders.city, required: true, error: shellCtx.errors["city"] }), _jsx(FormField, { label: mergedLabels.state, name: "state", type: "text", value: formData.state, onChange: (value) => handleChange("state", value), placeholder: mergedPlaceholders.state, required: true, error: shellCtx.errors["state"] }), _jsx(FormField, { label: mergedLabels.postalCode, name: "postalCode", type: "text", value: formData.postalCode, onChange: (value) => handleChange("postalCode", value), placeholder: mergedPlaceholders.postalCode, required: true, error: shellCtx.errors["postalCode"] })] }), _jsx(FormField, { label: mergedLabels.country, name: "country", type: "text", value: formData.country, onChange: (value) => handleChange("country", value), placeholder: mergedPlaceholders.country, required: true }), _jsx(Checkbox, { checked: formData.isDefault, onChange: (e) => handleChange("isDefault", e.target.checked), label: mergedLabels.setDefault }), _jsxs("div", { className: `flex items-center justify-start ${THEME_CONSTANTS.spacing.gap.xs} pt-2`, "data-section": "addressform-div-223", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isLoading, children: mergedLabels.cancel }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading, children: isLoading ? mergedLabels.loading : effectiveSubmitLabel })] })] }));
56
78
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { useMutation, useQuery } from "@tanstack/react-query";
5
5
  import { Button, Form, Input, RichTextEditor, Select, StackedViewShell, TagInput, Text, Toggle, useToast } from "../../../ui";
6
+ import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
6
7
  import { ImageUpload } from "../../media/upload/ImageUpload";
7
8
  import { useMediaUpload } from "../../media";
8
9
  import { apiClient } from "../../../http";
@@ -57,6 +58,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
57
58
  const [metaDescription, setMetaDescription] = React.useState("");
58
59
  const { showToast } = useToast();
59
60
  const { upload } = useMediaUpload();
61
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState();
60
62
  // --- load existing post (edit mode) ---
61
63
  const postQuery = useQuery({
62
64
  queryKey: ["admin", "blog", postId],
@@ -146,17 +148,22 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
146
148
  });
147
149
  const isSubmitting = saveMutation.isPending || postQuery.isLoading;
148
150
  const canSave = Boolean(title);
149
- const formSection = (_jsxs(Form, { onSubmit: (e) => {
150
- e.preventDefault();
151
- saveMutation.mutate();
152
- }, className: "space-y-5", children: [_jsx(Input, { label: "Title", value: title, onChange: (e) => handleTitleChange(e.target.value), required: true, placeholder: "e.g. How to Grade Pok\u00E9mon Cards" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
153
- setSlug(e.target.value);
154
- setSlugManual(true);
155
- }, placeholder: "blog-how-to-grade-pokemon-cards", helperText: "Auto-generated from title. Must start with 'blog-'." }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: category, onValueChange: (v) => setCategory(v) }), _jsx(Select, { label: "Status", options: STATUS_OPTIONS, value: status, onValueChange: (v) => setStatus(v) })] }), _jsx(Input, { label: "Excerpt", value: excerpt, onChange: (e) => setExcerpt(e.target.value), placeholder: "Short summary shown in listings and cards" }), _jsx(ImageUpload, { label: "Cover image", currentImage: coverImage, onUpload: (file) => upload(file, "blog", true, { type: "blog-cover", title: title || slug, category }), onChange: setCoverImage }), _jsxs("div", { className: "space-y-1", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Content" }), _jsx(RichTextEditor, { value: content, onChange: setContent, placeholder: "Write your article here...", minHeightClassName: "min-h-[320px]" })] }), _jsx(TagInput, { label: "Tags", value: tags, onChange: setTags, placeholder: "e.g. pokemon, grading, tcg" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Author name", value: authorName, onChange: (e) => setAuthorName(e.target.value), placeholder: "Author display name" }), _jsx(Input, { label: "Publish date (optional)", value: publishedAt, onChange: (e) => setPublishedAt(e.target.value), type: "date", helperText: "Auto-set to now when publishing." })] }), _jsx(Toggle, { label: "Featured post", checked: isFeatured, onChange: setIsFeatured }), _jsx(Input, { label: "Meta title (optional)", value: metaTitle, onChange: (e) => setMetaTitle(e.target.value), placeholder: "Defaults to post title" }), _jsx(Input, { label: "Meta description (optional)", value: metaDescription, onChange: (e) => setMetaDescription(e.target.value), placeholder: "SEO description \u2014 max 160 chars", maxLength: 160 }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? "Save changes" : "Create post" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
156
- if (confirm("Delete this post? This cannot be undone.")) {
157
- deleteMutation.mutate();
158
- }
159
- }, children: "Delete post" }))] })] }, "blog-form"));
151
+ const formSection = (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { onSubmit: (e) => {
152
+ e.preventDefault();
153
+ clearErrors();
154
+ if (!title.trim()) {
155
+ setFieldError("title", "Title is required");
156
+ return;
157
+ }
158
+ saveMutation.mutate();
159
+ }, className: "space-y-5", children: [_jsx(FieldInput, { name: "title", label: "Title", value: title, onChange: (v) => handleTitleChange(v), required: true, placeholder: "e.g. How to Grade Pok\u00E9mon Cards" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
160
+ setSlug(e.target.value);
161
+ setSlugManual(true);
162
+ }, placeholder: "blog-how-to-grade-pokemon-cards", helperText: "Auto-generated from title. Must start with 'blog-'." }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: category, onValueChange: (v) => setCategory(v) }), _jsx(Select, { label: "Status", options: STATUS_OPTIONS, value: status, onValueChange: (v) => setStatus(v) })] }), _jsx(Input, { label: "Excerpt", value: excerpt, onChange: (e) => setExcerpt(e.target.value), placeholder: "Short summary shown in listings and cards" }), _jsx(ImageUpload, { label: "Cover image", currentImage: coverImage, onUpload: (file) => upload(file, "blog", true, { type: "blog-cover", title: title || slug, category }), onChange: setCoverImage }), _jsxs("div", { className: "space-y-1", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Content" }), _jsx(RichTextEditor, { value: content, onChange: setContent, placeholder: "Write your article here...", minHeightClassName: "min-h-[320px]" })] }), _jsx(TagInput, { label: "Tags", value: tags, onChange: setTags, placeholder: "e.g. pokemon, grading, tcg" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Author name", value: authorName, onChange: (e) => setAuthorName(e.target.value), placeholder: "Author display name" }), _jsx(Input, { label: "Publish date (optional)", value: publishedAt, onChange: (e) => setPublishedAt(e.target.value), type: "date", helperText: "Auto-set to now when publishing." })] }), _jsx(Toggle, { label: "Featured post", checked: isFeatured, onChange: setIsFeatured }), _jsx(Input, { label: "Meta title (optional)", value: metaTitle, onChange: (e) => setMetaTitle(e.target.value), placeholder: "Defaults to post title" }), _jsx(Input, { label: "Meta description (optional)", value: metaDescription, onChange: (e) => setMetaDescription(e.target.value), placeholder: "SEO description \u2014 max 160 chars", maxLength: 160 }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? "Save changes" : "Create post" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
163
+ if (confirm("Delete this post? This cannot be undone.")) {
164
+ deleteMutation.mutate();
165
+ }
166
+ }, children: "Delete post" }))] })] }, "blog-form") }));
160
167
  if (embedded) {
161
168
  return _jsx("div", { className: "overflow-y-auto p-4", children: formSection });
162
169
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { useMutation, useQuery } from "@tanstack/react-query";
5
5
  import { Button, Form, Input, StackedViewShell, Toggle, useToast, } from "../../../ui";
6
+ import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
6
7
  import { ImageUpload } from "../../media/upload/ImageUpload";
7
8
  import { useMediaUpload } from "../../media";
8
9
  import { apiClient } from "../../../http";
@@ -23,6 +24,7 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
23
24
  const [isActive, setIsActive] = React.useState(true);
24
25
  const [displayOrder, setDisplayOrder] = React.useState("");
25
26
  const { showToast } = useToast();
27
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState();
26
28
  const brandQuery = useQuery({
27
29
  queryKey: ["admin", "brand", brandId],
28
30
  queryFn: async () => {
@@ -88,17 +90,22 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
88
90
  });
89
91
  const { upload } = useMediaUpload();
90
92
  const isSubmitting = saveMutation.isPending || brandQuery.isLoading;
91
- const formSection = (_jsxs(Form, { onSubmit: (e) => {
92
- e.preventDefault();
93
- saveMutation.mutate();
94
- }, className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Brand name", value: name, onChange: (e) => handleNameChange(e.target.value), required: true, placeholder: "e.g. Hot Wheels" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
95
- setSlug(e.target.value);
96
- setSlugManual(true);
97
- }, placeholder: "brand-hot-wheels", helperText: "Auto-generated from name. Must start with 'brand-'." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the brand" }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(ImageUpload, { label: "Logo", currentImage: logoURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-logo", brand: name || slug }), onChange: setLogoURL }), _jsx(ImageUpload, { label: "Banner", currentImage: bannerURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-banner", brand: name || slug }), onChange: setBannerURL })] }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Website", value: website, onChange: (e) => setWebsite(e.target.value), placeholder: "https://brand.com", type: "url" }), _jsx(Input, { label: "Display order", value: displayOrder, onChange: (e) => setDisplayOrder(e.target.value), type: "number", min: 0, placeholder: "0" })] }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create brand" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
98
- if (confirm("Delete this brand? This cannot be undone.")) {
99
- deleteMutation.mutate();
100
- }
101
- }, children: "Delete brand" }))] })] }, "brand-form"));
93
+ const formSection = (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { onSubmit: (e) => {
94
+ e.preventDefault();
95
+ clearErrors();
96
+ if (!name.trim()) {
97
+ setFieldError("name", "Brand name is required");
98
+ return;
99
+ }
100
+ saveMutation.mutate();
101
+ }, className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(FieldInput, { name: "name", label: "Brand name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Hot Wheels" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
102
+ setSlug(e.target.value);
103
+ setSlugManual(true);
104
+ }, placeholder: "brand-hot-wheels", helperText: "Auto-generated from name. Must start with 'brand-'." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the brand" }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(ImageUpload, { label: "Logo", currentImage: logoURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-logo", brand: name || slug }), onChange: setLogoURL }), _jsx(ImageUpload, { label: "Banner", currentImage: bannerURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-banner", brand: name || slug }), onChange: setBannerURL })] }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Website", value: website, onChange: (e) => setWebsite(e.target.value), placeholder: "https://brand.com", type: "url" }), _jsx(Input, { label: "Display order", value: displayOrder, onChange: (e) => setDisplayOrder(e.target.value), type: "number", min: 0, placeholder: "0" })] }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create brand" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
105
+ if (confirm("Delete this brand? This cannot be undone.")) {
106
+ deleteMutation.mutate();
107
+ }
108
+ }, children: "Delete brand" }))] })] }, "brand-form") }));
102
109
  if (embedded) {
103
110
  return _jsx("div", { className: "overflow-y-auto p-4", children: formSection });
104
111
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React from "react";
4
4
  import { useMutation, useQuery } from "@tanstack/react-query";
5
5
  import { Alert, Button, Form, FormActions, Input, Select, StackedViewShell, Toggle, Div, Text, Heading, Row, } from "../../../ui";
6
+ import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
6
7
  import { apiClient } from "../../../http";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  const CLS_PANEL = "rounded-xl border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-4 space-y-3";
@@ -103,6 +104,7 @@ export function AdminCarouselEditorView({ slideId, onSaved, onDeleted, onCancel,
103
104
  const [overlayBtnNewTab, setOverlayBtnNewTab] = React.useState(false);
104
105
  const [errorMsg, setErrorMsg] = React.useState("");
105
106
  const [successMsg, setSuccessMsg] = React.useState("");
107
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState();
106
108
  const slideQuery = useQuery({
107
109
  queryKey: ["admin", "carousel", slideId],
108
110
  queryFn: async () => {
@@ -195,13 +197,21 @@ export function AdminCarouselEditorView({ slideId, onSaved, onDeleted, onCancel,
195
197
  return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Carousel Slide" : "New Carousel Slide", sections: [
196
198
  errorMsg ? _jsx(Alert, { variant: "error", children: errorMsg }, "err") : null,
197
199
  successMsg ? _jsx(Alert, { variant: "success", children: successMsg }, "ok") : null,
198
- _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveMutation.mutate(); }, className: "space-y-6", children: [_jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Slide info" }), _jsx(Input, { label: "Title", value: title, onChange: (e) => setTitle(e.target.value), required: true, placeholder: "e.g. Hot Wheels RLC Exclusives" }), _jsx(Toggle, { label: "Active (visible on homepage)", checked: active, onChange: setActive }), _jsx(Input, { label: "Display order", type: "number", value: order, onChange: (e) => setOrder(e.target.value), min: 0, placeholder: "1" }), _jsx(Select, { label: "Slide height", value: height, onChange: (e) => setHeight(e.target.value), options: HEIGHT_OPTIONS }), _jsx(Input, { label: "Autoplay delay (ms)", type: "number", value: autoplayDelayMs, onChange: (e) => setAutoplayDelayMs(e.target.value), min: 1000, max: 15000, placeholder: "4000", helperText: "How long this slide shows before auto-advancing. Default: 4000 ms." })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Background" }), _jsx(BackgroundEditor, { value: background, onChange: setBackground, prefix: "slide" })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Overlay text (optional)" }), _jsx(Text, { className: "text-sm text-zinc-500", children: "Centred text layered over the background. Leave blank to use cards only." }), _jsx(Input, { label: "Heading", value: overlayTitle, onChange: (e) => setOverlayTitle(e.target.value), placeholder: "India's #1 Collectibles Marketplace" }), _jsx(Input, { label: "Subtitle", value: overlaySubtitle, onChange: (e) => setOverlaySubtitle(e.target.value), placeholder: "Pok\u00E9mon TCG \u00B7 Hot Wheels \u00B7 Beyblade X" }), _jsx(Input, { label: "Description", value: overlayDesc, onChange: (e) => setOverlayDesc(e.target.value), placeholder: "One sentence description..." }), _jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-800 p-3 space-y-2", children: [_jsx(Text, { className: "text-sm font-medium", children: "CTA button" }), _jsx(Input, { label: "Button text", value: overlayBtnText, onChange: (e) => setOverlayBtnText(e.target.value), placeholder: "Shop Now" }), _jsx(Input, { label: "Button link", value: overlayBtnLink, onChange: (e) => setOverlayBtnLink(e.target.value), placeholder: "/products" }), _jsx(Select, { label: "Variant", value: overlayBtnVariant, onChange: (e) => setOverlayBtnVariant(e.target.value), options: VARIANT_OPTIONS.filter((v) => ["primary", "secondary", "outline"].includes(v.value)) }), _jsx(Toggle, { label: "Open in new tab", checked: overlayBtnNewTab, onChange: setOverlayBtnNewTab })] })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsxs(Row, { className: CLS_ROW_BETWEEN, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Cards (0\u20132)" }), cards.length < 2 && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
199
- const usedZones = cards.map((c) => c.zone);
200
- const freeZone = [1, 2, 3, 4, 5, 6].find((z) => !usedZones.includes(z)) ?? 1;
201
- setCards([...cards, makeCard(freeZone)]);
202
- }, children: "+ Add card" }))] }), cards.length === 0 && (_jsx(Text, { className: "text-sm text-zinc-400", children: "No cards \u2014 the overlay text covers the whole slide." })), cards.map((card, i) => (_jsx(CardEditor, { card: card, index: i, otherZones: occupiedZones.filter((z) => z !== card.zone), onChange: (updated) => setCards(cards.map((c, ci) => (ci === i ? updated : c))), onRemove: () => setCards(cards.filter((_, ci) => ci !== i)) }, card.id)))] }), _jsxs(FormActions, { children: [_jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !title || saveMutation.isPending, children: isEdit ? "Save changes" : "Create slide" }), onCancel && (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, children: "Cancel" })), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
203
- if (confirm("Delete this slide? This cannot be undone."))
204
- deleteMutation.mutate();
205
- }, children: "Delete slide" }))] })] }, "slide-form"),
200
+ _jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { onSubmit: (e) => {
201
+ e.preventDefault();
202
+ clearErrors();
203
+ if (!title.trim()) {
204
+ setFieldError("title", "Title is required");
205
+ return;
206
+ }
207
+ saveMutation.mutate();
208
+ }, className: "space-y-6", children: [_jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Slide info" }), _jsx(FieldInput, { name: "title", label: "Title", value: title, onChange: (v) => setTitle(v), required: true, placeholder: "e.g. Hot Wheels RLC Exclusives" }), _jsx(Toggle, { label: "Active (visible on homepage)", checked: active, onChange: setActive }), _jsx(Input, { label: "Display order", type: "number", value: order, onChange: (e) => setOrder(e.target.value), min: 0, placeholder: "1" }), _jsx(Select, { label: "Slide height", value: height, onChange: (e) => setHeight(e.target.value), options: HEIGHT_OPTIONS }), _jsx(Input, { label: "Autoplay delay (ms)", type: "number", value: autoplayDelayMs, onChange: (e) => setAutoplayDelayMs(e.target.value), min: 1000, max: 15000, placeholder: "4000", helperText: "How long this slide shows before auto-advancing. Default: 4000 ms." })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Background" }), _jsx(BackgroundEditor, { value: background, onChange: setBackground, prefix: "slide" })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Overlay text (optional)" }), _jsx(Text, { className: "text-sm text-zinc-500", children: "Centred text layered over the background. Leave blank to use cards only." }), _jsx(Input, { label: "Heading", value: overlayTitle, onChange: (e) => setOverlayTitle(e.target.value), placeholder: "India's #1 Collectibles Marketplace" }), _jsx(Input, { label: "Subtitle", value: overlaySubtitle, onChange: (e) => setOverlaySubtitle(e.target.value), placeholder: "Pok\u00E9mon TCG \u00B7 Hot Wheels \u00B7 Beyblade X" }), _jsx(Input, { label: "Description", value: overlayDesc, onChange: (e) => setOverlayDesc(e.target.value), placeholder: "One sentence description..." }), _jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-800 p-3 space-y-2", children: [_jsx(Text, { className: "text-sm font-medium", children: "CTA button" }), _jsx(Input, { label: "Button text", value: overlayBtnText, onChange: (e) => setOverlayBtnText(e.target.value), placeholder: "Shop Now" }), _jsx(Input, { label: "Button link", value: overlayBtnLink, onChange: (e) => setOverlayBtnLink(e.target.value), placeholder: "/products" }), _jsx(Select, { label: "Variant", value: overlayBtnVariant, onChange: (e) => setOverlayBtnVariant(e.target.value), options: VARIANT_OPTIONS.filter((v) => ["primary", "secondary", "outline"].includes(v.value)) }), _jsx(Toggle, { label: "Open in new tab", checked: overlayBtnNewTab, onChange: setOverlayBtnNewTab })] })] }), _jsxs(Div, { className: CLS_PANEL, children: [_jsxs(Row, { className: CLS_ROW_BETWEEN, children: [_jsx(Heading, { level: 3, className: CLS_SECTION_HEADING, children: "Cards (0\u20132)" }), cards.length < 2 && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
209
+ const usedZones = cards.map((c) => c.zone);
210
+ const freeZone = [1, 2, 3, 4, 5, 6].find((z) => !usedZones.includes(z)) ?? 1;
211
+ setCards([...cards, makeCard(freeZone)]);
212
+ }, children: "+ Add card" }))] }), cards.length === 0 && (_jsx(Text, { className: "text-sm text-zinc-400", children: "No cards \u2014 the overlay text covers the whole slide." })), cards.map((card, i) => (_jsx(CardEditor, { card: card, index: i, otherZones: occupiedZones.filter((z) => z !== card.zone), onChange: (updated) => setCards(cards.map((c, ci) => (ci === i ? updated : c))), onRemove: () => setCards(cards.filter((_, ci) => ci !== i)) }, card.id)))] }), _jsxs(FormActions, { children: [_jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !title || saveMutation.isPending, children: isEdit ? "Save changes" : "Create slide" }), onCancel && (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, children: "Cancel" })), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
213
+ if (confirm("Delete this slide? This cannot be undone."))
214
+ deleteMutation.mutate();
215
+ }, children: "Delete slide" }))] })] }, "slide-form") }),
206
216
  ] }));
207
217
  }
@@ -6,6 +6,7 @@ import { Button, Card, CardBody, ConfirmDeleteModal, Form, InlineCreateSelect, I
6
6
  import { apiClient } from "../../../http";
7
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
8
  import { CategoryQuickCreateForm } from "./CategoryQuickCreateForm";
9
+ import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
9
10
  async function loadCategoryOptions(query, page) {
10
11
  const params = new URLSearchParams({ page: String(page), pageSize: "25" });
11
12
  if (query)
@@ -38,6 +39,7 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
38
39
  const [isActive, setIsActive] = React.useState(true);
39
40
  const [showInMenu, setShowInMenu] = React.useState(true);
40
41
  const { showToast } = useToast();
42
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState();
41
43
  const categoryQuery = useQuery({
42
44
  queryKey: ["admin", "category", categoryId],
43
45
  queryFn: async () => {
@@ -101,17 +103,22 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
101
103
  });
102
104
  const isSubmitting = saveMutation.isPending || categoryQuery.isLoading;
103
105
  const actionSidebar = (_jsxs(Card, { variant: "outlined", padding: "md", className: "space-y-3", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: isEdit ? (isActive ? "Active" : "Inactive") : "New" }), _jsx(Button, { type: "submit", form: "category-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete category" }))] }));
104
- const formContent = (_jsxs(Form, { id: "category-editor-form", onSubmit: (e) => {
105
- e.preventDefault();
106
- saveMutation.mutate();
107
- }, className: "space-y-6", children: [_jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400 mb-4", children: "Identity" }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Category name", value: name, onChange: (e) => handleNameChange(e.target.value), required: true, placeholder: "e.g. Toys & Games" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
108
- setSlug(e.target.value);
109
- setSlugManual(true);
110
- }, placeholder: "toys-and-games", helperText: "Auto-generated from name. Used in URLs." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the category" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Parent category" }), _jsx(InlineCreateSelect, { value: parentId || null, onChange: (v) => setParentId(v ?? ""), loadOptions: loadCategoryOptions, placeholder: "Search categories\u2026 (leave empty for root)", searchPlaceholder: "Type category name\u2026", noResultsText: "No categories found", ariaLabel: "Parent category", createLabel: "Category", renderCreateForm: ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, n) => { setParentId(id); onCreated({ value: id, label: n }); }, onCancel: onCancel })) }), _jsx(Text, { className: "text-xs text-neutral-500 dark:text-neutral-400", children: "Leave empty to create a root category." })] })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400 mb-4", children: "Display" }), _jsxs("div", { className: "space-y-4", children: [_jsx(Input, { label: "Display order", value: order, onChange: (e) => setOrder(e.target.value), type: "number", min: 0, placeholder: "0" }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Show in menu", checked: showInMenu, onChange: setShowInMenu })] })] }), _jsxs("div", { className: "flex gap-3 lg:hidden", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
111
- if (confirm("Delete this category? Products in this category will become uncategorized.")) {
112
- deleteMutation.mutate();
113
- }
114
- }, children: "Delete category" }))] })] }, "cat-form"));
106
+ const formContent = (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { id: "category-editor-form", onSubmit: (e) => {
107
+ e.preventDefault();
108
+ clearErrors();
109
+ if (!name.trim()) {
110
+ setFieldError("name", "Category name is required");
111
+ return;
112
+ }
113
+ saveMutation.mutate();
114
+ }, className: "space-y-6", children: [_jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400 mb-4", children: "Identity" }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(FieldInput, { name: "name", label: "Category name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Toys & Games" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
115
+ setSlug(e.target.value);
116
+ setSlugManual(true);
117
+ }, placeholder: "toys-and-games", helperText: "Auto-generated from name. Used in URLs." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the category" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Parent category" }), _jsx(InlineCreateSelect, { value: parentId || null, onChange: (v) => setParentId(v ?? ""), loadOptions: loadCategoryOptions, placeholder: "Search categories\u2026 (leave empty for root)", searchPlaceholder: "Type category name\u2026", noResultsText: "No categories found", ariaLabel: "Parent category", createLabel: "Category", renderCreateForm: ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, n) => { setParentId(id); onCreated({ value: id, label: n }); }, onCancel: onCancel })) }), _jsx(Text, { className: "text-xs text-neutral-500 dark:text-neutral-400", children: "Leave empty to create a root category." })] })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400 mb-4", children: "Display" }), _jsxs("div", { className: "space-y-4", children: [_jsx(Input, { label: "Display order", value: order, onChange: (e) => setOrder(e.target.value), type: "number", min: 0, placeholder: "0" }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Show in menu", checked: showInMenu, onChange: setShowInMenu })] })] }), _jsxs("div", { className: "flex gap-3 lg:hidden", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
118
+ if (confirm("Delete this category? Products in this category will become uncategorized.")) {
119
+ deleteMutation.mutate();
120
+ }
121
+ }, children: "Delete category" }))] })] }, "cat-form") }));
115
122
  if (embedded) {
116
123
  return _jsx("div", { className: "overflow-y-auto p-4", children: formContent });
117
124
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React from "react";
4
4
  import { useMutation, useQuery } from "@tanstack/react-query";
5
5
  import { Button, Form, Input, Select, StackedViewShell, Toggle, useToast, } from "../../../ui";
6
+ import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
6
7
  import { apiClient } from "../../../http";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  const TYPE_OPTIONS = [
@@ -60,6 +61,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
60
61
  const [combinable, setCombinable] = React.useState(false);
61
62
  const [appliesToAuctions, setAppliesToAuctions] = React.useState(false);
62
63
  const { showToast } = useToast();
64
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState();
63
65
  // --- load existing data (edit mode) ---
64
66
  const couponQuery = useQuery({
65
67
  queryKey: ["admin", "coupon", couponId],
@@ -169,17 +171,22 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
169
171
  : type === "fixed"
170
172
  ? "Discount amount (paise)"
171
173
  : "Discount value";
172
- const formSection = (_jsxs(Form, { onSubmit: (e) => {
173
- e.preventDefault();
174
- saveMutation.mutate();
175
- }, className: "space-y-5", children: [_jsx(Select, { label: "Coupon type", options: TYPE_OPTIONS, value: type, onValueChange: (v) => setType(v), required: true }), _jsx(Input, { label: "Campaign name", value: name, onChange: (e) => handleNameChange(e.target.value), required: true, placeholder: "e.g. Summer Sale 20%" }), !isEdit && (_jsx(Input, { label: "Coupon code", value: code, onChange: (e) => {
176
- setCode(toCouponCode(e.target.value));
177
- setCodeManual(true);
178
- }, required: true, placeholder: "e.g. SUMMER20", helperText: "Auto-generated from name. Uppercase alphanumeric + hyphens only." })), isEdit && (_jsx(Input, { label: "Coupon code", value: code, disabled: true, helperText: "Code cannot be changed after creation." })), _jsx(Input, { label: "Description (optional)", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Internal notes about this coupon" }), _jsx(CouponDiscountFields, { type: type, discountValue: discountValue, setDiscountValue: setDiscountValue, maxDiscount: maxDiscount, setMaxDiscount: setMaxDiscount, minPurchase: minPurchase, setMinPurchase: setMinPurchase, buyQty: buyQty, setBuyQty: setBuyQty, getQty: getQty, setGetQty: setGetQty, discountLabel: discountLabel }), _jsx(CouponValidityFields, { startDate: startDate, setStartDate: setStartDate, endDate: endDate, setEndDate: setEndDate, isActive: isActive, setIsActive: setIsActive, totalLimit: totalLimit, setTotalLimit: setTotalLimit, perUserLimit: perUserLimit, setPerUserLimit: setPerUserLimit, isEdit: isEdit, currentUsage: currentUsage }), _jsx(Toggle, { label: "First-time users only", checked: firstTimeOnly, onChange: setFirstTimeOnly }), _jsx(Toggle, { label: "Allow stacking with seller coupons", checked: combinable, onChange: setCombinable }), _jsx(Toggle, { label: "Applies to auctions", checked: appliesToAuctions, onChange: setAppliesToAuctions }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? "Save changes" : "Create coupon" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
179
- if (confirm("Delete this coupon? This cannot be undone.")) {
180
- deleteMutation.mutate();
181
- }
182
- }, children: "Delete coupon" }))] })] }, "coupon-form"));
174
+ const formSection = (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { onSubmit: (e) => {
175
+ e.preventDefault();
176
+ clearErrors();
177
+ if (!name.trim()) {
178
+ setFieldError("name", "Campaign name is required");
179
+ return;
180
+ }
181
+ saveMutation.mutate();
182
+ }, className: "space-y-5", children: [_jsx(Select, { label: "Coupon type", options: TYPE_OPTIONS, value: type, onValueChange: (v) => setType(v), required: true }), _jsx(FieldInput, { name: "name", label: "Campaign name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Summer Sale 20%" }), !isEdit && (_jsx(Input, { label: "Coupon code", value: code, onChange: (e) => {
183
+ setCode(toCouponCode(e.target.value));
184
+ setCodeManual(true);
185
+ }, required: true, placeholder: "e.g. SUMMER20", helperText: "Auto-generated from name. Uppercase alphanumeric + hyphens only." })), isEdit && (_jsx(Input, { label: "Coupon code", value: code, disabled: true, helperText: "Code cannot be changed after creation." })), _jsx(Input, { label: "Description (optional)", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Internal notes about this coupon" }), _jsx(CouponDiscountFields, { type: type, discountValue: discountValue, setDiscountValue: setDiscountValue, maxDiscount: maxDiscount, setMaxDiscount: setMaxDiscount, minPurchase: minPurchase, setMinPurchase: setMinPurchase, buyQty: buyQty, setBuyQty: setBuyQty, getQty: getQty, setGetQty: setGetQty, discountLabel: discountLabel }), _jsx(CouponValidityFields, { startDate: startDate, setStartDate: setStartDate, endDate: endDate, setEndDate: setEndDate, isActive: isActive, setIsActive: setIsActive, totalLimit: totalLimit, setTotalLimit: setTotalLimit, perUserLimit: perUserLimit, setPerUserLimit: setPerUserLimit, isEdit: isEdit, currentUsage: currentUsage }), _jsx(Toggle, { label: "First-time users only", checked: firstTimeOnly, onChange: setFirstTimeOnly }), _jsx(Toggle, { label: "Allow stacking with seller coupons", checked: combinable, onChange: setCombinable }), _jsx(Toggle, { label: "Applies to auctions", checked: appliesToAuctions, onChange: setAppliesToAuctions }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? "Save changes" : "Create coupon" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
186
+ if (confirm("Delete this coupon? This cannot be undone.")) {
187
+ deleteMutation.mutate();
188
+ }
189
+ }, children: "Delete coupon" }))] })] }, "coupon-form") }));
183
190
  if (embedded) {
184
191
  return _jsx("div", { className: "overflow-y-auto p-4", children: formSection });
185
192
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { useMutation, useQuery } from "@tanstack/react-query";
5
5
  import { Button, Form, Input, RichTextEditor, Select, StackedViewShell, TagInput, Text, Toggle, useToast } from "../../../ui";
6
+ import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
6
7
  import { apiClient } from "../../../http";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  // --- Constants ---------------------------------------------------------------
@@ -38,6 +39,7 @@ export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...res
38
39
  const [showOnHomepage, setShowOnHomepage] = React.useState(false);
39
40
  const [showInFooter, setShowInFooter] = React.useState(false);
40
41
  const { showToast } = useToast();
42
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState();
41
43
  // --- load existing FAQ (edit mode) ---
42
44
  const faqQuery = useQuery({
43
45
  queryKey: ["admin", "faqs", faqId],
@@ -112,17 +114,22 @@ export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...res
112
114
  });
113
115
  const isSubmitting = saveMutation.isPending || faqQuery.isLoading;
114
116
  const canSave = Boolean(question.trim()) && Boolean(answer.trim());
115
- const formSection = (_jsxs(Form, { onSubmit: (e) => {
116
- e.preventDefault();
117
- saveMutation.mutate();
118
- }, className: "space-y-5", children: [_jsx(Input, { label: "Question", value: question, onChange: (e) => handleQuestionChange(e.target.value), required: true, placeholder: "e.g. How does bidding work on LetItRip?" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
119
- setSlug(e.target.value);
120
- setSlugManual(true);
121
- }, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from question. Must start with 'faq-'." }), _jsxs("div", { className: "space-y-1", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Answer" }), _jsx(RichTextEditor, { value: answer, onChange: setAnswer, placeholder: "Write a clear, helpful answer...", minHeightClassName: "min-h-[200px]" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: category, onValueChange: setCategory }), _jsx(Input, { label: "Display order", value: String(order), onChange: (e) => setOrder(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Lower = shown first within category." })] }), _jsx(Input, { label: "Priority", value: String(priority), onChange: (e) => setPriority(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Higher priority FAQs appear first in search results." }), _jsx(TagInput, { label: "Tags", value: tags, onChange: setTags, placeholder: "e.g. shipping, pokemon, returns" }), _jsxs("div", { className: "space-y-3 rounded-lg border border-zinc-200 dark:border-zinc-700 p-4", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Visibility" }), _jsx(Toggle, { label: "Active (visible to users)", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Pinned (always shown at top)", checked: isPinned, onChange: setIsPinned }), _jsx(Toggle, { label: "Show on homepage FAQ section", checked: showOnHomepage, onChange: setShowOnHomepage }), _jsx(Toggle, { label: "Show in footer FAQ links", checked: showInFooter, onChange: setShowInFooter })] }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? "Save changes" : "Create FAQ" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
122
- if (confirm("Delete this FAQ? This cannot be undone.")) {
123
- deleteMutation.mutate();
124
- }
125
- }, children: "Delete FAQ" }))] })] }, "faq-form"));
117
+ const formSection = (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { onSubmit: (e) => {
118
+ e.preventDefault();
119
+ clearErrors();
120
+ if (!question.trim()) {
121
+ setFieldError("question", "Question is required");
122
+ return;
123
+ }
124
+ saveMutation.mutate();
125
+ }, className: "space-y-5", children: [_jsx(FieldInput, { name: "question", label: "Question", value: question, onChange: (v) => handleQuestionChange(v), required: true, placeholder: "e.g. How does bidding work on LetItRip?" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
126
+ setSlug(e.target.value);
127
+ setSlugManual(true);
128
+ }, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from question. Must start with 'faq-'." }), _jsxs("div", { className: "space-y-1", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Answer" }), _jsx(RichTextEditor, { value: answer, onChange: setAnswer, placeholder: "Write a clear, helpful answer...", minHeightClassName: "min-h-[200px]" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: category, onValueChange: setCategory }), _jsx(Input, { label: "Display order", value: String(order), onChange: (e) => setOrder(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Lower = shown first within category." })] }), _jsx(Input, { label: "Priority", value: String(priority), onChange: (e) => setPriority(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Higher priority FAQs appear first in search results." }), _jsx(TagInput, { label: "Tags", value: tags, onChange: setTags, placeholder: "e.g. shipping, pokemon, returns" }), _jsxs("div", { className: "space-y-3 rounded-lg border border-zinc-200 dark:border-zinc-700 p-4", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Visibility" }), _jsx(Toggle, { label: "Active (visible to users)", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Pinned (always shown at top)", checked: isPinned, onChange: setIsPinned }), _jsx(Toggle, { label: "Show on homepage FAQ section", checked: showOnHomepage, onChange: setShowOnHomepage }), _jsx(Toggle, { label: "Show in footer FAQ links", checked: showInFooter, onChange: setShowInFooter })] }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? "Save changes" : "Create FAQ" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
129
+ if (confirm("Delete this FAQ? This cannot be undone.")) {
130
+ deleteMutation.mutate();
131
+ }
132
+ }, children: "Delete FAQ" }))] })] }, "faq-form") }));
126
133
  if (embedded) {
127
134
  return _jsx("div", { className: "overflow-y-auto p-4", children: formSection });
128
135
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { useMutation, useQuery } from "@tanstack/react-query";
5
5
  import { Button, Form, Input, StackedViewShell, useToast, } from "../../../ui";
6
+ import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
6
7
  import { ImageUpload } from "../../media/upload/ImageUpload";
7
8
  import { useMediaUpload } from "../../media";
8
9
  import { apiClient } from "../../../http";
@@ -67,17 +68,23 @@ export function AdminSublistingCategoryEditorView({ categoryId, onSaved, onDelet
67
68
  });
68
69
  const { upload } = useMediaUpload();
69
70
  const isSubmitting = saveMutation.isPending || categoryQuery.isLoading;
71
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState();
70
72
  return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Sub-listing Category" : "New Sub-listing Category", sections: [
71
- _jsxs(Form, { onSubmit: (e) => {
72
- e.preventDefault();
73
- saveMutation.mutate();
74
- }, className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Category name", value: name, onChange: (e) => setName(e.target.value), required: true, placeholder: "e.g. Base Set Charizard 108/120" }), _jsx(Input, { label: "Item code", value: itemCode, onChange: (e) => setItemCode(e.target.value), placeholder: "e.g. PSA 10, 108/120, STH", helperText: "Grade, card number, or series code. Optional." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description shown on the public category page" }), _jsx(ImageUpload, { label: "Cover image", currentImage: coverImage, onUpload: (file) => upload(file, "sublisting-categories", true, {
75
- type: "category-image",
76
- name: name || "sublisting",
77
- }), onChange: setCoverImage }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
78
- if (confirm("Delete this category? All linked listings will be unlinked. This cannot be undone.")) {
79
- deleteMutation.mutate();
80
- }
81
- }, children: "Delete" }))] })] }, "sc-editor-form"),
73
+ _jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { onSubmit: (e) => {
74
+ e.preventDefault();
75
+ clearErrors();
76
+ if (!name.trim()) {
77
+ setFieldError("name", "Category name is required");
78
+ return;
79
+ }
80
+ saveMutation.mutate();
81
+ }, className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(FieldInput, { name: "name", label: "Category name", value: name, onChange: (v) => setName(v), required: true, placeholder: "e.g. Base Set Charizard 108/120" }), _jsx(Input, { label: "Item code", value: itemCode, onChange: (e) => setItemCode(e.target.value), placeholder: "e.g. PSA 10, 108/120, STH", helperText: "Grade, card number, or series code. Optional." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description shown on the public category page" }), _jsx(ImageUpload, { label: "Cover image", currentImage: coverImage, onUpload: (file) => upload(file, "sublisting-categories", true, {
82
+ type: "category-image",
83
+ name: name || "sublisting",
84
+ }), onChange: setCoverImage }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
85
+ if (confirm("Delete this category? All linked listings will be unlinked. This cannot be undone.")) {
86
+ deleteMutation.mutate();
87
+ }
88
+ }, children: "Delete" }))] })] }, "sc-editor-form") }, "sc-ctx"),
82
89
  ] }));
83
90
  }
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useRef, useState } from "react";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { useRouter } from "next/navigation";
5
5
  import { FormShell, StepForm, StepFormActions, useFormShell } from "../../shell";
6
+ import { FormShellProvider } from "../../../ui/forms";
6
7
  import { Alert, Button, Div, FormField, FormGroup, Heading, Section, Stack, Text, Toggle } from "../../../ui";
7
8
  import { ImageUpload, MediaUploadField, MediaUploadList, useMediaUpload } from "../../media";
8
9
  import { StoreAddressSelectorCreate } from "../../stores/components/StoreAddressSelectorCreate";
@@ -146,6 +147,25 @@ export function SellerProductShell({ mode, listingType = "standard", initialValu
146
147
  const [stepError, setStepError] = useState(null);
147
148
  const { isDirty, markDirty, markClean } = useFormShell();
148
149
  const router = useRouter();
150
+ // Auto-save in create mode — debounce 2s on any draft change
151
+ const autoSaveTimer = useRef(null);
152
+ const draftRef = useRef(draft);
153
+ draftRef.current = draft;
154
+ const onSaveRef = useRef(onSave);
155
+ onSaveRef.current = onSave;
156
+ useEffect(() => {
157
+ if (mode !== "create" || !isDirty)
158
+ return;
159
+ if (autoSaveTimer.current)
160
+ clearTimeout(autoSaveTimer.current);
161
+ autoSaveTimer.current = setTimeout(() => {
162
+ const result = onSaveRef.current(draftRef.current);
163
+ if (result && typeof result.catch === "function")
164
+ result.catch(() => { });
165
+ }, 2000);
166
+ return () => { if (autoSaveTimer.current)
167
+ clearTimeout(autoSaveTimer.current); };
168
+ }, [draft, isDirty, mode]);
149
169
  const update = useCallback((partial) => {
150
170
  setDraft((prev) => ({ ...prev, ...partial }));
151
171
  markDirty();
@@ -247,12 +267,16 @@ export function SellerProductShell({ mode, listingType = "standard", initialValu
247
267
  await handlePublish();
248
268
  }
249
269
  }, [currentStep, steps, draft, handlePublish]);
270
+ // Step error badges — run each step's validate against current draft
271
+ const stepValidationErrors = useMemo(() => steps.map((s) => (s.validate ? Boolean(s.validate(draft)) : false)),
272
+ // eslint-disable-next-line react-hooks/exhaustive-deps
273
+ [draft]);
250
274
  const breadcrumb = mode === "create" ? `Store / ${listingTypeLabel}s / New` : `Store / ${listingTypeLabel}s / Edit`;
251
275
  const title = mode === "create"
252
276
  ? `New ${listingTypeLabel}`
253
277
  : draft.title ?? `Edit ${listingTypeLabel}`;
254
278
  if (mode === "create") {
255
- return (_jsx(FormShell, { isOpen: true, onClose: handleDiscard, title: title, breadcrumb: breadcrumb, isDirty: isDirty, isLoading: isLoading, previewSlot: previewSlot, renderBottomBar: () => (_jsxs("div", { className: "flex-shrink-0 border-t border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", children: [_jsx(StepFormActions, { currentStep: currentStep, totalSteps: steps.length, onNext: () => void handleNext(), onPrev: currentStep > 0 ? () => setCurrentStep((c) => c - 1) : undefined, completeLabel: `Publish ${listingTypeLabel}`, isLoading: isLoading && currentStep === steps.length - 1, disabled: isLoading }), stepError && (_jsx(Text, { className: "px-5 pb-3 text-sm text-[var(--appkit-color-error)]", children: stepError }))] })), children: _jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: handlePublish, completeLabel: `Publish ${listingTypeLabel}`, currentStep: currentStep, onStepChange: setCurrentStep, isLoading: isLoading, hideActions: true }) }));
279
+ return (_jsx(FormShell, { isOpen: true, onClose: handleDiscard, title: title, breadcrumb: breadcrumb, isDirty: isDirty, isLoading: isLoading, previewSlot: previewSlot, renderBottomBar: () => (_jsxs("div", { className: "flex-shrink-0 border-t border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", children: [_jsx(StepFormActions, { currentStep: currentStep, totalSteps: steps.length, onNext: () => void handleNext(), onPrev: currentStep > 0 ? () => setCurrentStep((c) => c - 1) : undefined, completeLabel: `Publish ${listingTypeLabel}`, isLoading: isLoading && currentStep === steps.length - 1, disabled: isLoading }), stepError && (_jsx(Text, { className: "px-5 pb-3 text-sm text-[var(--appkit-color-error)]", children: stepError }))] })), children: _jsx(FormShellProvider, { isDirty: isDirty, values: draft, children: _jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: handlePublish, completeLabel: `Publish ${listingTypeLabel}`, currentStep: currentStep, onStepChange: setCurrentStep, isLoading: isLoading, hideActions: true, stepErrors: stepValidationErrors }) }) }));
256
280
  }
257
281
  // Edit mode — FormShell with section nav + full form
258
282
  const editSections = [
@@ -263,5 +287,5 @@ export function SellerProductShell({ mode, listingType = "standard", initialValu
263
287
  ...(listingType === "digital-code" ? [{ id: "digitalcode", label: "Code Details" }] : []),
264
288
  ...(listingType === "live" ? [{ id: "live", label: "Live Item" }] : []),
265
289
  ];
266
- return (_jsx(FormShell, { isOpen: true, onClose: handleDiscard, title: title, breadcrumb: breadcrumb, isDirty: isDirty, isLoading: isLoading, sections: editSections, onSaveDraft: handleSave, onPublish: handlePublish, saveLabel: "Save Changes", publishLabel: "Update", previewSlot: previewSlot, children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Section, { id: "basic", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Basic Info" }), _jsx(StepBasic, { values: draft, onChange: update, renderCategorySelector: renderCategorySelector, renderBrandSelector: renderBrandSelector, renderTemplateSelector: renderTemplateSelector })] }), _jsxs(Section, { id: "media", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Media" }), _jsx(StepMedia, { values: draft, onChange: update, storeSlug: storeSlug })] }), listingType === "auction" && (_jsxs(Section, { id: "auction", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Auction Settings" }), _jsx(StepAuctionSettings, { values: draft, onChange: update })] })), listingType === "pre-order" && (_jsxs(Section, { id: "preorder", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Pre-Order Settings" }), _jsx(StepPreOrderSettings, { values: draft, onChange: update })] })), listingType === "classified" && (_jsxs(Section, { id: "classified", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Meetup Details" }), _jsx(StepClassifiedSettings, { values: draft, onChange: update })] })), listingType === "digital-code" && (_jsxs(Section, { id: "digitalcode", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Code Details" }), _jsx(StepDigitalCodeSettings, { values: draft, onChange: update })] })), listingType === "live" && (_jsxs(Section, { id: "live", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Live Item Details" }), _jsx(StepLiveItemSettings, { values: draft, onChange: update })] })), _jsxs(Section, { id: "pricing", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Pricing" }), _jsx(StepPricing, { values: draft, onChange: update, listingType: listingType })] }), _jsxs(Section, { id: "shipping", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Shipping" }), _jsx(StepShipping, { values: draft, onChange: update, renderAddressSelector: renderAddressSelector })] }), _jsxs(Section, { id: "publish", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Publish" }), _jsx(StepPublish, { values: draft, onChange: update }), onSaveAsTemplate && (_jsxs(Div, { className: "mt-4 border-t border-[var(--appkit-color-border)] pt-4", children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onSaveAsTemplate(draft), children: "Save as Template" }), _jsx(Text, { className: "mt-1 text-xs text-[var(--appkit-color-secondary-text)]", children: "Save these settings as a reusable template for future listings." })] }))] })] }) }));
290
+ return (_jsx(FormShell, { isOpen: true, onClose: handleDiscard, title: title, breadcrumb: breadcrumb, isDirty: isDirty, isLoading: isLoading, sections: editSections, onSaveDraft: handleSave, onPublish: handlePublish, saveLabel: "Save Changes", publishLabel: "Update", previewSlot: previewSlot, children: _jsx(FormShellProvider, { isDirty: isDirty, values: draft, children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Section, { id: "basic", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Basic Info" }), _jsx(StepBasic, { values: draft, onChange: update, renderCategorySelector: renderCategorySelector, renderBrandSelector: renderBrandSelector, renderTemplateSelector: renderTemplateSelector })] }), _jsxs(Section, { id: "media", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Media" }), _jsx(StepMedia, { values: draft, onChange: update, storeSlug: storeSlug })] }), listingType === "auction" && (_jsxs(Section, { id: "auction", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Auction Settings" }), _jsx(StepAuctionSettings, { values: draft, onChange: update })] })), listingType === "pre-order" && (_jsxs(Section, { id: "preorder", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Pre-Order Settings" }), _jsx(StepPreOrderSettings, { values: draft, onChange: update })] })), listingType === "classified" && (_jsxs(Section, { id: "classified", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Meetup Details" }), _jsx(StepClassifiedSettings, { values: draft, onChange: update })] })), listingType === "digital-code" && (_jsxs(Section, { id: "digitalcode", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Code Details" }), _jsx(StepDigitalCodeSettings, { values: draft, onChange: update })] })), listingType === "live" && (_jsxs(Section, { id: "live", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Live Item Details" }), _jsx(StepLiveItemSettings, { values: draft, onChange: update })] })), _jsxs(Section, { id: "pricing", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Pricing" }), _jsx(StepPricing, { values: draft, onChange: update, listingType: listingType })] }), _jsxs(Section, { id: "shipping", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Shipping" }), _jsx(StepShipping, { values: draft, onChange: update, renderAddressSelector: renderAddressSelector })] }), _jsxs(Section, { id: "publish", children: [_jsx(Heading, { level: 3, className: "mb-4", children: "Publish" }), _jsx(StepPublish, { values: draft, onChange: update }), onSaveAsTemplate && (_jsxs(Div, { className: "mt-4 border-t border-[var(--appkit-color-border)] pt-4", children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onSaveAsTemplate(draft), children: "Save as Template" }), _jsx(Text, { className: "mt-1 text-xs text-[var(--appkit-color-secondary-text)]", children: "Save these settings as a reusable template for future listings." })] }))] })] }) }) }));
267
291
  }