@mohasinac/appkit 3.5.5 → 3.5.7
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/_internal/client/features/layout/DashboardLayoutClient.js +24 -3
- package/dist/_internal/client/features/tour/TourProvider.d.ts +11 -3
- package/dist/_internal/client/features/tour/TourProvider.js +93 -4
- package/dist/_internal/client/scaffolds/index.d.ts +0 -2
- package/dist/_internal/client/scaffolds/index.js +0 -1
- package/dist/_internal/server/features/checkout/actions.js +65 -11
- package/dist/_internal/server/features/orders/adapters.js +3 -0
- package/dist/_internal/shared/actions/action-registry.js +20 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +21 -0
- package/dist/_internal/shared/checkout/order-math.js +24 -0
- package/dist/_internal/shared/features/orders/config.d.ts +1 -1
- package/dist/_internal/shared/features/orders/config.js +1 -1
- package/dist/_internal/shared/fees/calculator.d.ts +13 -0
- package/dist/_internal/shared/fees/calculator.js +8 -0
- package/dist/client-entry.d.ts +2 -2
- package/dist/client-entry.js +2 -2
- package/dist/client.d.ts +8 -0
- package/dist/client.js +8 -0
- package/dist/constants/api-endpoints.d.ts +9 -0
- package/dist/constants/api-endpoints.js +3 -0
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/admin/components/AdminBundleEditorView.d.ts +8 -1
- package/dist/features/admin/components/AdminBundleEditorView.js +15 -11
- package/dist/features/admin/components/AdminFeaturesView.js +1 -1
- package/dist/features/admin/components/AdminPayoutsView.js +41 -4
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +49 -1
- package/dist/features/admin/components/DataTable.js +1 -1
- package/dist/features/admin/components/index.d.ts +0 -2
- package/dist/features/admin/components/index.js +0 -1
- package/dist/features/admin/schemas/firestore.d.ts +8 -0
- package/dist/features/admin/schemas/firestore.js +6 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +1 -1
- package/dist/features/before-after/components/BeforeAfterSlider.js +4 -0
- package/dist/features/blog/components/BlogFeaturedCard.js +1 -1
- package/dist/features/categories/components/BundleDynamicRuleEditor.js +2 -9
- package/dist/features/events/components/EventCard.js +1 -1
- package/dist/features/events/components/EventRafflesSection.d.ts +1 -1
- package/dist/features/events/components/EventRafflesSection.js +3 -3
- package/dist/features/homepage/components/BeforeAfterCard.js +5 -0
- package/dist/features/homepage/components/FeaturedBundlesSection.d.ts +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +3 -1
- package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
- package/dist/features/homepage/components/HeroCarousel.js +1 -1
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +5 -5
- package/dist/features/media/MediaLightbox.js +7 -1
- package/dist/features/media/components/MediaSlider.js +4 -1
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/upload/ImageUpload.js +24 -12
- package/dist/features/messages/hooks/useConversations.d.ts +7 -1
- package/dist/features/messages/hooks/useConversations.js +7 -6
- package/dist/features/orders/actions/order-actions.d.ts +10 -0
- package/dist/features/orders/actions/order-actions.js +57 -4
- package/dist/features/orders/components/MarketplaceOrderCard.js +1 -1
- package/dist/features/orders/schemas/firestore.d.ts +16 -0
- package/dist/features/orders/types/index.d.ts +2 -0
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +1 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
- package/dist/features/products/components/PrizeDrawsSection.d.ts +1 -1
- package/dist/features/products/components/PrizeDrawsSection.js +3 -2
- package/dist/features/products/components/ProductForm.js +4 -9
- package/dist/features/products/components/ProductGrid.js +1 -1
- package/dist/features/products/schemas/firestore.d.ts +4 -0
- package/dist/features/promotions/components/CouponCard.js +1 -1
- package/dist/features/scams/actions/scam-actions.d.ts +12 -0
- package/dist/features/scams/actions/scam-actions.js +31 -0
- package/dist/features/scams/components/SellerTrustBadge.d.ts +16 -0
- package/dist/features/scams/components/SellerTrustBadge.js +21 -0
- package/dist/features/scams/components/index.d.ts +2 -0
- package/dist/features/scams/components/index.js +1 -0
- package/dist/features/seller/components/SellerBundlesView.d.ts +5 -5
- package/dist/features/seller/components/SellerBundlesView.js +81 -84
- package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
- package/dist/features/seller/components/SellerProductShell.js +11 -5
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/shipments/components/ShipmentItemLinkModal.js +5 -5
- package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +8 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +9 -3
- package/dist/features/stores/components/StoreHeader.d.ts +4 -1
- package/dist/features/stores/components/StoreHeader.js +3 -2
- package/dist/index.d.ts +7 -6
- package/dist/index.js +6 -5
- package/dist/jobs.d.ts +2 -0
- package/dist/jobs.js +6 -0
- package/dist/next/routing/route-map.d.ts +3 -0
- package/dist/next/routing/route-map.js +1 -0
- package/dist/react/hooks/useClickOutside.js +3 -0
- package/dist/security/rate-limit.d.ts +5 -0
- package/dist/security/rate-limit.js +2 -0
- package/dist/seed/categories-seed-data.js +180 -1
- package/dist/seed/grouped-listings-seed-data.d.ts +2 -0
- package/dist/seed/products-preorders-seed-data.d.ts +2 -0
- package/dist/seed/site-settings-seed-data.js +9 -2
- package/dist/server-entry.d.ts +2 -2
- package/dist/server-entry.js +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.js +5 -0
- package/dist/styles.css +11 -4
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/cobalt-night.d.ts +17 -0
- package/dist/tokens/themes/cobalt-night.js +50 -0
- package/dist/tokens/themes/index.d.ts +9 -1
- package/dist/tokens/themes/index.js +12 -1
- package/dist/tokens/themes/sunset.d.ts +18 -0
- package/dist/tokens/themes/sunset.js +51 -0
- package/dist/tokens/tokens.css +10 -3
- package/dist/ui/components/BaseListingCard.d.ts +2 -1
- package/dist/ui/components/BaseListingCard.js +4 -4
- package/dist/ui/components/Card.d.ts +1 -1
- package/dist/ui/components/Card.js +2 -2
- package/dist/ui/components/Div.js +2 -2
- package/dist/ui/components/Form.d.ts +1 -1
- package/dist/ui/components/Form.js +2 -2
- package/dist/ui/components/Input.js +1 -0
- package/dist/ui/components/ListingToolbar.js +1 -1
- package/dist/ui/components/QuickCreateModal.style.css +1 -1
- package/dist/ui/components/Slider.js +5 -1
- package/package.json +3 -2
|
@@ -27,7 +27,7 @@ function NavLink({ item, isActive, onClick }) {
|
|
|
27
27
|
const linkClass = `flex items-center gap-[var(--appkit-space-2-5)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
|
|
28
28
|
? "bg-primary-50 dark:bg-primary-900/25 text-primary-700 dark:text-primary-300"
|
|
29
29
|
: "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)] hover:text-zinc-900 dark:hover:text-zinc-100"}`;
|
|
30
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Link, { href: item.href, onClick: handleClick, className: linkClass, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-70", children: item.icon }), _jsx(Span, {
|
|
30
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Link, { href: item.href, onClick: handleClick, className: linkClass, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-70", children: item.icon }), _jsx(Span, { className: "flex-1 truncate", children: item.label })] }), showConfirm && item.confirm && (_jsx(ConfirmDeleteModal, { isOpen: true, title: item.confirm.title ?? "Confirm", message: item.confirm.message, confirmText: "Continue", variant: "warning", onConfirm: () => {
|
|
31
31
|
setShowConfirm(false);
|
|
32
32
|
onClick?.();
|
|
33
33
|
window.location.href = item.href;
|
|
@@ -18,5 +18,12 @@ export interface AdminBundleEditorViewProps {
|
|
|
18
18
|
onSaved?: (id: string) => void;
|
|
19
19
|
/** Called after a successful delete. */
|
|
20
20
|
onDeleted?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* "admin" (default) hits /api/admin/bundles and lets the product picker
|
|
23
|
+
* search all products. "store" hits /api/store/bundles (server-scoped to
|
|
24
|
+
* the caller's own store) and restricts the picker to the seller's own
|
|
25
|
+
* products.
|
|
26
|
+
*/
|
|
27
|
+
scope?: "admin" | "store";
|
|
21
28
|
}
|
|
22
|
-
export declare function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }: AdminBundleEditorViewProps): React.JSX.Element;
|
|
29
|
+
export declare function AdminBundleEditorView({ bundleId, onSaved, onDeleted, scope, }: AdminBundleEditorViewProps): React.JSX.Element;
|
|
@@ -18,11 +18,11 @@ import { z } from "zod";
|
|
|
18
18
|
import { Button, Checkbox, ConfirmDeleteModal, Container, Form, Heading, Input, Row, Section, Select, Stack, Text, Textarea, useToast, } from "../../../ui";
|
|
19
19
|
import { FieldInput } from "../../../ui/forms";
|
|
20
20
|
import { apiClient } from "../../../http";
|
|
21
|
-
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
21
|
+
import { ADMIN_ENDPOINTS, SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
22
22
|
import { BundleDynamicRuleEditor } from "../../categories/components/BundleDynamicRuleEditor";
|
|
23
23
|
import { ProductInlineSelect } from "../../seller/components/ProductInlineSelect";
|
|
24
24
|
import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
25
|
-
import { BUNDLE_MIN_ITEMS, BUNDLE_MAX_ITEMS, } from "../../../_internal/shared/features/categories/bundle-config";
|
|
25
|
+
import { BUNDLE_MIN_ITEMS, BUNDLE_MAX_ITEMS, BUNDLE_KIND_SPECIAL, } from "../../../_internal/shared/features/categories/bundle-config";
|
|
26
26
|
const DEFAULT_DYNAMIC_RULE = {
|
|
27
27
|
type: "dynamic",
|
|
28
28
|
filter: {},
|
|
@@ -81,8 +81,11 @@ function parsePriceRupees(input) {
|
|
|
81
81
|
return null;
|
|
82
82
|
return Math.round(n * 100);
|
|
83
83
|
}
|
|
84
|
-
export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
84
|
+
export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, scope = "admin", }) {
|
|
85
85
|
const isEdit = Boolean(bundleId);
|
|
86
|
+
const endpoints = React.useMemo(() => scope === "admin"
|
|
87
|
+
? { collection: ADMIN_ENDPOINTS.BUNDLES, byId: ADMIN_ENDPOINTS.BUNDLE_BY_ID }
|
|
88
|
+
: { collection: SELLER_ENDPOINTS.BUNDLES, byId: SELLER_ENDPOINTS.BUNDLE_BY_ID }, [scope]);
|
|
86
89
|
const [form, setForm] = useState(EMPTY_FORM);
|
|
87
90
|
const [loading, setLoading] = useState(isEdit);
|
|
88
91
|
const [saving, setSaving] = useState(false);
|
|
@@ -100,7 +103,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
100
103
|
let cancelled = false;
|
|
101
104
|
setLoading(true);
|
|
102
105
|
apiClient
|
|
103
|
-
.get(
|
|
106
|
+
.get(endpoints.byId(encodeURIComponent(bundleId)))
|
|
104
107
|
.then((res) => {
|
|
105
108
|
if (cancelled)
|
|
106
109
|
return;
|
|
@@ -122,7 +125,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
122
125
|
return () => {
|
|
123
126
|
cancelled = true;
|
|
124
127
|
};
|
|
125
|
-
}, [bundleId]);
|
|
128
|
+
}, [bundleId, endpoints]);
|
|
126
129
|
const handleSave = useCallback(async () => {
|
|
127
130
|
clearErrors();
|
|
128
131
|
setApiError(null);
|
|
@@ -155,6 +158,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
155
158
|
const body = {
|
|
156
159
|
name: form.name.trim(),
|
|
157
160
|
description: form.description.trim() || undefined,
|
|
161
|
+
bundleKind: BUNDLE_KIND_SPECIAL,
|
|
158
162
|
bundlePriceInPaise: priceInPaise,
|
|
159
163
|
bundleQueryRule,
|
|
160
164
|
bundleProductIds,
|
|
@@ -164,12 +168,12 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
164
168
|
isActive: form.isActive,
|
|
165
169
|
};
|
|
166
170
|
if (isEdit && bundleId) {
|
|
167
|
-
await apiClient.put(
|
|
171
|
+
await apiClient.put(endpoints.byId(encodeURIComponent(bundleId)), body);
|
|
168
172
|
showToast("Bundle saved.", "success");
|
|
169
173
|
onSaved?.(bundleId);
|
|
170
174
|
}
|
|
171
175
|
else {
|
|
172
|
-
const res = (await apiClient.post(
|
|
176
|
+
const res = (await apiClient.post(endpoints.collection, body));
|
|
173
177
|
const newId = res?.data?.id;
|
|
174
178
|
showToast("Bundle created.", "success");
|
|
175
179
|
if (newId)
|
|
@@ -185,14 +189,14 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
185
189
|
finally {
|
|
186
190
|
setSaving(false);
|
|
187
191
|
}
|
|
188
|
-
}, [form, bundleId, isEdit, onSaved, clearErrors, setFieldError, showToast]);
|
|
192
|
+
}, [form, bundleId, isEdit, onSaved, clearErrors, setFieldError, showToast, endpoints]);
|
|
189
193
|
const handleDelete = useCallback(async () => {
|
|
190
194
|
if (!bundleId)
|
|
191
195
|
return;
|
|
192
196
|
setDeleting(true);
|
|
193
197
|
setApiError(null);
|
|
194
198
|
try {
|
|
195
|
-
await apiClient.delete(
|
|
199
|
+
await apiClient.delete(endpoints.byId(encodeURIComponent(bundleId)));
|
|
196
200
|
showToast("Bundle deleted.", "success");
|
|
197
201
|
setDeleteConfirmOpen(false);
|
|
198
202
|
onDeleted?.();
|
|
@@ -206,7 +210,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
206
210
|
finally {
|
|
207
211
|
setDeleting(false);
|
|
208
212
|
}
|
|
209
|
-
}, [bundleId, onDeleted, showToast]);
|
|
213
|
+
}, [bundleId, onDeleted, showToast, endpoints]);
|
|
210
214
|
if (loading) {
|
|
211
215
|
return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "lg", children: _jsx(Text, { children: BUNDLE_COPY.adminEditor.loading }) }) }));
|
|
212
216
|
}
|
|
@@ -214,6 +218,6 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
214
218
|
formHelpersRef.current = helpers;
|
|
215
219
|
return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "lg", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, children: [_jsx(Heading, { level: 1, size: "2xl", weight: "semibold", color: "primary", children: isEdit
|
|
216
220
|
? BUNDLE_COPY.adminEditorTitleEdit
|
|
217
|
-
: BUNDLE_COPY.adminEditorTitleNew }), isEdit && (_jsx(Button, { variant: "danger", onClick: () => setDeleteConfirmOpen(true), disabled: deleting, children: BUNDLE_COPY.adminEditor.deleteButton(deleting) }))] }), _jsx(ConfirmDeleteModal, { isOpen: deleteConfirmOpen, onClose: () => setDeleteConfirmOpen(false), onConfirm: handleDelete, title: "Delete this bundle?", message: BUNDLE_COPY.adminEditor.deleteConfirm, isDeleting: deleting }), apiError && (_jsx(Text, { color: "danger", role: "alert", children: apiError })), _jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "name", label: BUNDLE_COPY.adminEditor.fields.nameLabel, value: form.name, onChange: (v) => setForm((f) => ({ ...f, name: v })), placeholder: BUNDLE_COPY.adminEditor.fields.namePlaceholder, disabled: saving, required: true }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.descriptionLabel }), _jsx(Textarea, { value: form.description, onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })), placeholder: BUNDLE_COPY.adminEditor.fields.descriptionPlaceholder, rows: 4, disabled: saving })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(FieldInput, { name: "price", label: BUNDLE_COPY.adminEditor.fields.priceLabel, type: "number", inputMode: "decimal", min: 1, step: 1, value: form.priceRupees, onChange: (v) => setForm((f) => ({ ...f, priceRupees: v })), placeholder: BUNDLE_COPY.adminEditor.fields.pricePlaceholder, disabled: saving, required: true }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.fields.pricePaiseHint(parsePriceRupees(form.priceRupees)) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.coverImageLabel }), _jsx(Input, { type: "url", value: form.coverImage, onChange: (e) => setForm((f) => ({ ...f, coverImage: e.target.value })), placeholder: "https://\u2026", disabled: saving })] }), _jsx(Checkbox, { checked: form.isActive, onChange: (e) => setForm((f) => ({ ...f, isActive: e.target.checked })), disabled: saving, label: BUNDLE_COPY.adminEditor.fields.activeLabel }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.ruleTypeLabel }), _jsx(Select, { options: RULE_TYPE_OPTIONS, value: form.ruleType, onValueChange: (next) => setForm((f) => ({ ...f, ruleType: next })), disabled: saving, "aria-label": BUNDLE_COPY.adminEditor.ruleTypeLabel })] }), form.ruleType === "static" ? (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: [BUNDLE_COPY.adminEditor.ruleTypeStatic, " products (", BUNDLE_MIN_ITEMS, "\u2013", BUNDLE_MAX_ITEMS, ")"] }), _jsx(ProductInlineSelect, { scope:
|
|
221
|
+
: BUNDLE_COPY.adminEditorTitleNew }), isEdit && (_jsx(Button, { variant: "danger", onClick: () => setDeleteConfirmOpen(true), disabled: deleting, children: BUNDLE_COPY.adminEditor.deleteButton(deleting) }))] }), _jsx(ConfirmDeleteModal, { isOpen: deleteConfirmOpen, onClose: () => setDeleteConfirmOpen(false), onConfirm: handleDelete, title: "Delete this bundle?", message: BUNDLE_COPY.adminEditor.deleteConfirm, isDeleting: deleting }), apiError && (_jsx(Text, { color: "danger", role: "alert", children: apiError })), _jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "name", label: BUNDLE_COPY.adminEditor.fields.nameLabel, value: form.name, onChange: (v) => setForm((f) => ({ ...f, name: v })), placeholder: BUNDLE_COPY.adminEditor.fields.namePlaceholder, disabled: saving, required: true }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.descriptionLabel }), _jsx(Textarea, { value: form.description, onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })), placeholder: BUNDLE_COPY.adminEditor.fields.descriptionPlaceholder, rows: 4, disabled: saving })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(FieldInput, { name: "price", label: BUNDLE_COPY.adminEditor.fields.priceLabel, type: "number", inputMode: "decimal", min: 1, step: 1, value: form.priceRupees, onChange: (v) => setForm((f) => ({ ...f, priceRupees: v })), placeholder: BUNDLE_COPY.adminEditor.fields.pricePlaceholder, disabled: saving, required: true }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.fields.pricePaiseHint(parsePriceRupees(form.priceRupees)) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.coverImageLabel }), _jsx(Input, { type: "url", value: form.coverImage, onChange: (e) => setForm((f) => ({ ...f, coverImage: e.target.value })), placeholder: "https://\u2026", disabled: saving })] }), _jsx(Checkbox, { checked: form.isActive, onChange: (e) => setForm((f) => ({ ...f, isActive: e.target.checked })), disabled: saving, label: BUNDLE_COPY.adminEditor.fields.activeLabel }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.ruleTypeLabel }), _jsx(Select, { options: RULE_TYPE_OPTIONS, value: form.ruleType, onValueChange: (next) => setForm((f) => ({ ...f, ruleType: next })), disabled: saving, "aria-label": BUNDLE_COPY.adminEditor.ruleTypeLabel })] }), form.ruleType === "static" ? (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: [BUNDLE_COPY.adminEditor.ruleTypeStatic, " products (", BUNDLE_MIN_ITEMS, "\u2013", BUNDLE_MAX_ITEMS, ")"] }), _jsx(ProductInlineSelect, { scope: scope, multiple: true, value: form.productIds, onChange: (ids) => setForm((f) => ({ ...f, productIds: ids })), disabled: saving, placeholder: "Search and select products\u2026" }), form.productIds.length > 0 && form.productIds.length < BUNDLE_MIN_ITEMS && (_jsxs(Text, { size: "xs", color: "danger", children: ["Select at least ", BUNDLE_MIN_ITEMS, " products (currently ", form.productIds.length, ")."] })), form.productIds.length > BUNDLE_MAX_ITEMS && (_jsxs(Text, { size: "xs", color: "danger", children: ["Maximum ", BUNDLE_MAX_ITEMS, " products allowed (currently ", form.productIds.length, ")."] }))] })) : (_jsx(BundleDynamicRuleEditor, { value: form.dynamicRule, onChange: (next) => setForm((f) => ({ ...f, dynamicRule: next })), disabled: saving }))] }), _jsx(Row, { gap: "sm", align: "center", justify: "end", children: _jsx(Button, { variant: "primary", onClick: handleSave, disabled: saving, "aria-busy": saving, children: BUNDLE_COPY.adminEditor.saveButton(saving, isEdit) }) })] }) }) }));
|
|
218
222
|
} }));
|
|
219
223
|
}
|
|
@@ -12,7 +12,7 @@ import { AdminFeatureEditorView } from "./AdminFeatureEditorView";
|
|
|
12
12
|
import { PRODUCT_FEATURE_SCOPE_TABS } from "../../products/constants/product-features.constants";
|
|
13
13
|
const PAGE_SIZE = 50;
|
|
14
14
|
const DEFAULT_SCOPE = "platform";
|
|
15
|
-
const STICKY_TABS_CLASS = "sticky top-[calc(var(--header-height,0px)+44px)] z-10 bg-
|
|
15
|
+
const STICKY_TABS_CLASS = "sticky top-[calc(var(--header-height,0px)+44px)] z-10 bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-[var(--appkit-space-3)] py-[var(--appkit-space-2)]";
|
|
16
16
|
function mapFeatureRow(item, index) {
|
|
17
17
|
const productTypes = Array.isArray(item.productTypes) ? item.productTypes : [];
|
|
18
18
|
const isActive = typeof item.isActive === "boolean" ? item.isActive : true;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { normalizeError } from "../../../errors/normalize";
|
|
4
|
-
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
4
|
+
import { useApiMutation, useBulkEvent, RTDB_PATHS } from "@mohasinac/appkit/client";
|
|
5
5
|
import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
|
|
6
6
|
import { sortBy } from "@mohasinac/appkit";
|
|
7
|
-
import React, { useState } from "react";
|
|
7
|
+
import React, { useState, useEffect } from "react";
|
|
8
8
|
import { useQueryClient } from "@tanstack/react-query";
|
|
9
9
|
import { Button, FilterChipGroup, Form, FormActions, Input, ListingLayout, Modal, RowActionMenu, useToast, } from "../../../ui";
|
|
10
10
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
@@ -18,8 +18,45 @@ export function AdminPayoutsView({ children, ...props }) {
|
|
|
18
18
|
const [markPaidOpen, setMarkPaidOpen] = useState(false);
|
|
19
19
|
const [selectedPayoutId, setSelectedPayoutId] = useState(null);
|
|
20
20
|
const [transactionId, setTransactionId] = useState("");
|
|
21
|
+
const [calculating, setCalculating] = useState(false);
|
|
21
22
|
const toast = useToast();
|
|
22
23
|
const queryClient = useQueryClient();
|
|
24
|
+
// P-7 — "Calculate Payouts" triggers the payoutsWeekly async job (Async Job
|
|
25
|
+
// Primitive) and tracks progress via the shared useBulkEvent RTDB channel.
|
|
26
|
+
const bulkEvent = useBulkEvent({ rtdbPath: RTDB_PATHS.BULK_EVENTS });
|
|
27
|
+
const handleCalculatePayouts = async () => {
|
|
28
|
+
setCalculating(true);
|
|
29
|
+
try {
|
|
30
|
+
const res = (await apiClient.post(ADMIN_ENDPOINTS.PAYOUTS_WEEKLY, {}));
|
|
31
|
+
const { jobId, customToken } = res.data ?? {};
|
|
32
|
+
if (jobId && customToken) {
|
|
33
|
+
bulkEvent.subscribe(jobId, customToken);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
setCalculating(false);
|
|
37
|
+
toast.showToast("Failed to start payout calculation.", "error");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (_err) {
|
|
41
|
+
void normalizeError(_err);
|
|
42
|
+
setCalculating(false);
|
|
43
|
+
toast.showToast("Failed to start payout calculation.", "error");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (bulkEvent.status === "success") {
|
|
48
|
+
setCalculating(false);
|
|
49
|
+
toast.showToast(bulkEvent.result?.summary
|
|
50
|
+
? `Payout calculation complete: ${JSON.stringify(bulkEvent.result.summary)}`
|
|
51
|
+
: "Payout calculation complete.", "success");
|
|
52
|
+
void queryClient.invalidateQueries({ queryKey: ["admin", "payouts", "listing"] });
|
|
53
|
+
}
|
|
54
|
+
else if (bulkEvent.status === "failed" || bulkEvent.status === "timeout") {
|
|
55
|
+
setCalculating(false);
|
|
56
|
+
toast.showToast(bulkEvent.error ?? "Payout calculation failed.", "error");
|
|
57
|
+
}
|
|
58
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
+
}, [bulkEvent.status]);
|
|
23
60
|
const markPaid = useApiMutation({
|
|
24
61
|
mutationFn: () => {
|
|
25
62
|
if (!selectedPayoutId)
|
|
@@ -85,7 +122,7 @@ export function AdminPayoutsView({ children, ...props }) {
|
|
|
85
122
|
})),
|
|
86
123
|
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
87
124
|
buildFilters: (state) => state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined,
|
|
88
|
-
toolbarExtra: (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: ACTIONS.ADMIN["export-csv"].label })),
|
|
125
|
+
toolbarExtra: (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", action: ACTIONS.ADMIN["calculate-payouts"], size: "sm", isLoading: calculating, disabled: calculating, onClick: handleCalculatePayouts }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: ACTIONS.ADMIN["export-csv"].label })] })),
|
|
89
126
|
// Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
|
|
90
127
|
buildBulkActions: (selection) => ADMIN_BULK_ACTIONS.payouts.map((id) => ({
|
|
91
128
|
id,
|
|
@@ -16,7 +16,7 @@ function isNavItemActive(item, activePath) {
|
|
|
16
16
|
function NavLink({ item, isActive, onClick }) {
|
|
17
17
|
return (_jsxs(Link, { href: item.href, onClick: onClick, className: `flex items-center gap-[var(--appkit-space-2-5)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
|
|
18
18
|
? "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text)]"
|
|
19
|
-
: "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-zinc-800 hover:text-[var(--appkit-color-text-muted)]"}`, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-60", children: item.icon }), _jsx(Span, {
|
|
19
|
+
: "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-zinc-800 hover:text-[var(--appkit-color-text-muted)]"}`, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-60", children: item.icon }), _jsx(Span, { className: "flex-1 truncate", children: item.label })] }));
|
|
20
20
|
}
|
|
21
21
|
function GroupsContent({ groups, activePath, onItemClick, }) {
|
|
22
22
|
const [openGroups, setOpenGroups] = useState(() => Object.fromEntries(groups.map((g) => [
|
|
@@ -110,6 +110,19 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
110
110
|
// ⑦ Fees — all read/written under commissions key
|
|
111
111
|
const [laborHourlyRatePaise, setLaborHourlyRatePaise] = React.useState(20000);
|
|
112
112
|
const [laborMaxHoursPerDay, setLaborMaxHoursPerDay] = React.useState(6);
|
|
113
|
+
// ⑯ EMI — site-wide installment settings
|
|
114
|
+
const [emiEnabled, setEmiEnabled] = React.useState(false);
|
|
115
|
+
const [emiMinOrderValuePaise, setEmiMinOrderValuePaise] = React.useState(1000000);
|
|
116
|
+
const [emiTenureOptionsText, setEmiTenureOptionsText] = React.useState("2,3,4,5,6");
|
|
117
|
+
const [emiTokenPercent, setEmiTokenPercent] = React.useState(20);
|
|
118
|
+
const [emiBillingDay, setEmiBillingDay] = React.useState(5);
|
|
119
|
+
const [emiSurchargePercentPerMonth, setEmiSurchargePercentPerMonth] = React.useState(2);
|
|
120
|
+
const [emiSurchargeSellerSharePercent, setEmiSurchargeSellerSharePercent] = React.useState(50);
|
|
121
|
+
// ⑰ GST — Indian tax compliance
|
|
122
|
+
const [gstEnabled, setGstEnabled] = React.useState(false);
|
|
123
|
+
const [gstin, setGstin] = React.useState("");
|
|
124
|
+
const [gstLegalName, setGstLegalName] = React.useState("");
|
|
125
|
+
const [gstAddress, setGstAddress] = React.useState("");
|
|
113
126
|
const [platformFeePercent, setPlatformFeePercent] = React.useState(5);
|
|
114
127
|
const [gstPercent, setGstPercent] = React.useState(18);
|
|
115
128
|
const [minimumTransactionFee, setMinimumTransactionFee] = React.useState(0);
|
|
@@ -249,6 +262,17 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
249
262
|
setPromotedSlotFee(s.commissions?.promotedSlotFee ?? 499);
|
|
250
263
|
setLaborHourlyRatePaise(s.laborRate?.hourlyRatePaise ?? 20000);
|
|
251
264
|
setLaborMaxHoursPerDay(s.laborRate?.maxHoursPerDay ?? 6);
|
|
265
|
+
setEmiEnabled(s.emi?.enabled ?? false);
|
|
266
|
+
setEmiMinOrderValuePaise(s.emi?.minOrderValueInPaise ?? 1000000);
|
|
267
|
+
setEmiTenureOptionsText((s.emi?.tenureOptions ?? [2, 3, 4, 5, 6]).join(","));
|
|
268
|
+
setEmiTokenPercent(s.emi?.tokenPercent ?? 20);
|
|
269
|
+
setEmiBillingDay(s.emi?.billingDay ?? 5);
|
|
270
|
+
setEmiSurchargePercentPerMonth(s.emi?.surchargePercentPerMonth ?? 2);
|
|
271
|
+
setEmiSurchargeSellerSharePercent(s.emi?.surchargeSellerSharePercent ?? 50);
|
|
272
|
+
setGstEnabled(s.gst?.enabled ?? false);
|
|
273
|
+
setGstin(s.gst?.gstin ?? "");
|
|
274
|
+
setGstLegalName(s.gst?.legalName ?? "");
|
|
275
|
+
setGstAddress(s.gst?.address ?? "");
|
|
252
276
|
setRazorpayKeyId(s.credentialsMasked?.razorpayKeyId ?? "");
|
|
253
277
|
setRazorpaySecret(s.credentialsMasked?.razorpaySecret ?? "");
|
|
254
278
|
setSmtpHost(s.emailSettings?.host ?? "");
|
|
@@ -361,6 +385,28 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
361
385
|
const laborRateMutation = useSave("Procurement", () => ({
|
|
362
386
|
laborRate: { hourlyRatePaise: laborHourlyRatePaise, maxHoursPerDay: laborMaxHoursPerDay },
|
|
363
387
|
}));
|
|
388
|
+
const emiSettingsMutation = useSave("EMI", () => ({
|
|
389
|
+
emi: {
|
|
390
|
+
enabled: emiEnabled,
|
|
391
|
+
minOrderValueInPaise: emiMinOrderValuePaise,
|
|
392
|
+
tenureOptions: emiTenureOptionsText
|
|
393
|
+
.split(",")
|
|
394
|
+
.map((v) => parseInt(v.trim(), 10))
|
|
395
|
+
.filter((v) => !isNaN(v) && v > 0),
|
|
396
|
+
tokenPercent: emiTokenPercent,
|
|
397
|
+
billingDay: emiBillingDay,
|
|
398
|
+
surchargePercentPerMonth: emiSurchargePercentPerMonth,
|
|
399
|
+
surchargeSellerSharePercent: emiSurchargeSellerSharePercent,
|
|
400
|
+
},
|
|
401
|
+
}));
|
|
402
|
+
const gstSettingsMutation = useSave("GST", () => ({
|
|
403
|
+
gst: {
|
|
404
|
+
enabled: gstEnabled,
|
|
405
|
+
gstin: gstin.trim().toUpperCase(),
|
|
406
|
+
legalName: gstLegalName.trim(),
|
|
407
|
+
address: gstAddress.trim(),
|
|
408
|
+
},
|
|
409
|
+
}));
|
|
364
410
|
const integrationsMutation = useSave("Integrations", () => ({
|
|
365
411
|
credentials: {
|
|
366
412
|
razorpayKeyId, razorpaySecret, smtpPassword,
|
|
@@ -438,6 +484,8 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
438
484
|
["whatsapp", "⑬ WhatsApp"],
|
|
439
485
|
["notifications", "⑭ Notifications"],
|
|
440
486
|
["procurement", "⑮ Procurement"],
|
|
487
|
+
["emi", "⑯ EMI"],
|
|
488
|
+
["gst", "⑰ GST"],
|
|
441
489
|
].map(([value, label]) => (_jsx(TabsTrigger, { value: value, children: label }, value))) }), _jsx(TabsContent, { value: "about", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); aboutMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Override the About page hero and mission text. Leave blank to use the platform defaults." }), _jsx(Input, { label: "Hero title", value: aboutTitle, onChange: (e) => setAboutTitle(e.target.value), placeholder: "About LetItRip" }), _jsx(Input, { label: "Hero subtitle", value: aboutSubtitle, onChange: (e) => setAboutSubtitle(e.target.value), placeholder: "Connecting buyers, sellers, and bidders in one vibrant marketplace" }), _jsx(Input, { label: "Mission section title", value: aboutMissionTitle, onChange: (e) => setAboutMissionTitle(e.target.value), placeholder: "Our Mission" }), _jsxs(_Fragment, { children: [_jsx(Text, { className: "mb-1", color: "muted", size: "sm", weight: "medium", children: "Mission text" }), _jsx(Textarea, { value: aboutMissionText, onChange: (e) => setAboutMissionText(e.target.value), placeholder: "LetItRip was built to democratise commerce\u2026", rows: 4 })] }), _jsx(Input, { label: "CTA banner title", value: aboutCtaTitle, onChange: (e) => setAboutCtaTitle(e.target.value), placeholder: "Ready to get started?" }), _jsx(GroupSaveButton, { isPending: aboutMutation.isPending })] }) }), _jsx(TabsContent, { value: "branding", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); brandingMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Site name", value: siteName, onChange: (e) => setSiteName(e.target.value), placeholder: "LetItRip" }), _jsx(Input, { label: "Tagline", value: tagline, onChange: (e) => setTagline(e.target.value), placeholder: "India's Largest Collectibles Marketplace" }), _jsx(ImageUpload, { label: "Logo", currentImage: logoUrl, onUpload: (file) => upload(file, "store"), onChange: setLogoUrl }), _jsx(ImageUpload, { label: "Favicon", currentImage: faviconUrl, onUpload: (file) => upload(file, "store"), onChange: setFaviconUrl }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Maintenance mode", checked: maintenanceMode, onChange: setMaintenanceMode }), _jsx(Input, { label: "Maintenance message", value: maintenanceMessage, onChange: (e) => setMaintenanceMessage(e.target.value), placeholder: "We're back soon.", disabled: !maintenanceMode })] }), _jsx(GroupSaveButton, { isPending: brandingMutation.isPending })] }) }), _jsx(TabsContent, { value: "appearance", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); appearanceMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Sets the background behind the public shell, dashboard sidebars, and any Section/Card that opts into it. Leave the value blank for a plain surface." }), _jsxs(Grid, { cols: 2, gap: "lg", children: [_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Light mode" }), _jsx(Select, { label: "Type", options: BG_TYPE_OPTIONS, value: lightBgType, onValueChange: (v) => setLightBgType(v) }), lightBgType === "color" ? (_jsx(Input, { type: "color", label: "Color", value: lightBgValue || "#ffffff", onChange: (e) => setLightBgValue(e.target.value), className: "h-10 w-full cursor-pointer", bare: true }) /* audit-hex-tokens-ok: native color picker requires literal hex string fallback */) : lightBgType === "gradient" ? (_jsx(Textarea, { label: "CSS gradient expression", rows: 2, value: lightBgValue, onChange: (e) => setLightBgValue(e.target.value), placeholder: "linear-gradient(...)" })) : (_jsx(MediaUploadField, { label: lightBgType === "video" ? "Background video" : "Background image", kind: lightBgType === "video" ? "video" : "image", value: lightBgValue, onChange: setLightBgValue, onUpload: (file) => upload(file, "store") })), _jsx(Toggle, { label: "Dim overlay", checked: lightBgOverlayEnabled, onChange: setLightBgOverlayEnabled }), lightBgOverlayEnabled && (_jsxs(_Fragment, { children: [_jsx(Input, { type: "color", label: "Overlay color", value: lightBgOverlayColor, onChange: (e) => setLightBgOverlayColor(e.target.value), className: "h-10 w-full cursor-pointer", bare: true }), " ", _jsx(Slider, { label: "Overlay opacity", min: 0, max: 100, value: lightBgOverlayOpacity, onChange: setLightBgOverlayOpacity })] })), _jsx(Text, { size: "xs", color: "muted", children: "Preview" }), _jsx(Div, { className: "relative h-24 w-full", overflow: "hidden", rounded: "md", border: "default", children: _jsx(BackgroundRenderer, { mode: "light", lightMode: {
|
|
442
490
|
type: lightBgType,
|
|
443
491
|
value: lightBgValue,
|
|
@@ -449,7 +497,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
449
497
|
} }) })] })] }), _jsx(GroupSaveButton, { isPending: appearanceMutation.isPending })] }) }), _jsx(TabsContent, { value: "themes", children: _jsxs(Form, { onSubmit: (e) => {
|
|
450
498
|
e.preventDefault();
|
|
451
499
|
themeMutation.mutate();
|
|
452
|
-
}, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(ThemeManagerView, { value: themeRegistry, onChange: setThemeRegistry, previewOrigin: "/" }), _jsx(GroupSaveButton, { isPending: themeMutation.isPending })] }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); announcementMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Toggle, { label: "Show announcement bar", checked: announcementEnabled, onChange: setAnnouncementEnabled }), _jsx(Input, { label: "Announcement text", value: announcementText, onChange: (e) => setAnnouncementText(e.target.value), placeholder: "\uD83C\uDF89 Free shipping on orders \u20B9999+", disabled: !announcementEnabled }), _jsx(Input, { label: "Link URL (optional)", value: announcementLink, onChange: (e) => setAnnouncementLink(e.target.value), placeholder: String(ROUTES.PUBLIC.PRODUCTS), disabled: !announcementEnabled }), _jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Background color" }), _jsx(Input, { type: "color", value: announcementBg || "#1d4ed8", onChange: (e) => setAnnouncementBg(e.target.value), className: "h-10 w-32 cursor-pointer", bare: true, disabled: !announcementEnabled }), " "] }), _jsx(GroupSaveButton, { isPending: announcementMutation.isPending })] }) }), _jsx(TabsContent, { value: "seo", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); seoMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Default meta title", value: seoTitle, onChange: (e) => setSeoTitle(e.target.value), placeholder: "LetItRip \u2014 Buy, Sell & Auction Collectibles in India", maxLength: 60, helperText: "Max 60 chars. Use {page} token for dynamic insertion." }), _jsx(Input, { label: "Default meta description", value: seoDescription, onChange: (e) => setSeoDescription(e.target.value), placeholder: "India's largest collectibles marketplace\u2026", maxLength: 160, helperText: "Max 160 chars." }), _jsx(ImageUpload, { label: "Default OG image", currentImage: seoOgImage, onUpload: (file) => upload(file, "store"), onChange: setSeoOgImage }), _jsx(Input, { label: "Canonical base URL", value: canonicalUrl, onChange: (e) => setCanonicalUrl(e.target.value), placeholder: "https://letitrip.in" }), _jsx(Toggle, { label: "Robots noindex (disables search indexing \u2014 use carefully)", checked: seoNoIndex, onChange: setSeoNoIndex }), _jsx(GroupSaveButton, { isPending: seoMutation.isPending })] }) }), _jsx(TabsContent, { value: "contact", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); contactMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Support email", value: supportEmail, onChange: (e) => setSupportEmail(e.target.value), type: "email", placeholder: "support@letitrip.in" }), _jsx(Input, { label: "Support phone", value: supportPhone, onChange: (e) => setSupportPhone(e.target.value), placeholder: "+91 XXXXX XXXXX" })] }), _jsx(Input, { label: "Physical address", value: supportAddress, onChange: (e) => setSupportAddress(e.target.value), placeholder: "Mumbai, Maharashtra, India" }), _jsx(Input, { label: "Support hours", value: supportHours, onChange: (e) => setSupportHours(e.target.value), placeholder: "Mon\u2013Fri, 10 AM \u2013 6 PM IST" }), _jsx(Text, { className: "pt-[0.5rem]", color: "muted", size: "sm", weight: "medium", children: "Social links" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Instagram URL", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "https://instagram.com/letitrip" }), _jsx(Input, { label: "Twitter / X URL", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "https://twitter.com/letitrip" }), _jsx(Input, { label: "Facebook URL", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/letitrip" }), _jsx(Input, { label: "YouTube URL", value: youtube, onChange: (e) => setYoutube(e.target.value), placeholder: "https://youtube.com/@letitrip" }), _jsx(Input, { label: "WhatsApp number", value: whatsapp, onChange: (e) => setWhatsapp(e.target.value), placeholder: "+91XXXXXXXXXX" }), _jsx(Input, { label: "LinkedIn URL", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/company/letitrip" }), _jsx(Input, { label: "Pinterest URL", value: pinterest, onChange: (e) => setPinterest(e.target.value), placeholder: "https://pinterest.com/letitrip" })] }), _jsx(GroupSaveButton, { isPending: contactMutation.isPending })] }) }), _jsx(TabsContent, { value: "watermark", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); watermarkMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Select, { label: "Watermark type", options: [{ label: "Text", value: "text" }, { label: "Image", value: "image" }], value: watermarkType, onValueChange: (v) => setWatermarkType(v) }), watermarkType === "text" ? (_jsx(Input, { label: "Watermark text", value: watermarkText, onChange: (e) => setWatermarkText(e.target.value), placeholder: "letitrip.in" })) : (_jsx(ImageUpload, { label: "Watermark image", currentImage: watermarkImageUrl, onUpload: (file) => upload(file, "store"), onChange: setWatermarkImageUrl })), _jsx(Slider, { label: `Size — ${watermarkSize}% of image width`, value: watermarkSize, onChange: setWatermarkSize, min: 5, max: 100, step: 5 }), _jsx(Slider, { label: `Opacity — ${watermarkOpacity}%`, value: watermarkOpacity, onChange: setWatermarkOpacity, min: 5, max: 100, step: 5 }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Preview (text watermark only)" }), _jsx(Row, { surface: "default", justify: "end", align: "end", className: `relative h-32 ${__O.hidden}`, rounded: "default", children: _jsx(Span, { ref: watermarkPreviewRef, weight: "medium", color: "faint", padding: "1", className: "select-none", children: watermarkText }) })] }), _jsx(GroupSaveButton, { isPending: watermarkMutation.isPending })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); feesMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Platform fee \u2014 our cut (%)", helperText: "% charged on order value. Buyer pays this.", value: String(platformFeePercent), onChange: (e) => setPlatformFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "GST on platform fee (%)", helperText: "Applied to our fee only (not full order). Usually 18%.", value: String(gstPercent), onChange: (e) => setGstPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway cost (%)", helperText: "Gateway's own fee \u2014 absorbed by platform, not passed through.", value: String(gatewayFeePercent), onChange: (e) => setGatewayFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Minimum transaction fee (\u20B9)", helperText: "Per-transaction floor. Total charge will never be below base + this.", value: String(minimumTransactionFee), onChange: (e) => setMinimumTransactionFee(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: feesMutation.isPending })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); laborRateMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Used to compute each procurement shipment's labor cost (hours spent \u00D7 hourly rate) and its estimated processing time (hours spent \u00F7 max hours/day)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Hourly rate (\u20B9)", helperText: "Your effective hourly wage for sorting/categorizing/listing a shipment.", value: String(laborHourlyRatePaise / 100), onChange: (e) => setLaborHourlyRatePaise(Math.round((parseFloat(e.target.value) || 0) * 100)), type: "number", min: 0 }), _jsx(Input, { label: "Max hours per day", helperText: "Used only to project how many days a shipment's tracked hours will take.", value: String(laborMaxHoursPerDay), onChange: (e) => setLaborMaxHoursPerDay(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 24 })] }), _jsx(GroupSaveButton, { isPending: laborRateMutation.isPending })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); integrationsMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsx(GroupSaveButton, { isPending: integrationsMutation.isPending })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); shippingMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Payment methods" }), _jsx(Toggle, { label: "Razorpay (online card/UPI) enabled \u2014 disabled by default, manual payment is the default", checked: razorpayEnabled, onChange: setRazorpayEnabled }), _jsx(Toggle, { label: "Manual UPI/bank transfer enabled", checked: upiManualEnabled, onChange: setUpiManualEnabled }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(GroupSaveButton, { isPending: shippingMutation.isPending })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); auctionMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Minimum bid increment (\u20B9)", value: String(minBidIncrement), onChange: (e) => setMinBidIncrement(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Global default \u2014 individual auctions may override." }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." }), _jsx(GroupSaveButton, { isPending: auctionMutation.isPending })] }) }), _jsx(TabsContent, { value: "limits", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); limitsMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: limitsMutation.isPending })] }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); whatsappMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(GroupSaveButton, { isPending: whatsappMutation.isPending })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); notifChannelsMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "In-app notifications are always on. Enable external channels below to let the platform fan out to email, WhatsApp, or SMS. Users can further restrict which types they receive." }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "In-app (notification bell)" }), _jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-xs", children: "Always on" })] }), _jsx(Text, { size: "xs", color: "muted", children: "Displayed in the notification bell and inbox. Cannot be disabled." })] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Email notifications", checked: notifEmailEnabled, onChange: setNotifEmailEnabled }), notifEmailEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send email", options: PRIORITY_OPTIONS, value: notifEmailMinPriority, onValueChange: setNotifEmailMinPriority }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Resend API (for transactional email)" }), _jsx(MaskedInput, { label: "Resend API Key", value: resendApiKey, onChange: setResendApiKey, placeholder: "re_live_\u2026", helperText: "Get your key at resend.com \u2014 used for all transactional notifications." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "From email", value: notifFromEmail, onChange: (e) => setNotifFromEmail(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Input, { label: "From name", value: notifFromName, onChange: (e) => setNotifFromName(e.target.value), placeholder: "LetItRip" })] })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "WhatsApp notifications", checked: notifWhatsappEnabled, onChange: setNotifWhatsappEnabled }), notifWhatsappEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send WhatsApp", options: PRIORITY_OPTIONS, value: notifWhatsappMinPriority, onValueChange: setNotifWhatsappMinPriority }), _jsx(Toggle, { label: "Enable WhatsApp OTP (for login and verification)", checked: notifWhatsappOtpEnabled, onChange: setNotifWhatsappOtpEnabled }), _jsx(Text, { size: "xs", color: "muted", children: "WhatsApp credentials are configured in the WhatsApp tab (\u246C). OTP messages use the same phone number." })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "SMS notifications", checked: notifSmsEnabled, onChange: setNotifSmsEnabled }), notifSmsEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send SMS", options: PRIORITY_OPTIONS, value: notifSmsMinPriority, onValueChange: setNotifSmsMinPriority }), _jsx(Text, { size: "xs", color: "muted", children: "SMS gateway credentials (e.g. Twilio, MSG91) can be configured in the Integrations tab once an SMS provider is connected." })] }))] }), _jsx(GroupSaveButton, { isPending: notifChannelsMutation.isPending })] }) }), _jsx(TabsContent, { value: "legal", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); legalMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [[
|
|
500
|
+
}, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(ThemeManagerView, { value: themeRegistry, onChange: setThemeRegistry, previewOrigin: "/" }), _jsx(GroupSaveButton, { isPending: themeMutation.isPending })] }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); announcementMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Toggle, { label: "Show announcement bar", checked: announcementEnabled, onChange: setAnnouncementEnabled }), _jsx(Input, { label: "Announcement text", value: announcementText, onChange: (e) => setAnnouncementText(e.target.value), placeholder: "\uD83C\uDF89 Free shipping on orders \u20B9999+", disabled: !announcementEnabled }), _jsx(Input, { label: "Link URL (optional)", value: announcementLink, onChange: (e) => setAnnouncementLink(e.target.value), placeholder: String(ROUTES.PUBLIC.PRODUCTS), disabled: !announcementEnabled }), _jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Background color" }), _jsx(Input, { type: "color", value: announcementBg || "#1d4ed8", onChange: (e) => setAnnouncementBg(e.target.value), className: "h-10 w-32 cursor-pointer", bare: true, disabled: !announcementEnabled }), " "] }), _jsx(GroupSaveButton, { isPending: announcementMutation.isPending })] }) }), _jsx(TabsContent, { value: "seo", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); seoMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Default meta title", value: seoTitle, onChange: (e) => setSeoTitle(e.target.value), placeholder: "LetItRip \u2014 Buy, Sell & Auction Collectibles in India", maxLength: 60, helperText: "Max 60 chars. Use {page} token for dynamic insertion." }), _jsx(Input, { label: "Default meta description", value: seoDescription, onChange: (e) => setSeoDescription(e.target.value), placeholder: "India's largest collectibles marketplace\u2026", maxLength: 160, helperText: "Max 160 chars." }), _jsx(ImageUpload, { label: "Default OG image", currentImage: seoOgImage, onUpload: (file) => upload(file, "store"), onChange: setSeoOgImage }), _jsx(Input, { label: "Canonical base URL", value: canonicalUrl, onChange: (e) => setCanonicalUrl(e.target.value), placeholder: "https://letitrip.in" }), _jsx(Toggle, { label: "Robots noindex (disables search indexing \u2014 use carefully)", checked: seoNoIndex, onChange: setSeoNoIndex }), _jsx(GroupSaveButton, { isPending: seoMutation.isPending })] }) }), _jsx(TabsContent, { value: "contact", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); contactMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Support email", value: supportEmail, onChange: (e) => setSupportEmail(e.target.value), type: "email", placeholder: "support@letitrip.in" }), _jsx(Input, { label: "Support phone", value: supportPhone, onChange: (e) => setSupportPhone(e.target.value), placeholder: "+91 XXXXX XXXXX" })] }), _jsx(Input, { label: "Physical address", value: supportAddress, onChange: (e) => setSupportAddress(e.target.value), placeholder: "Mumbai, Maharashtra, India" }), _jsx(Input, { label: "Support hours", value: supportHours, onChange: (e) => setSupportHours(e.target.value), placeholder: "Mon\u2013Fri, 10 AM \u2013 6 PM IST" }), _jsx(Text, { className: "pt-[0.5rem]", color: "muted", size: "sm", weight: "medium", children: "Social links" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Instagram URL", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "https://instagram.com/letitrip" }), _jsx(Input, { label: "Twitter / X URL", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "https://twitter.com/letitrip" }), _jsx(Input, { label: "Facebook URL", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/letitrip" }), _jsx(Input, { label: "YouTube URL", value: youtube, onChange: (e) => setYoutube(e.target.value), placeholder: "https://youtube.com/@letitrip" }), _jsx(Input, { label: "WhatsApp number", value: whatsapp, onChange: (e) => setWhatsapp(e.target.value), placeholder: "+91XXXXXXXXXX" }), _jsx(Input, { label: "LinkedIn URL", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/company/letitrip" }), _jsx(Input, { label: "Pinterest URL", value: pinterest, onChange: (e) => setPinterest(e.target.value), placeholder: "https://pinterest.com/letitrip" })] }), _jsx(GroupSaveButton, { isPending: contactMutation.isPending })] }) }), _jsx(TabsContent, { value: "watermark", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); watermarkMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Select, { label: "Watermark type", options: [{ label: "Text", value: "text" }, { label: "Image", value: "image" }], value: watermarkType, onValueChange: (v) => setWatermarkType(v) }), watermarkType === "text" ? (_jsx(Input, { label: "Watermark text", value: watermarkText, onChange: (e) => setWatermarkText(e.target.value), placeholder: "letitrip.in" })) : (_jsx(ImageUpload, { label: "Watermark image", currentImage: watermarkImageUrl, onUpload: (file) => upload(file, "store"), onChange: setWatermarkImageUrl })), _jsx(Slider, { label: `Size — ${watermarkSize}% of image width`, value: watermarkSize, onChange: setWatermarkSize, min: 5, max: 100, step: 5 }), _jsx(Slider, { label: `Opacity — ${watermarkOpacity}%`, value: watermarkOpacity, onChange: setWatermarkOpacity, min: 5, max: 100, step: 5 }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Preview (text watermark only)" }), _jsx(Row, { surface: "default", justify: "end", align: "end", className: `relative h-32 ${__O.hidden}`, rounded: "default", children: _jsx(Span, { ref: watermarkPreviewRef, weight: "medium", color: "faint", padding: "1", className: "select-none", children: watermarkText }) })] }), _jsx(GroupSaveButton, { isPending: watermarkMutation.isPending })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); feesMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Platform fee \u2014 our cut (%)", helperText: "% charged on order value. Buyer pays this.", value: String(platformFeePercent), onChange: (e) => setPlatformFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "GST on platform fee (%)", helperText: "Applied to our fee only (not full order). Usually 18%.", value: String(gstPercent), onChange: (e) => setGstPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway cost (%)", helperText: "Gateway's own fee \u2014 absorbed by platform, not passed through.", value: String(gatewayFeePercent), onChange: (e) => setGatewayFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Minimum transaction fee (\u20B9)", helperText: "Per-transaction floor. Total charge will never be below base + this.", value: String(minimumTransactionFee), onChange: (e) => setMinimumTransactionFee(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: feesMutation.isPending })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); laborRateMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Used to compute each procurement shipment's labor cost (hours spent \u00D7 hourly rate) and its estimated processing time (hours spent \u00F7 max hours/day)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Hourly rate (\u20B9)", helperText: "Your effective hourly wage for sorting/categorizing/listing a shipment.", value: String(laborHourlyRatePaise / 100), onChange: (e) => setLaborHourlyRatePaise(Math.round((parseFloat(e.target.value) || 0) * 100)), type: "number", min: 0 }), _jsx(Input, { label: "Max hours per day", helperText: "Used only to project how many days a shipment's tracked hours will take.", value: String(laborMaxHoursPerDay), onChange: (e) => setLaborMaxHoursPerDay(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 24 })] }), _jsx(GroupSaveButton, { isPending: laborRateMutation.isPending })] }) }), _jsx(TabsContent, { value: "emi", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); emiSettingsMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "A seller must also opt in via their own Payout Settings for EMI to appear at checkout on their items. See the \"How EMI Works\" public page for buyer-facing copy." }), _jsx(Toggle, { checked: emiEnabled, onChange: setEmiEnabled, label: "Enable EMI platform-wide" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Minimum order value (\u20B9)", helperText: "A seller's cart subtotal must exceed this for EMI to appear as an option.", value: String(emiMinOrderValuePaise / 100), onChange: (e) => setEmiMinOrderValuePaise(Math.round((parseFloat(e.target.value) || 0) * 100)), type: "number", min: 0 }), _jsx(Input, { label: "Tenure options (months, comma-separated)", helperText: "e.g. 2,3,4,5,6", value: emiTenureOptionsText, onChange: (e) => setEmiTenureOptionsText(e.target.value) }), _jsx(Input, { label: "Token / down-payment (%)", helperText: "Collected upfront at checkout.", value: String(emiTokenPercent), onChange: (e) => setEmiTokenPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 }), _jsx(Input, { label: "Billing day (1\u201310)", helperText: "Day of month each installment is due.", value: String(emiBillingDay), onChange: (e) => setEmiBillingDay(parseFloat(e.target.value) || 1), type: "number", min: 1, max: 10 }), _jsx(Input, { label: "Surcharge (% of principal per month)", helperText: "The 'excess EMI fee' the buyer pays for spreading payment.", value: String(emiSurchargePercentPerMonth), onChange: (e) => setEmiSurchargePercentPerMonth(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Seller's share of surcharge (%)", helperText: "The rest of the surcharge goes to the platform.", value: String(emiSurchargeSellerSharePercent), onChange: (e) => setEmiSurchargeSellerSharePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 })] }), _jsx(GroupSaveButton, { isPending: emiSettingsMutation.isPending })] }) }), _jsx(TabsContent, { value: "gst", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); gstSettingsMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Required before enabling GST-inclusive checkout (P-8). Also set a GST rate + HSN code on each taxable product for the tax breakdown to appear." }), _jsx(Toggle, { checked: gstEnabled, onChange: setGstEnabled, label: "Enable GST on checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "GSTIN", helperText: "15-character GST Identification Number.", value: gstin, onChange: (e) => setGstin(e.target.value), placeholder: "29AAAAA0000A1Z5", maxLength: 15 }), _jsx(Input, { label: "Legal name", helperText: "Registered business name for invoices.", value: gstLegalName, onChange: (e) => setGstLegalName(e.target.value), placeholder: "LetItRip Collectibles Pvt Ltd" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Registered address" }), _jsx(Textarea, { value: gstAddress, onChange: (e) => setGstAddress(e.target.value), placeholder: "Registered business address for GST invoices\u2026", rows: 3 })] }), _jsx(GroupSaveButton, { isPending: gstSettingsMutation.isPending })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); integrationsMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsx(GroupSaveButton, { isPending: integrationsMutation.isPending })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); shippingMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Payment methods" }), _jsx(Toggle, { label: "Razorpay (online card/UPI) enabled \u2014 disabled by default, manual payment is the default", checked: razorpayEnabled, onChange: setRazorpayEnabled }), _jsx(Toggle, { label: "Manual UPI/bank transfer enabled", checked: upiManualEnabled, onChange: setUpiManualEnabled }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(GroupSaveButton, { isPending: shippingMutation.isPending })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); auctionMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Minimum bid increment (\u20B9)", value: String(minBidIncrement), onChange: (e) => setMinBidIncrement(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Global default \u2014 individual auctions may override." }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." }), _jsx(GroupSaveButton, { isPending: auctionMutation.isPending })] }) }), _jsx(TabsContent, { value: "limits", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); limitsMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: limitsMutation.isPending })] }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); whatsappMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(GroupSaveButton, { isPending: whatsappMutation.isPending })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); notifChannelsMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "In-app notifications are always on. Enable external channels below to let the platform fan out to email, WhatsApp, or SMS. Users can further restrict which types they receive." }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "In-app (notification bell)" }), _jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-xs", children: "Always on" })] }), _jsx(Text, { size: "xs", color: "muted", children: "Displayed in the notification bell and inbox. Cannot be disabled." })] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Email notifications", checked: notifEmailEnabled, onChange: setNotifEmailEnabled }), notifEmailEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send email", options: PRIORITY_OPTIONS, value: notifEmailMinPriority, onValueChange: setNotifEmailMinPriority }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Resend API (for transactional email)" }), _jsx(MaskedInput, { label: "Resend API Key", value: resendApiKey, onChange: setResendApiKey, placeholder: "re_live_\u2026", helperText: "Get your key at resend.com \u2014 used for all transactional notifications." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "From email", value: notifFromEmail, onChange: (e) => setNotifFromEmail(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Input, { label: "From name", value: notifFromName, onChange: (e) => setNotifFromName(e.target.value), placeholder: "LetItRip" })] })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "WhatsApp notifications", checked: notifWhatsappEnabled, onChange: setNotifWhatsappEnabled }), notifWhatsappEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send WhatsApp", options: PRIORITY_OPTIONS, value: notifWhatsappMinPriority, onValueChange: setNotifWhatsappMinPriority }), _jsx(Toggle, { label: "Enable WhatsApp OTP (for login and verification)", checked: notifWhatsappOtpEnabled, onChange: setNotifWhatsappOtpEnabled }), _jsx(Text, { size: "xs", color: "muted", children: "WhatsApp credentials are configured in the WhatsApp tab (\u246C). OTP messages use the same phone number." })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "SMS notifications", checked: notifSmsEnabled, onChange: setNotifSmsEnabled }), notifSmsEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send SMS", options: PRIORITY_OPTIONS, value: notifSmsMinPriority, onValueChange: setNotifSmsMinPriority }), _jsx(Text, { size: "xs", color: "muted", children: "SMS gateway credentials (e.g. Twilio, MSG91) can be configured in the Integrations tab once an SMS provider is connected." })] }))] }), _jsx(GroupSaveButton, { isPending: notifChannelsMutation.isPending })] }) }), _jsx(TabsContent, { value: "legal", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); legalMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [[
|
|
453
501
|
["Terms of Service", termsHtml, setTermsHtml],
|
|
454
502
|
["Privacy Policy", privacyHtml, setPrivacyHtml],
|
|
455
503
|
["Refund Policy", refundHtml, setRefundHtml],
|
|
@@ -39,7 +39,7 @@ function SelectableRow({ row, columns, isSelected, onToggle, renderRowActions, o
|
|
|
39
39
|
}
|
|
40
40
|
: undefined;
|
|
41
41
|
const isInteractive = Boolean(onRowClick ?? rowHref);
|
|
42
|
-
return (_jsxs(Tr, { "data-testid": "data-table-row", onClick: handleClick, onKeyDown: handleKeyDown, role: isInteractive ? "link" : undefined, tabIndex: isInteractive ? 0 : undefined, onMouseDown: selectionEnabled && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: selectionEnabled && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: selectionEnabled && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: selectionEnabled && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: selectionEnabled && !isSelected ? longPress.onTouchEnd : undefined, className: `hover:bg-[var(--appkit-color-bg)] dark:hover:bg-[var(--appkit-color-bg)] ${isInteractive ? "cursor-pointer" : ""} ${isSelected ? "bg-primary/5 dark:bg-primary/10" : ""}`, border: "default", children: [selectionEnabled && (_jsx(Td, { padding: "xs-3", className: "relative w-10", onClick: (e) => e.stopPropagation(), children: _jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onToggle?.(row.id, !isSelected); }, label: isSelected ? "Deselect row" : "Select row", position: "top-1/2 left-2 -translate-y-1/2", "data-testid": "row-checkbox" }) })), columns.map((col) => (_jsx(Td, { color: "muted", className: col.className ?? "", padding: "md", children: col.render
|
|
42
|
+
return (_jsxs(Tr, { "data-testid": "data-table-row", onClick: handleClick, onKeyDown: handleKeyDown, role: isInteractive ? "link" : undefined, tabIndex: isInteractive ? 0 : undefined, onMouseDown: selectionEnabled && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: selectionEnabled && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: selectionEnabled && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: selectionEnabled && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: selectionEnabled && !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: selectionEnabled && !isSelected ? longPress.onTouchCancel : undefined, className: `hover:bg-[var(--appkit-color-bg)] dark:hover:bg-[var(--appkit-color-bg)] ${isInteractive ? "cursor-pointer" : ""} ${isSelected ? "bg-primary/5 dark:bg-primary/10" : ""}`, border: "default", children: [selectionEnabled && (_jsx(Td, { padding: "xs-3", className: "relative w-10", onClick: (e) => e.stopPropagation(), children: _jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onToggle?.(row.id, !isSelected); }, label: isSelected ? "Deselect row" : "Select row", position: "top-1/2 left-2 -translate-y-1/2", "data-testid": "row-checkbox" }) })), columns.map((col) => (_jsx(Td, { color: "muted", className: col.className ?? "", padding: "md", children: col.render
|
|
43
43
|
? col.render(row)
|
|
44
44
|
: String(row[col.key] ?? "") }, col.key))), renderRowActions && (_jsx(Td, { padding: "xs-3", onClick: (e) => e.stopPropagation(), children: renderRowActions(row) }))] }));
|
|
45
45
|
}
|
|
@@ -109,8 +109,6 @@ export { AdminCarouselEditorView } from "./AdminCarouselEditorView";
|
|
|
109
109
|
export type { AdminCarouselEditorViewProps } from "./AdminCarouselEditorView";
|
|
110
110
|
export { AdminDashboardView } from "./AdminDashboardView";
|
|
111
111
|
export type { AdminDashboardViewProps } from "./AdminDashboardView";
|
|
112
|
-
export { QuickActionsPanel } from "./QuickActionsPanel";
|
|
113
|
-
export type { QuickActionItem, QuickActionsPanelProps, } from "./QuickActionsPanel";
|
|
114
112
|
export { AdminSiteView } from "./AdminSiteView";
|
|
115
113
|
export type { AdminSiteViewProps } from "./AdminSiteView";
|
|
116
114
|
export { AdminSiteSettingsView } from "./AdminSiteSettingsView";
|
|
@@ -59,7 +59,6 @@ export { AdminAdEditorView } from "./AdminAdEditorView";
|
|
|
59
59
|
export { AdminCarouselView } from "./AdminCarouselView";
|
|
60
60
|
export { AdminCarouselEditorView } from "./AdminCarouselEditorView";
|
|
61
61
|
export { AdminDashboardView } from "./AdminDashboardView";
|
|
62
|
-
export { QuickActionsPanel } from "./QuickActionsPanel";
|
|
63
62
|
export { AdminSiteView } from "./AdminSiteView";
|
|
64
63
|
export { AdminSiteSettingsView } from "./AdminSiteSettingsView";
|
|
65
64
|
export { AdminAnalyticsView } from "./AdminAnalyticsView";
|
|
@@ -337,6 +337,14 @@ export interface SiteSettingsDocument extends BaseDocument {
|
|
|
337
337
|
hourlyRatePaise: number;
|
|
338
338
|
maxHoursPerDay: number;
|
|
339
339
|
};
|
|
340
|
+
/** P-8 GST — Indian tax compliance. Distinct from commissions.gstPercent
|
|
341
|
+
* (which is GST on our platform commission) — this is buyer-facing product GST. */
|
|
342
|
+
gst: {
|
|
343
|
+
enabled: boolean;
|
|
344
|
+
gstin: string;
|
|
345
|
+
legalName: string;
|
|
346
|
+
address: string;
|
|
347
|
+
};
|
|
340
348
|
socialLinks: {
|
|
341
349
|
facebook?: string;
|
|
342
350
|
twitter?: string;
|
|
@@ -203,6 +203,12 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
|
|
|
203
203
|
hourlyRatePaise: 20000,
|
|
204
204
|
maxHoursPerDay: 6,
|
|
205
205
|
},
|
|
206
|
+
gst: {
|
|
207
|
+
enabled: false,
|
|
208
|
+
gstin: "",
|
|
209
|
+
legalName: "",
|
|
210
|
+
address: "",
|
|
211
|
+
},
|
|
206
212
|
featureFlags: {
|
|
207
213
|
chats: true,
|
|
208
214
|
smsVerification: true,
|
|
@@ -161,5 +161,5 @@ export function MarketplaceAuctionCard({ product, buyoutPrice, className = "", v
|
|
|
161
161
|
remaining, resolvedBuyoutPrice, inWishlist, wishlistLoading, wishlistActions,
|
|
162
162
|
countdownClass, mergedLabels, handleWishlist, handleNavigate,
|
|
163
163
|
};
|
|
164
|
-
return (_jsxs(BaseListingCard, { isSelected: isSelected, isDisabled: isEnded, variant: baseVariant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, children: [renderAuctionCardHero({ product, auctionHref, currentSrc, hovered, images, imageIndex, hasVideo, isEnded, isEndingSoon, isSelected, selectable, mergedLabels, onSelect, setHovered, handleSelect }), _jsx(BaseListingCard.Info, { variant: baseVariant, children: baseVariant === "list" ? renderAuctionCardInfoList(cardInfoProps) : renderAuctionCardInfoGrid(cardInfoProps) })] }));
|
|
164
|
+
return (_jsxs(BaseListingCard, { isSelected: isSelected, isDisabled: isEnded, variant: baseVariant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [renderAuctionCardHero({ product, auctionHref, currentSrc, hovered, images, imageIndex, hasVideo, isEnded, isEndingSoon, isSelected, selectable, mergedLabels, onSelect, setHovered, handleSelect }), _jsx(BaseListingCard.Info, { variant: baseVariant, children: baseVariant === "list" ? renderAuctionCardInfoList(cardInfoProps) : renderAuctionCardInfoGrid(cardInfoProps) })] }));
|
|
165
165
|
}
|
|
@@ -10,6 +10,10 @@ export function BeforeAfterSlider({ item, className = "", }) {
|
|
|
10
10
|
return;
|
|
11
11
|
move(e.clientX);
|
|
12
12
|
}
|
|
13
|
+
// audit-touch-handler-ok: position is derived fresh from each move event's
|
|
14
|
+
// coordinates, no "dragging in progress" flag or timer persists across
|
|
15
|
+
// gestures — a cancelled touch just leaves `position` at its last value,
|
|
16
|
+
// which is the same outcome as the user simply stopping mid-drag.
|
|
13
17
|
function handleTouchMove(e) {
|
|
14
18
|
move(e.touches[0].clientX);
|
|
15
19
|
}
|
|
@@ -31,5 +31,5 @@ export function BlogFeaturedCard({ post, href, labels = {}, className = "", sele
|
|
|
31
31
|
day: "numeric",
|
|
32
32
|
})
|
|
33
33
|
: "";
|
|
34
|
-
return (_jsxs(Article, { rounded: "xl", shadow: "hover-md", className: `group relative h-full overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] ${isSelected ? "ring-2 ring-primary outline outline-2 outline-primary" : ""} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(post.id, !isSelected); }, label: isSelected ? "Deselect post" : "Select post", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(TextLink, { href: href, layout: "flex-col", className: "h-full", children: [_jsx(Div, { className: `relative aspect-video ${__O.hidden} flex-shrink-0`, children: coverImageUrl ? (_jsx(MediaImage, { src: coverImageUrl, alt: safeTitle, size: "card", className: "transition-transform duration-300 group-hover:scale-105" })) : (_jsx(Div, { role: "img", "aria-label": safeTitle, surface: "muted", className: "h-full w-full" })) }), _jsxs(Stack, { className: `flex-1 ${__P.p4}`, children: [_jsxs(Row, { gap: "xs", className: "mb-2", wrap: true, children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-block capitalize ${CATEGORY_BADGE[post.category] ?? "bg-[var(--appkit-color-bg)] text-[var(--appkit-color-text-muted)]"}`, rounded: "full", padding: "pill-xs", children: post.category }), post.isFeatured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_FEATURED_BADGE, children: labels.featuredBadge ?? "Featured" }))] }), _jsx(Heading, { level: 3, className: `leading-snug mb-1`, color: "primary", truncate: 2, size: "base", weight: "semibold", children: safeTitle }), post.excerpt && (_jsx(Text, { className: `mb-3`, color: "muted", truncate: 2, size: "sm", children: post.excerpt })), _jsxs(Row, { color: "muted", textSize: "xs", className: "mt-auto", gap: "3", wrap: true, children: [post.authorName && (_jsx(Span, { children: safeDisplayName(post.authorName, "Author") })), post.readTimeMinutes != null && (_jsxs(Span, { children: [post.readTimeMinutes, " ", labels.readTime ?? "min read"] })), date && _jsx(Span, { children: date })] })] })] })] }));
|
|
34
|
+
return (_jsxs(Article, { rounded: "xl", shadow: "hover-md", className: `group relative h-full overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] ${isSelected ? "ring-2 ring-primary outline outline-2 outline-primary" : ""} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: onSelect && !isSelected ? longPress.onTouchCancel : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(post.id, !isSelected); }, label: isSelected ? "Deselect post" : "Select post", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(TextLink, { href: href, layout: "flex-col", className: "h-full", children: [_jsx(Div, { className: `relative aspect-video ${__O.hidden} flex-shrink-0`, children: coverImageUrl ? (_jsx(MediaImage, { src: coverImageUrl, alt: safeTitle, size: "card", className: "transition-transform duration-300 group-hover:scale-105" })) : (_jsx(Div, { role: "img", "aria-label": safeTitle, surface: "muted", className: "h-full w-full" })) }), _jsxs(Stack, { className: `flex-1 ${__P.p4}`, children: [_jsxs(Row, { gap: "xs", className: "mb-2", wrap: true, children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-block capitalize ${CATEGORY_BADGE[post.category] ?? "bg-[var(--appkit-color-bg)] text-[var(--appkit-color-text-muted)]"}`, rounded: "full", padding: "pill-xs", children: post.category }), post.isFeatured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_FEATURED_BADGE, children: labels.featuredBadge ?? "Featured" }))] }), _jsx(Heading, { level: 3, className: `leading-snug mb-1`, color: "primary", truncate: 2, size: "base", weight: "semibold", children: safeTitle }), post.excerpt && (_jsx(Text, { className: `mb-3`, color: "muted", truncate: 2, size: "sm", children: post.excerpt })), _jsxs(Row, { color: "muted", textSize: "xs", className: "mt-auto", gap: "3", wrap: true, children: [post.authorName && (_jsx(Span, { children: safeDisplayName(post.authorName, "Author") })), post.readTimeMinutes != null && (_jsxs(Span, { children: [post.readTimeMinutes, " ", labels.readTime ?? "min read"] })), date && _jsx(Span, { children: date })] })] })] })] }));
|
|
35
35
|
}
|
|
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
* `bundleQueryResolvedAt` timestamp. This editor only writes the rule.
|
|
15
15
|
*/
|
|
16
16
|
import { useCallback } from "react";
|
|
17
|
-
import { Div, Input, Row, Select, Stack, Text } from "../../../ui";
|
|
17
|
+
import { Div, Input, Row, Select, Stack, TagInput, Text } from "../../../ui";
|
|
18
18
|
import { CategoryInlineSelect } from "../../seller/components/CategoryInlineSelect";
|
|
19
19
|
import { BrandInlineSelect } from "../../seller/components/BrandInlineSelect";
|
|
20
20
|
import { BUNDLE_MAX_ITEMS } from "../../../_internal/shared/features/categories/bundle-config";
|
|
@@ -40,14 +40,7 @@ export function BundleDynamicRuleEditor({ value, onChange, disabled = false, })
|
|
|
40
40
|
const rule = value ?? DEFAULT_DYNAMIC_RULE;
|
|
41
41
|
const patch = useCallback((next) => onChange({ ...rule, ...next }), [rule, onChange]);
|
|
42
42
|
const patchFilter = useCallback((next) => onChange({ ...rule, filter: { ...rule.filter, ...next } }), [rule, onChange]);
|
|
43
|
-
|
|
44
|
-
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.title }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.dynamic.hint })] }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.categorySlug }), _jsx(CategoryInlineSelect, { value: rule.filter.categorySlug ?? "", onChange: (id) => patchFilter({ categorySlug: id || undefined }), disabled: disabled, placeholder: "Filter by category\u2026" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.brandSlug }), _jsx(BrandInlineSelect, { value: rule.filter.brandSlug ?? "", onChange: (id) => patchFilter({ brandSlug: id || undefined }), disabled: disabled, placeholder: "Filter by brand\u2026" })] })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.tags }), _jsx(Input, { type: "text", value: tagsValue, onChange: (e) => {
|
|
45
|
-
const parsed = e.target.value
|
|
46
|
-
.split(",")
|
|
47
|
-
.map((t) => t.trim())
|
|
48
|
-
.filter(Boolean);
|
|
49
|
-
patchFilter({ tags: parsed.length ? parsed : undefined });
|
|
50
|
-
}, placeholder: "pokemon, starter-deck", disabled: disabled })] }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-3", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.listingType }), _jsx(Select, { options: LISTING_TYPE_OPTIONS, value: rule.filter.listingType ?? "", onValueChange: (next) => patchFilter({
|
|
43
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.title }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.dynamic.hint })] }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.categorySlug }), _jsx(CategoryInlineSelect, { value: rule.filter.categorySlug ?? "", onChange: (id) => patchFilter({ categorySlug: id || undefined }), disabled: disabled, placeholder: "Filter by category\u2026" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.brandSlug }), _jsx(BrandInlineSelect, { value: rule.filter.brandSlug ?? "", onChange: (id) => patchFilter({ brandSlug: id || undefined }), disabled: disabled, placeholder: "Filter by brand\u2026" })] })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.tags }), _jsx(TagInput, { value: rule.filter.tags ?? [], onChange: (tags) => patchFilter({ tags: tags.length ? tags : undefined }), placeholder: "pokemon, starter-deck", disabled: disabled })] }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-3", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.listingType }), _jsx(Select, { options: LISTING_TYPE_OPTIONS, value: rule.filter.listingType ?? "", onValueChange: (next) => patchFilter({
|
|
51
44
|
listingType: next === "" ? undefined : next,
|
|
52
45
|
}), disabled: disabled, "aria-label": BUNDLE_COPY.adminEditor.dynamic.fields.listingType })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", children: BUNDLE_COPY.adminEditor.dynamic.fields.orderBy }), _jsx(Select, { options: ORDER_BY_OPTIONS.map((o) => ({
|
|
53
46
|
label: o.label,
|