@mohasinac/appkit 4.18.0 → 4.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +3 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.js +3 -1
- package/dist/_internal/shared/features/lottery/config-form.d.ts +58 -0
- package/dist/_internal/shared/features/lottery/config-form.js +71 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +3 -0
- package/dist/features/account/schemas/index.d.ts +23 -0
- package/dist/features/account/schemas/index.js +21 -0
- package/dist/features/admin/components/AdminSiteSettingsView.js +8 -6
- package/dist/features/admin/components/AdminUserEditorView.js +14 -12
- package/dist/features/admin/schemas/admin-user-form.d.ts +82 -0
- package/dist/features/admin/schemas/admin-user-form.js +102 -0
- package/dist/features/admin/schemas/site-settings-form.d.ts +309 -0
- package/dist/features/admin/schemas/site-settings-form.js +159 -0
- package/dist/features/auctions/components/PlaceBidFormClient.js +3 -2
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +4 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/schemas/registry.d.ts +682 -0
- package/dist/schemas/registry.js +22 -0
- package/package.json +1 -1
|
@@ -4,6 +4,8 @@ import { useState, useTransition } from "react";
|
|
|
4
4
|
import { normalizeError } from "../../../../errors/normalize";
|
|
5
5
|
import { Stack, Row, Text, Heading, Section, Container, Button, DataTable, } from "../../../../ui";
|
|
6
6
|
import { Form, FieldInput, FieldSelect, Input } from "../../../../ui";
|
|
7
|
+
import { lotteryConfigFormSchema } from "../../../shared/features/lottery/config-form";
|
|
8
|
+
import { FormErrorSummary } from "../../../../ui/forms/FormErrorSummary";
|
|
7
9
|
export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
|
|
8
10
|
const [isPending, startTransition] = useTransition();
|
|
9
11
|
const [error, setError] = useState(null);
|
|
@@ -85,7 +87,7 @@ export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
|
|
|
85
87
|
: []),
|
|
86
88
|
];
|
|
87
89
|
const slotsWithIdx = slots.map((s, i) => ({ ...s, _idx: i }));
|
|
88
|
-
return (_jsx(Container, { children: _jsx(Section, { children: _jsx(Form, { onSubmit: (e) => e.preventDefault(), className: "space-y-6", children: ({ clearErrors }) => (_jsxs(Stack, { gap: "xl", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "bold", children: eventId ? "Edit Lottery" : "Create Lottery" }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: "Basic Info" }), _jsx(FieldInput, { name: "title", label: "Title", required: true, value: title, onChange: (v) => { setTitle(v); clearErrors(); } }), _jsx(FieldInput, { name: "description", label: "Description", value: description, onChange: (v) => { setDescription(v); clearErrors(); } })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: "Configuration" }), _jsx(FieldSelect, { name: "pricingMode", label: "Pricing Mode", value: pricingMode, options: [
|
|
90
|
+
return (_jsx(Container, { children: _jsx(Section, { children: _jsx(Form, { schema: lotteryConfigFormSchema, onSubmit: (e) => e.preventDefault(), className: "space-y-6", children: ({ clearErrors }) => (_jsxs(Stack, { gap: "xl", children: [_jsx(FormErrorSummary, {}), _jsx(Heading, { level: 1, size: "2xl", weight: "bold", children: eventId ? "Edit Lottery" : "Create Lottery" }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: "Basic Info" }), _jsx(FieldInput, { name: "title", label: "Title", required: true, value: title, onChange: (v) => { setTitle(v); clearErrors(); } }), _jsx(FieldInput, { name: "description", label: "Description", value: description, onChange: (v) => { setDescription(v); clearErrors(); } })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: "Configuration" }), _jsx(FieldSelect, { name: "pricingMode", label: "Pricing Mode", value: pricingMode, options: [
|
|
89
91
|
{ label: "Uniform (all slots same price)", value: "uniform" },
|
|
90
92
|
{ label: "Variable (per-slot pricing)", value: "variable" },
|
|
91
93
|
], onChange: (v) => setPricingMode(v) }), pricingMode === "uniform" && (_jsx(FieldInput, { name: "uniformPrice", label: "Price per Slot (\u20B9)", type: "number", value: uniformPrice, onChange: (v) => setUniformPrice(v) })), _jsx(FieldInput, { name: "drawWindowMinutes", label: "Draw Window (minutes)", type: "number", value: drawWindowMinutes, onChange: (v) => setDrawWindowMinutes(v) }), _jsx(FieldInput, { name: "maxPullsPerTx", label: "Max Pulls per Transaction", type: "number", value: maxPullsPerTx, onChange: (v) => setMaxPullsPerTx(v) }), _jsx(FieldInput, { name: "maxPullsPerUser", label: "Max Pulls per User", type: "number", value: maxPullsPerUser, onChange: (v) => setMaxPullsPerUser(v) })] }), _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Heading, { level: 2, size: "lg", weight: "semibold", children: ["Slots (", slots.length, ")"] }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: addSlot, disabled: slots.length >= 200, children: "+ Add Slot" })] }), _jsx(DataTable, { data: slotsWithIdx, columns: slotColumns, keyExtractor: (s) => String(s.slotNumber), emptyMessage: "No slots yet.", actions: (s) => slots.length > 1 ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeSlot(s._idx), "aria-label": `Remove slot ${s.slotNumber}`, children: "\u00D7" })) : null })] }), error && _jsx(Text, { className: "text-error", size: "sm", role: "alert", children: error }), _jsx(Button, { type: "submit", variant: "primary", isLoading: isPending, onClick: handleSubmit, className: "w-full sm:w-auto", children: isPending ? "Saving…" : eventId ? "Update Lottery" : "Create Lottery" })] })) }) }) }));
|
|
@@ -5,6 +5,8 @@ import { Stack, Row, Text, Heading, Span, } from "../../../../ui";
|
|
|
5
5
|
import { Form, FieldInput, Button } from "../../../../ui";
|
|
6
6
|
import { useApiMutation } from "../../../../client/api/useApiMutation";
|
|
7
7
|
import { apiClient } from "../../../../http";
|
|
8
|
+
import { lotteryPullSchema } from "../../../../features/admin/schemas/admin-user-form";
|
|
9
|
+
import { FormErrorSummary } from "../../../../ui/forms/FormErrorSummary";
|
|
8
10
|
/**
|
|
9
11
|
* User-facing lottery pull form.
|
|
10
12
|
* Submits TX ID + phone + payment time + item number.
|
|
@@ -39,7 +41,7 @@ export function LotteryPullForm({ sourceType, eventId, productId, totalSlots, ma
|
|
|
39
41
|
if (result) {
|
|
40
42
|
return (_jsxs(Stack, { gap: "sm", padding: "md", rounded: "2xl", surface: "success-surface", className: "border border-success/20", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { layout: "inline-flex", color: "inverse", weight: "semibold", className: "bg-success text-[11px] tracking-wide", padding: "pill-sm", rounded: "full", transform: "uppercase", children: "Entry Confirmed" }), _jsxs(Heading, { level: 2, className: "text-success", size: "lg", weight: "bold", children: ["You're #", result.userLotteryNumber, "!"] })] }), _jsxs(Text, { className: "text-success", size: "sm", children: ["You've been assigned", " ", _jsxs(Span, { weight: "bold", children: ["Slot #", result.assignedPrizeSlotNumber] }), result.slotName ? ` — ${result.slotName}` : "", ". Good luck!"] })] }));
|
|
41
43
|
}
|
|
42
|
-
return (_jsx(Form, { onSubmit: (e) => e.preventDefault(), className: "space-y-4", children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, size: "lg", weight: "semibold", children: "Submit Your Entry" }), _jsxs(Text, { size: "sm", color: "muted", children: ["Enter your UPI Transaction ID and payment details to claim your slot.", " ", "You may enter up to ", maxPullsPerUser, " time", maxPullsPerUser === 1 ? "" : "s", "."] }), _jsx(FieldInput, { name: "transactionId", label: "Transaction ID (UPI Ref / Payment Ref)", placeholder: "e.g. 123456789012", required: true, value: transactionId, onChange: (v) => { setTransactionId(v); clearErrors(); } }), _jsx(FieldInput, { name: "paymentTime", label: "Payment Date & Time", type: "datetime-local", required: true, value: paymentTime, onChange: (v) => { setPaymentTime(v); clearErrors(); } }), _jsx(FieldInput, { name: "itemNumber", label: `Slot Number (1 – ${totalSlots})`, type: "number", placeholder: `Enter a number between 1 and ${totalSlots}`, required: true, value: itemNumber, onChange: (v) => { setItemNumber(v); clearErrors(); } }), _jsx(FieldInput, { name: "userPhone", label: "Your Phone Number", type: "tel", placeholder: "e.g. 9876543210", required: true, value: userPhone, onChange: (v) => { setUserPhone(v); clearErrors(); } }), _jsx(Button, { type: "submit", variant: "primary", isLoading: mutation.isPending, onClick: () => {
|
|
44
|
+
return (_jsx(Form, { schema: lotteryPullSchema, onSubmit: (e) => e.preventDefault(), className: "space-y-4", children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(FormErrorSummary, {}), _jsx(Heading, { level: 3, size: "lg", weight: "semibold", children: "Submit Your Entry" }), _jsxs(Text, { size: "sm", color: "muted", children: ["Enter your UPI Transaction ID and payment details to claim your slot.", " ", "You may enter up to ", maxPullsPerUser, " time", maxPullsPerUser === 1 ? "" : "s", "."] }), _jsx(FieldInput, { name: "transactionId", label: "Transaction ID (UPI Ref / Payment Ref)", placeholder: "e.g. 123456789012", required: true, value: transactionId, onChange: (v) => { setTransactionId(v); clearErrors(); } }), _jsx(FieldInput, { name: "paymentTime", label: "Payment Date & Time", type: "datetime-local", required: true, value: paymentTime, onChange: (v) => { setPaymentTime(v); clearErrors(); } }), _jsx(FieldInput, { name: "itemNumber", label: `Slot Number (1 – ${totalSlots})`, type: "number", placeholder: `Enter a number between 1 and ${totalSlots}`, required: true, value: itemNumber, onChange: (v) => { setItemNumber(v); clearErrors(); } }), _jsx(FieldInput, { name: "userPhone", label: "Your Phone Number", type: "tel", placeholder: "e.g. 9876543210", required: true, value: userPhone, onChange: (v) => { setUserPhone(v); clearErrors(); } }), _jsx(Button, { type: "submit", variant: "primary", isLoading: mutation.isPending, onClick: () => {
|
|
43
45
|
clearErrors();
|
|
44
46
|
const num = parseInt(itemNumber, 10);
|
|
45
47
|
let valid = true;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const lotteryConfigFormSchema: z.ZodEffects<z.ZodObject<{
|
|
3
|
+
pricingMode: z.ZodEnum<["uniform", "variable"]>;
|
|
4
|
+
uniformPrice: z.ZodOptional<z.ZodNumber>;
|
|
5
|
+
slots: z.ZodArray<z.ZodObject<{
|
|
6
|
+
slotNumber: z.ZodNumber;
|
|
7
|
+
name: z.ZodOptional<z.ZodString>;
|
|
8
|
+
price: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
image: z.ZodOptional<z.ZodString>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
slotNumber: number;
|
|
12
|
+
image?: string | undefined;
|
|
13
|
+
name?: string | undefined;
|
|
14
|
+
price?: number | undefined;
|
|
15
|
+
}, {
|
|
16
|
+
slotNumber: number;
|
|
17
|
+
image?: string | undefined;
|
|
18
|
+
name?: string | undefined;
|
|
19
|
+
price?: number | undefined;
|
|
20
|
+
}>, "many">;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
slots: {
|
|
23
|
+
slotNumber: number;
|
|
24
|
+
image?: string | undefined;
|
|
25
|
+
name?: string | undefined;
|
|
26
|
+
price?: number | undefined;
|
|
27
|
+
}[];
|
|
28
|
+
pricingMode: "uniform" | "variable";
|
|
29
|
+
uniformPrice?: number | undefined;
|
|
30
|
+
}, {
|
|
31
|
+
slots: {
|
|
32
|
+
slotNumber: number;
|
|
33
|
+
image?: string | undefined;
|
|
34
|
+
name?: string | undefined;
|
|
35
|
+
price?: number | undefined;
|
|
36
|
+
}[];
|
|
37
|
+
pricingMode: "uniform" | "variable";
|
|
38
|
+
uniformPrice?: number | undefined;
|
|
39
|
+
}>, {
|
|
40
|
+
slots: {
|
|
41
|
+
slotNumber: number;
|
|
42
|
+
image?: string | undefined;
|
|
43
|
+
name?: string | undefined;
|
|
44
|
+
price?: number | undefined;
|
|
45
|
+
}[];
|
|
46
|
+
pricingMode: "uniform" | "variable";
|
|
47
|
+
uniformPrice?: number | undefined;
|
|
48
|
+
}, {
|
|
49
|
+
slots: {
|
|
50
|
+
slotNumber: number;
|
|
51
|
+
image?: string | undefined;
|
|
52
|
+
name?: string | undefined;
|
|
53
|
+
price?: number | undefined;
|
|
54
|
+
}[];
|
|
55
|
+
pricingMode: "uniform" | "variable";
|
|
56
|
+
uniformPrice?: number | undefined;
|
|
57
|
+
}>;
|
|
58
|
+
export type LotteryConfigFormValues = z.infer<typeof lotteryConfigFormSchema>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: `LotteryAdminEditView` rendered `<Form>` with no schema. A lottery
|
|
3
|
+
* config is priced money: `uniformPrice` was
|
|
4
|
+
* `Math.round(parseFloat(x) * 100) / 100 || 0`, which turns any
|
|
5
|
+
* unparseable input into **zero** rather than an error — so a typo in the
|
|
6
|
+
* price box published a free lottery.
|
|
7
|
+
*
|
|
8
|
+
* `slots` is the other half: each row carries a `slotNumber`, a name and
|
|
9
|
+
* a price, and a duplicate slot number makes two buyers think they own
|
|
10
|
+
* the same slot.
|
|
11
|
+
*
|
|
12
|
+
* WHAT: The config contract.
|
|
13
|
+
*
|
|
14
|
+
* @tag domain:lottery
|
|
15
|
+
* @tag layer:schema
|
|
16
|
+
* @tag pattern:none
|
|
17
|
+
* @tag access:isomorphic
|
|
18
|
+
* @tag consumers:LotteryAdminEditView
|
|
19
|
+
* @tag sideEffects:none
|
|
20
|
+
*/
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { annotate } from "../../../../features/shell/field-ui-meta";
|
|
23
|
+
const slotSchema = z.object({
|
|
24
|
+
slotNumber: z.coerce.number().int().min(1, "Slot numbers start at 1."),
|
|
25
|
+
name: z.string().trim().max(120).optional(),
|
|
26
|
+
price: z.coerce.number().min(0, "A slot price cannot be negative.").optional(),
|
|
27
|
+
image: z.string().trim().optional(),
|
|
28
|
+
});
|
|
29
|
+
export const lotteryConfigFormSchema = z
|
|
30
|
+
.object({
|
|
31
|
+
pricingMode: annotate(z.enum(["uniform", "variable"]), {
|
|
32
|
+
section: "pricing", sectionLabel: "Pricing", sectionRequired: true, quick: true, order: 1, row: "pair",
|
|
33
|
+
}),
|
|
34
|
+
/*
|
|
35
|
+
* 🛑 Coerced with a real error, NOT `|| 0`. The previous
|
|
36
|
+
* `parseFloat(x) || 0` silently turned "12,00" or "twelve" into a free
|
|
37
|
+
* lottery — the failure was indistinguishable from deliberately pricing
|
|
38
|
+
* it at zero.
|
|
39
|
+
*/
|
|
40
|
+
uniformPrice: annotate(z.coerce
|
|
41
|
+
.number({ invalid_type_error: "Enter the slot price as a number." })
|
|
42
|
+
.min(0, "A price cannot be negative.")
|
|
43
|
+
.max(10000000, "That looks like a typo.")
|
|
44
|
+
.optional(), { section: "pricing", order: 2, row: "pair", kind: "number" }),
|
|
45
|
+
slots: annotate(z.array(slotSchema).min(1, "A lottery needs at least one slot.").max(1000), {
|
|
46
|
+
section: "slots", sectionLabel: "Slots", sectionRequired: true, order: 1, row: "full", kind: "list",
|
|
47
|
+
}),
|
|
48
|
+
})
|
|
49
|
+
.superRefine((v, ctx) => {
|
|
50
|
+
// Uniform pricing with no price is the free-lottery case the `|| 0` used
|
|
51
|
+
// to create silently.
|
|
52
|
+
if (v.pricingMode === "uniform" && (v.uniformPrice === undefined || v.uniformPrice <= 0)) {
|
|
53
|
+
ctx.addIssue({
|
|
54
|
+
code: z.ZodIssueCode.custom,
|
|
55
|
+
path: ["uniformPrice"],
|
|
56
|
+
message: "Set a slot price above zero, or switch to variable pricing.",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Two buyers cannot own the same slot number.
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
for (const [i, s] of v.slots.entries()) {
|
|
62
|
+
if (seen.has(s.slotNumber)) {
|
|
63
|
+
ctx.addIssue({
|
|
64
|
+
code: z.ZodIssueCode.custom,
|
|
65
|
+
path: ["slots", i, "slotNumber"],
|
|
66
|
+
message: `Slot ${s.slotNumber} appears more than once.`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
seen.add(s.slotNumber);
|
|
70
|
+
}
|
|
71
|
+
});
|
package/dist/client.d.ts
CHANGED
|
@@ -441,3 +441,6 @@ export { scamReportFormSchema } from "./features/scams/schemas/report-form";
|
|
|
441
441
|
export { sublistingCategoryFormSchema } from "./features/store-extensions/schemas/sublisting-category-form";
|
|
442
442
|
export { adminAddressFormSchema, adminStoreUpdateSchema, announcementBarSchema, } from "./features/admin/schemas/admin-editor-forms";
|
|
443
443
|
export { adminOrderUpdateSchema, employeeInviteSchema, hardBanReasonSchema, featureFlagsUpdateSchema, adminAdFormSchema, } from "./features/admin/schemas/admin-ops-forms";
|
|
444
|
+
export { changeEmailSchema } from "./features/account/schemas/index";
|
|
445
|
+
export { adminUserUpdateSchema, whatsappSettingsSchema, lotteryPullSchema, } from "./features/admin/schemas/admin-user-form";
|
|
446
|
+
export { siteSettingsFormSchema } from "./features/admin/schemas/site-settings-form";
|
package/dist/client.js
CHANGED
|
@@ -427,3 +427,6 @@ export { scamReportFormSchema } from "./features/scams/schemas/report-form";
|
|
|
427
427
|
export { sublistingCategoryFormSchema } from "./features/store-extensions/schemas/sublisting-category-form";
|
|
428
428
|
export { adminAddressFormSchema, adminStoreUpdateSchema, announcementBarSchema, } from "./features/admin/schemas/admin-editor-forms";
|
|
429
429
|
export { adminOrderUpdateSchema, employeeInviteSchema, hardBanReasonSchema, featureFlagsUpdateSchema, adminAdFormSchema, } from "./features/admin/schemas/admin-ops-forms";
|
|
430
|
+
export { changeEmailSchema } from "./features/account/schemas/index";
|
|
431
|
+
export { adminUserUpdateSchema, whatsappSettingsSchema, lotteryPullSchema, } from "./features/admin/schemas/admin-user-form";
|
|
432
|
+
export { siteSettingsFormSchema } from "./features/admin/schemas/site-settings-form";
|
|
@@ -205,3 +205,26 @@ export declare const updateProfileSchema: z.ZodObject<{
|
|
|
205
205
|
phoneNumber?: string | undefined;
|
|
206
206
|
profileIsPublic?: boolean | undefined;
|
|
207
207
|
}>;
|
|
208
|
+
/**
|
|
209
|
+
* Changing the account email.
|
|
210
|
+
*
|
|
211
|
+
* Both fields were bare `<Input required>` on `/user/settings` — an HTML
|
|
212
|
+
* attribute any programmatic submit bypasses — on a form that changes the
|
|
213
|
+
* address every future sign-in and password reset goes to.
|
|
214
|
+
*
|
|
215
|
+
* The current password is required because re-authentication is what stops a
|
|
216
|
+
* stolen session cookie from redirecting an account to an attacker's inbox;
|
|
217
|
+
* the length floor is deliberately low, since it is checked against a stored
|
|
218
|
+
* credential, not chosen here.
|
|
219
|
+
*/
|
|
220
|
+
export declare const changeEmailSchema: z.ZodObject<{
|
|
221
|
+
newEmail: z.ZodString;
|
|
222
|
+
emailPassword: z.ZodString;
|
|
223
|
+
}, "strip", z.ZodTypeAny, {
|
|
224
|
+
newEmail: string;
|
|
225
|
+
emailPassword: string;
|
|
226
|
+
}, {
|
|
227
|
+
newEmail: string;
|
|
228
|
+
emailPassword: string;
|
|
229
|
+
}>;
|
|
230
|
+
export type ChangeEmailValues = z.infer<typeof changeEmailSchema>;
|
|
@@ -56,3 +56,24 @@ export const updateProfileSchema = z.object({
|
|
|
56
56
|
bio: z.string().max(500, "Bio must be 500 characters or fewer").optional(),
|
|
57
57
|
profileIsPublic: z.boolean().optional(),
|
|
58
58
|
});
|
|
59
|
+
/**
|
|
60
|
+
* Changing the account email.
|
|
61
|
+
*
|
|
62
|
+
* Both fields were bare `<Input required>` on `/user/settings` — an HTML
|
|
63
|
+
* attribute any programmatic submit bypasses — on a form that changes the
|
|
64
|
+
* address every future sign-in and password reset goes to.
|
|
65
|
+
*
|
|
66
|
+
* The current password is required because re-authentication is what stops a
|
|
67
|
+
* stolen session cookie from redirecting an account to an attacker's inbox;
|
|
68
|
+
* the length floor is deliberately low, since it is checked against a stored
|
|
69
|
+
* credential, not chosen here.
|
|
70
|
+
*/
|
|
71
|
+
export const changeEmailSchema = z.object({
|
|
72
|
+
newEmail: z
|
|
73
|
+
.string()
|
|
74
|
+
.trim()
|
|
75
|
+
.toLowerCase()
|
|
76
|
+
.min(1, "Enter the new email address.")
|
|
77
|
+
.email("That doesn't look like an email address."),
|
|
78
|
+
emailPassword: z.string().min(1, "Enter your current password to confirm."),
|
|
79
|
+
});
|
|
@@ -15,6 +15,8 @@ import { NOTIFICATION_TYPE_TABS } from "../../../constants/notification-types";
|
|
|
15
15
|
import { ThemeManagerView, } from "../../site-settings/components/ThemeManagerView";
|
|
16
16
|
import { DEFAULT_LIGHT_THEME, DEFAULT_DARK_THEME } from "../../../tokens/themes";
|
|
17
17
|
import { DEFAULT_AUCTION_BID_INCREMENT_TIERS, } from "../../../_internal/shared/features/auctions/config";
|
|
18
|
+
import { siteSettingsFormSchema } from "../schemas/site-settings-form";
|
|
19
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
18
20
|
const __O = {
|
|
19
21
|
hidden: "overflow-hidden",
|
|
20
22
|
};
|
|
@@ -41,7 +43,7 @@ const PRIORITY_OPTIONS = [
|
|
|
41
43
|
* into the same combined payload, so one save covers whichever tabs were edited
|
|
42
44
|
* regardless of which one is currently active. */
|
|
43
45
|
function SaveAllBar({ isPending, onSave }) {
|
|
44
|
-
return (_jsx(FormActions, { align: "right", children: _jsx(Button, { type: "button", isLoading: isPending, disabled: isPending, onClick: onSave, children: isPending ? "Saving…" : "Save all changes" }) }));
|
|
46
|
+
return (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(FormActions, { align: "right", children: _jsx(Button, { type: "button", isLoading: isPending, disabled: isPending, onClick: onSave, children: isPending ? "Saving…" : "Save all changes" }) })] }));
|
|
45
47
|
}
|
|
46
48
|
// --- Component ---------------------------------------------------------------
|
|
47
49
|
export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
@@ -669,7 +671,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
669
671
|
["emi", "⑯ EMI"],
|
|
670
672
|
["gst", "⑰ GST"],
|
|
671
673
|
["featureflags", "⑱ Feature Flags"],
|
|
672
|
-
].map(([value, label]) => (_jsx(TabsTrigger, { value: value, children: label }, value))) }), _jsx(TabsContent, { value: "about", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Override the About page content. 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 })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: '"How it works" section title', value: aboutHowItWorksTitle, onChange: (e) => setAboutHowItWorksTitle(e.target.value), placeholder: "How It Works" }), aboutHowItems.map((item, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Step ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutHowItems(aboutHowItems.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(Input, { label: "Icon (emoji)", value: item.icon, onChange: (e) => { const next = [...aboutHowItems]; next[i] = { ...item, icon: e.target.value }; setAboutHowItems(next); }, placeholder: "\uD83D\uDED2" }), _jsx(Input, { label: "Title", value: item.title, onChange: (e) => { const next = [...aboutHowItems]; next[i] = { ...item, title: e.target.value }; setAboutHowItems(next); } }), _jsx(Textarea, { value: item.text, onChange: (e) => { const next = [...aboutHowItems]; next[i] = { ...item, text: e.target.value }; setAboutHowItems(next); }, rows: 2 })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutHowItems([...aboutHowItems, { title: "", text: "", icon: "✨", tone: "indigo" }]), children: "+ Add step" })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: "Values section title", value: aboutValuesTitle, onChange: (e) => setAboutValuesTitle(e.target.value), placeholder: "Our Values" }), _jsx(Input, { label: "Values section subtitle", value: aboutValuesSubtitle, onChange: (e) => setAboutValuesSubtitle(e.target.value), placeholder: "Optional intro line under the heading" }), aboutValueItems.map((item, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Value ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutValueItems(aboutValueItems.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(Input, { label: "Icon (emoji)", value: item.icon, onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, icon: e.target.value }; setAboutValueItems(next); }, placeholder: "\uD83D\uDEE1\uFE0F" }), _jsx(Input, { label: "Title", value: item.title, onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, title: e.target.value }; setAboutValueItems(next); } }), _jsx(Textarea, { label: "Text", value: item.text, onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, text: e.target.value }; setAboutValueItems(next); }, rows: 2 }), _jsx(Textarea, { label: "Detail (optional)", value: item.detail ?? "", onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, detail: e.target.value }; setAboutValueItems(next); }, rows: 2, placeholder: "How this value is actually enforced" })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutValueItems([...aboutValueItems, { title: "", text: "", icon: "✨" }]), children: "+ Add value" })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: "Milestones section title", value: aboutMilestonesTitle, onChange: (e) => setAboutMilestonesTitle(e.target.value), placeholder: "Our Journey" }), aboutMilestones.map((item, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Milestone ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutMilestones(aboutMilestones.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(Input, { label: "Year", value: item.year, onChange: (e) => { const next = [...aboutMilestones]; next[i] = { ...item, year: e.target.value }; setAboutMilestones(next); }, placeholder: "2024" }), _jsx(Textarea, { value: item.text, onChange: (e) => { const next = [...aboutMilestones]; next[i] = { ...item, text: e.target.value }; setAboutMilestones(next); }, rows: 2 })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutMilestones([...aboutMilestones, { year: "", text: "" }]), children: "+ Add milestone" })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: "Team section title", value: aboutTeamTitle, onChange: (e) => setAboutTeamTitle(e.target.value), placeholder: "Meet the Team" }), _jsx(Input, { label: "Team section subtitle", value: aboutTeamSubtitle, onChange: (e) => setAboutTeamSubtitle(e.target.value), placeholder: "The people building LetItRip" }), aboutTeamMembers.map((member, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Team member ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutTeamMembers(aboutTeamMembers.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(ImageUpload, { label: "Photo", currentImage: member.photoUrl, onUpload: (file) => upload(file, "store"), onChange: (url) => { const next = [...aboutTeamMembers]; next[i] = { ...member, photoUrl: url }; setAboutTeamMembers(next); } }), _jsx(Input, { label: "Name", value: member.name, onChange: (e) => { const next = [...aboutTeamMembers]; next[i] = { ...member, name: e.target.value }; setAboutTeamMembers(next); } }), _jsx(Input, { label: "Role", value: member.role, onChange: (e) => { const next = [...aboutTeamMembers]; next[i] = { ...member, role: e.target.value }; setAboutTeamMembers(next); }, placeholder: "Founder & Developer" }), _jsx(Textarea, { value: member.bio, onChange: (e) => { const next = [...aboutTeamMembers]; next[i] = { ...member, bio: e.target.value }; setAboutTeamMembers(next); }, rows: 3 }), _jsxs(Row, { gap: "md", children: [_jsx(Toggle, { label: "Founder", checked: !!member.isFounder, onChange: (v) => { const next = [...aboutTeamMembers]; next[i] = { ...member, isFounder: v }; setAboutTeamMembers(next); } }), _jsx(Toggle, { label: "Developer", checked: !!member.isDeveloper, onChange: (v) => { const next = [...aboutTeamMembers]; next[i] = { ...member, isDeveloper: v }; setAboutTeamMembers(next); } })] })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutTeamMembers([...aboutTeamMembers, { name: "", role: "", bio: "" }]), children: "+ Add team member" })] }), _jsx(Input, { label: "CTA banner title", value: aboutCtaTitle, onChange: (e) => setAboutCtaTitle(e.target.value), placeholder: "Ready to get started?" }), _jsx(Input, { label: "Sell CTA copy", value: aboutCtaSell, onChange: (e) => setAboutCtaSell(e.target.value), placeholder: "Start selling in minutes" }), _jsx(Input, { label: "Shop CTA copy", value: aboutCtaShop, onChange: (e) => setAboutCtaShop(e.target.value), placeholder: "Browse the marketplace" })] }) }), _jsx(TabsContent, { value: "branding", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "appearance", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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: {
|
|
674
|
+
].map(([value, label]) => (_jsx(TabsTrigger, { value: value, children: label }, value))) }), _jsx(TabsContent, { value: "about", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Override the About page content. 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 })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: '"How it works" section title', value: aboutHowItWorksTitle, onChange: (e) => setAboutHowItWorksTitle(e.target.value), placeholder: "How It Works" }), aboutHowItems.map((item, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Step ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutHowItems(aboutHowItems.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(Input, { label: "Icon (emoji)", value: item.icon, onChange: (e) => { const next = [...aboutHowItems]; next[i] = { ...item, icon: e.target.value }; setAboutHowItems(next); }, placeholder: "\uD83D\uDED2" }), _jsx(Input, { label: "Title", value: item.title, onChange: (e) => { const next = [...aboutHowItems]; next[i] = { ...item, title: e.target.value }; setAboutHowItems(next); } }), _jsx(Textarea, { value: item.text, onChange: (e) => { const next = [...aboutHowItems]; next[i] = { ...item, text: e.target.value }; setAboutHowItems(next); }, rows: 2 })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutHowItems([...aboutHowItems, { title: "", text: "", icon: "✨", tone: "indigo" }]), children: "+ Add step" })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: "Values section title", value: aboutValuesTitle, onChange: (e) => setAboutValuesTitle(e.target.value), placeholder: "Our Values" }), _jsx(Input, { label: "Values section subtitle", value: aboutValuesSubtitle, onChange: (e) => setAboutValuesSubtitle(e.target.value), placeholder: "Optional intro line under the heading" }), aboutValueItems.map((item, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Value ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutValueItems(aboutValueItems.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(Input, { label: "Icon (emoji)", value: item.icon, onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, icon: e.target.value }; setAboutValueItems(next); }, placeholder: "\uD83D\uDEE1\uFE0F" }), _jsx(Input, { label: "Title", value: item.title, onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, title: e.target.value }; setAboutValueItems(next); } }), _jsx(Textarea, { label: "Text", value: item.text, onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, text: e.target.value }; setAboutValueItems(next); }, rows: 2 }), _jsx(Textarea, { label: "Detail (optional)", value: item.detail ?? "", onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, detail: e.target.value }; setAboutValueItems(next); }, rows: 2, placeholder: "How this value is actually enforced" })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutValueItems([...aboutValueItems, { title: "", text: "", icon: "✨" }]), children: "+ Add value" })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: "Milestones section title", value: aboutMilestonesTitle, onChange: (e) => setAboutMilestonesTitle(e.target.value), placeholder: "Our Journey" }), aboutMilestones.map((item, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Milestone ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutMilestones(aboutMilestones.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(Input, { label: "Year", value: item.year, onChange: (e) => { const next = [...aboutMilestones]; next[i] = { ...item, year: e.target.value }; setAboutMilestones(next); }, placeholder: "2024" }), _jsx(Textarea, { value: item.text, onChange: (e) => { const next = [...aboutMilestones]; next[i] = { ...item, text: e.target.value }; setAboutMilestones(next); }, rows: 2 })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutMilestones([...aboutMilestones, { year: "", text: "" }]), children: "+ Add milestone" })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: "Team section title", value: aboutTeamTitle, onChange: (e) => setAboutTeamTitle(e.target.value), placeholder: "Meet the Team" }), _jsx(Input, { label: "Team section subtitle", value: aboutTeamSubtitle, onChange: (e) => setAboutTeamSubtitle(e.target.value), placeholder: "The people building LetItRip" }), aboutTeamMembers.map((member, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Team member ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutTeamMembers(aboutTeamMembers.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(ImageUpload, { label: "Photo", currentImage: member.photoUrl, onUpload: (file) => upload(file, "store"), onChange: (url) => { const next = [...aboutTeamMembers]; next[i] = { ...member, photoUrl: url }; setAboutTeamMembers(next); } }), _jsx(Input, { label: "Name", value: member.name, onChange: (e) => { const next = [...aboutTeamMembers]; next[i] = { ...member, name: e.target.value }; setAboutTeamMembers(next); } }), _jsx(Input, { label: "Role", value: member.role, onChange: (e) => { const next = [...aboutTeamMembers]; next[i] = { ...member, role: e.target.value }; setAboutTeamMembers(next); }, placeholder: "Founder & Developer" }), _jsx(Textarea, { value: member.bio, onChange: (e) => { const next = [...aboutTeamMembers]; next[i] = { ...member, bio: e.target.value }; setAboutTeamMembers(next); }, rows: 3 }), _jsxs(Row, { gap: "md", children: [_jsx(Toggle, { label: "Founder", checked: !!member.isFounder, onChange: (v) => { const next = [...aboutTeamMembers]; next[i] = { ...member, isFounder: v }; setAboutTeamMembers(next); } }), _jsx(Toggle, { label: "Developer", checked: !!member.isDeveloper, onChange: (v) => { const next = [...aboutTeamMembers]; next[i] = { ...member, isDeveloper: v }; setAboutTeamMembers(next); } })] })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutTeamMembers([...aboutTeamMembers, { name: "", role: "", bio: "" }]), children: "+ Add team member" })] }), _jsx(Input, { label: "CTA banner title", value: aboutCtaTitle, onChange: (e) => setAboutCtaTitle(e.target.value), placeholder: "Ready to get started?" }), _jsx(Input, { label: "Sell CTA copy", value: aboutCtaSell, onChange: (e) => setAboutCtaSell(e.target.value), placeholder: "Start selling in minutes" }), _jsx(Input, { label: "Shop CTA copy", value: aboutCtaShop, onChange: (e) => setAboutCtaShop(e.target.value), placeholder: "Browse the marketplace" })] }) }), _jsx(TabsContent, { value: "branding", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "appearance", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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: {
|
|
673
675
|
type: lightBgType,
|
|
674
676
|
value: lightBgValue,
|
|
675
677
|
overlay: { enabled: lightBgOverlayEnabled, color: lightBgOverlayColor, opacity: lightBgOverlayOpacity / 100 },
|
|
@@ -677,23 +679,23 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
677
679
|
type: darkBgType,
|
|
678
680
|
value: darkBgValue,
|
|
679
681
|
overlay: { enabled: darkBgOverlayEnabled, color: darkBgOverlayColor, opacity: darkBgOverlayOpacity / 100 },
|
|
680
|
-
} }) })] })] })] }) }), _jsx(TabsContent, { value: "themes", children: _jsx(Form, { onSubmit: (e) => {
|
|
682
|
+
} }) })] })] })] }) }), _jsx(TabsContent, { value: "themes", children: _jsx(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => {
|
|
681
683
|
e.preventDefault();
|
|
682
684
|
saveAllMutation.mutate();
|
|
683
|
-
}, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: _jsx(ThemeManagerView, { value: themeRegistry, onChange: setThemeRegistry, previewOrigin: "/" }) }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "seo", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "contact", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "watermark", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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 }), _jsx(Select, { label: "Position", options: [
|
|
685
|
+
}, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: _jsx(ThemeManagerView, { value: themeRegistry, onChange: setThemeRegistry, previewOrigin: "/" }) }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "seo", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "contact", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "watermark", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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 }), _jsx(Select, { label: "Position", options: [
|
|
684
686
|
{ label: "Center", value: "center" },
|
|
685
687
|
{ label: "Top left", value: "top-left" },
|
|
686
688
|
{ label: "Top right", value: "top-right" },
|
|
687
689
|
{ label: "Bottom left", value: "bottom-left" },
|
|
688
690
|
{ label: "Bottom right", value: "bottom-right" },
|
|
689
|
-
], value: watermarkPosition, onValueChange: (v) => setWatermarkPosition(v), disabled: watermarkUseCustomOffset }), _jsx(Toggle, { checked: watermarkUseCustomOffset, onChange: setWatermarkUseCustomOffset, label: "Use a custom X/Y offset instead" }), watermarkUseCustomOffset && (_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "X offset (%) \u2014 + right / \u2212 left of center", value: String(watermarkOffsetX), onChange: (e) => setWatermarkOffsetX(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 }), _jsx(Input, { label: "Y offset (%) \u2014 + down / \u2212 up from center", value: String(watermarkOffsetY), onChange: (e) => setWatermarkOffsetY(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 })] })), _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(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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: "Maximum platform fee (\u20B9)", helperText: "Ceiling on the buyer's platform commission, charged once per checkout on every payment method. GST is calculated on the capped amount.", value: String(platformFeeMax), onChange: (e) => setPlatformFeeMax(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(Input, { label: "COD deposit (%)", helperText: "% of order collected upfront on COD orders; remainder paid on delivery.", value: String(codDepositPercent), onChange: (e) => setCodDepositPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 })] }), _jsx(Toggle, { checked: whatsappNotifyFeeEnabled, onChange: setWhatsappNotifyFeeEnabled, label: "Offer the WhatsApp order-updates addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "WhatsApp addon fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in. Requires WhatsApp credentials + at least one approved template \u2014 see the WhatsApp tab.", value: String(whatsappNotifyFee), onChange: (e) => setWhatsappNotifyFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !whatsappNotifyFeeEnabled }) }), _jsx(Toggle, { checked: giftWrapFeeEnabled, onChange: setGiftWrapFeeEnabled, label: "Offer the gift-wrap addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "Gift wrap fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in.", value: String(giftWrapFee), onChange: (e) => setGiftWrapFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !giftWrapFeeEnabled }) }), _jsx(Toggle, { checked: shipmentProtectionFeeEnabled, onChange: setShipmentProtectionFeeEnabled, label: "Offer the shipment-protection addon at checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Shipment protection (%)", helperText: "% of order subtotal, charged when the buyer opts in.", value: String(shipmentProtectionFeePercent), onChange: (e) => setShipmentProtectionFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1, disabled: !shipmentProtectionFeeEnabled }), _jsx(Input, { label: "Shipment protection minimum (\u20B9)", helperText: "Floor for the shipment-protection fee.", value: String(shipmentProtectionFeeMin), onChange: (e) => setShipmentProtectionFeeMin(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !shipmentProtectionFeeEnabled })] })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(laborHourlyRate), onChange: (e) => setLaborHourlyRate(Math.round((parseFloat(e.target.value) || 0) * 100) / 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(TabsContent, { value: "emi", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(emiMinOrderValue), onChange: (e) => setEmiMinOrderValue(Math.round((parseFloat(e.target.value) || 0) * 100) / 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(TabsContent, { value: "gst", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "featureflags", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "Site-wide platform feature toggles. Disabling a flag hides that feature's nav entries and pages \u2014 existing data is untouched, it just becomes unreachable until re-enabled." }), _jsx(Grid, { cols: 2, gap: "md", children: FEATURE_FLAG_META.map((flag) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Toggle, { label: `${flag.icon} ${flag.labelKey}`, checked: featureFlags[flag.key] ?? false, onChange: (v) => setFeatureFlags((prev) => ({ ...prev, [flag.key]: v })) }), _jsx(Text, { size: "xs", color: "muted", children: flag.descKey })] }, flag.key))) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Listing types" }), _jsx(Text, { size: "xs", color: "muted", children: "Disabled types are hidden from listings and reject create/add-to-cart." }), _jsx(Grid, { cols: 2, gap: "sm", children: LISTING_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: listingTypeFlags[key] ?? true, onChange: (v) => setListingTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Category types" }), _jsx(Grid, { cols: 2, gap: "sm", children: CATEGORY_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: categoryTypeFlags[key] ?? true, onChange: (v) => setCategoryTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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 Key Secret", value: razorpayKeySecret, onChange: setRazorpayKeySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Razorpay Webhook Secret", value: razorpayWebhookSecret, onChange: setRazorpayWebhookSecret, 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(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Text, { size: "xs", color: "muted", children: "Transactional email is delivered through Resend \u2014 set the Resend API key below. These SMTP fields are stored but not currently used for delivery." })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Google Places (store reviews)" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Google Maps API key", value: googleMapsApiKey, onChange: setGoogleMapsApiKey, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(Input, { label: "Default Google Place ID", value: googlePlaceId, onChange: (e) => setGooglePlaceId(e.target.value), placeholder: "ChIJ\u2026" })] })] }), _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(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(Input, { label: "High-value checkout OTP threshold (\u20B9)", helperText: "Carts at or above this subtotal require an extra OTP verification step before payment (0 = disabled).", value: String(otpCheckoutThreshold), onChange: (e) => setOtpCheckoutThreshold(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "pt-[var(--appkit-space-2)]", children: "Seller shipping charge defaults" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Seller shipping fixed (\u20B9)", helperText: "Flat shipping charge collected on the seller's behalf.", value: String(sellerShippingFixed), onChange: (e) => setSellerShippingFixed(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Platform shipping (%)", helperText: "Platform's own shipping markup, as a % of order value.", value: String(platformShippingPercent), onChange: (e) => setPlatformShippingPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Platform shipping minimum (\u20B9)", helperText: "Floor for the platform shipping markup.", value: String(platformShippingFixedMin), onChange: (e) => setPlatformShippingFixedMin(parseFloat(e.target.value) || 0), type: "number", min: 0 })] })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsx(Text, { size: "xs", weight: "medium", color: "muted", children: "Bid increment tiers" }), _jsx(Text, { size: "xs", color: "muted", children: "The minimum jump a new bid must clear, based on the current bid amount. A seller's per-listing override can require more than the matching tier, but can never undercut it." }), bidIncrementTiers.map((tier, i) => {
|
|
691
|
+
], value: watermarkPosition, onValueChange: (v) => setWatermarkPosition(v), disabled: watermarkUseCustomOffset }), _jsx(Toggle, { checked: watermarkUseCustomOffset, onChange: setWatermarkUseCustomOffset, label: "Use a custom X/Y offset instead" }), watermarkUseCustomOffset && (_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "X offset (%) \u2014 + right / \u2212 left of center", value: String(watermarkOffsetX), onChange: (e) => setWatermarkOffsetX(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 }), _jsx(Input, { label: "Y offset (%) \u2014 + down / \u2212 up from center", value: String(watermarkOffsetY), onChange: (e) => setWatermarkOffsetY(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 })] })), _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(TabsContent, { value: "fees", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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: "Maximum platform fee (\u20B9)", helperText: "Ceiling on the buyer's platform commission, charged once per checkout on every payment method. GST is calculated on the capped amount.", value: String(platformFeeMax), onChange: (e) => setPlatformFeeMax(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(Input, { label: "COD deposit (%)", helperText: "% of order collected upfront on COD orders; remainder paid on delivery.", value: String(codDepositPercent), onChange: (e) => setCodDepositPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 })] }), _jsx(Toggle, { checked: whatsappNotifyFeeEnabled, onChange: setWhatsappNotifyFeeEnabled, label: "Offer the WhatsApp order-updates addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "WhatsApp addon fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in. Requires WhatsApp credentials + at least one approved template \u2014 see the WhatsApp tab.", value: String(whatsappNotifyFee), onChange: (e) => setWhatsappNotifyFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !whatsappNotifyFeeEnabled }) }), _jsx(Toggle, { checked: giftWrapFeeEnabled, onChange: setGiftWrapFeeEnabled, label: "Offer the gift-wrap addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "Gift wrap fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in.", value: String(giftWrapFee), onChange: (e) => setGiftWrapFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !giftWrapFeeEnabled }) }), _jsx(Toggle, { checked: shipmentProtectionFeeEnabled, onChange: setShipmentProtectionFeeEnabled, label: "Offer the shipment-protection addon at checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Shipment protection (%)", helperText: "% of order subtotal, charged when the buyer opts in.", value: String(shipmentProtectionFeePercent), onChange: (e) => setShipmentProtectionFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1, disabled: !shipmentProtectionFeeEnabled }), _jsx(Input, { label: "Shipment protection minimum (\u20B9)", helperText: "Floor for the shipment-protection fee.", value: String(shipmentProtectionFeeMin), onChange: (e) => setShipmentProtectionFeeMin(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !shipmentProtectionFeeEnabled })] })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(laborHourlyRate), onChange: (e) => setLaborHourlyRate(Math.round((parseFloat(e.target.value) || 0) * 100) / 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(TabsContent, { value: "emi", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(emiMinOrderValue), onChange: (e) => setEmiMinOrderValue(Math.round((parseFloat(e.target.value) || 0) * 100) / 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(TabsContent, { value: "gst", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "featureflags", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "Site-wide platform feature toggles. Disabling a flag hides that feature's nav entries and pages \u2014 existing data is untouched, it just becomes unreachable until re-enabled." }), _jsx(Grid, { cols: 2, gap: "md", children: FEATURE_FLAG_META.map((flag) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Toggle, { label: `${flag.icon} ${flag.labelKey}`, checked: featureFlags[flag.key] ?? false, onChange: (v) => setFeatureFlags((prev) => ({ ...prev, [flag.key]: v })) }), _jsx(Text, { size: "xs", color: "muted", children: flag.descKey })] }, flag.key))) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Listing types" }), _jsx(Text, { size: "xs", color: "muted", children: "Disabled types are hidden from listings and reject create/add-to-cart." }), _jsx(Grid, { cols: 2, gap: "sm", children: LISTING_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: listingTypeFlags[key] ?? true, onChange: (v) => setListingTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Category types" }), _jsx(Grid, { cols: 2, gap: "sm", children: CATEGORY_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: categoryTypeFlags[key] ?? true, onChange: (v) => setCategoryTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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 Key Secret", value: razorpayKeySecret, onChange: setRazorpayKeySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Razorpay Webhook Secret", value: razorpayWebhookSecret, onChange: setRazorpayWebhookSecret, 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(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Text, { size: "xs", color: "muted", children: "Transactional email is delivered through Resend \u2014 set the Resend API key below. These SMTP fields are stored but not currently used for delivery." })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Google Places (store reviews)" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Google Maps API key", value: googleMapsApiKey, onChange: setGoogleMapsApiKey, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(Input, { label: "Default Google Place ID", value: googlePlaceId, onChange: (e) => setGooglePlaceId(e.target.value), placeholder: "ChIJ\u2026" })] })] }), _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(TabsContent, { value: "shipping", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(Input, { label: "High-value checkout OTP threshold (\u20B9)", helperText: "Carts at or above this subtotal require an extra OTP verification step before payment (0 = disabled).", value: String(otpCheckoutThreshold), onChange: (e) => setOtpCheckoutThreshold(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "pt-[var(--appkit-space-2)]", children: "Seller shipping charge defaults" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Seller shipping fixed (\u20B9)", helperText: "Flat shipping charge collected on the seller's behalf.", value: String(sellerShippingFixed), onChange: (e) => setSellerShippingFixed(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Platform shipping (%)", helperText: "Platform's own shipping markup, as a % of order value.", value: String(platformShippingPercent), onChange: (e) => setPlatformShippingPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Platform shipping minimum (\u20B9)", helperText: "Floor for the platform shipping markup.", value: String(platformShippingFixedMin), onChange: (e) => setPlatformShippingFixedMin(parseFloat(e.target.value) || 0), type: "number", min: 0 })] })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsx(Text, { size: "xs", weight: "medium", color: "muted", children: "Bid increment tiers" }), _jsx(Text, { size: "xs", color: "muted", children: "The minimum jump a new bid must clear, based on the current bid amount. A seller's per-listing override can require more than the matching tier, but can never undercut it." }), bidIncrementTiers.map((tier, i) => {
|
|
690
692
|
const isLast = i === bidIncrementTiers.length - 1;
|
|
691
693
|
return (_jsxs(Grid, { align: "end", gap: "xs", className: "grid-cols-12", children: [_jsx(Div, { className: "col-span-5", children: isLast ? (_jsxs(_Fragment, { children: [_jsx(Text, { size: "xs", weight: "medium", className: "mb-1", children: "Up to (\u20B9)" }), _jsx(Text, { size: "sm", color: "muted", children: "and above" })] })) : (_jsx(Input, { label: "Up to (\u20B9)", type: "number", min: 1, value: String(tier.upTo ?? ""), onChange: (e) => setBidIncrementTiers(bidIncrementTiers.map((t, j) => j === i ? { ...t, upTo: parseInt(e.target.value) || 0 } : t)) })) }), _jsx(Div, { className: "col-span-5", children: _jsx(Input, { label: "Increment (\u20B9)", type: "number", min: 1, value: String(tier.increment), onChange: (e) => setBidIncrementTiers(bidIncrementTiers.map((t, j) => j === i ? { ...t, increment: parseInt(e.target.value) || 0 } : t)) }) }), _jsx(Row, { centered: true, className: "col-span-2", padding: "b-xs", children: _jsx(Button, { variant: "ghost", type: "button", disabled: bidIncrementTiers.length <= 1, onClick: () => setBidIncrementTiers(bidIncrementTiers.filter((_, j) => j !== i)), "aria-label": "Remove tier", children: "\u00D7" }) })] }, i));
|
|
692
694
|
}), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setBidIncrementTiers([
|
|
693
695
|
...bidIncrementTiers.slice(0, -1),
|
|
694
696
|
{ upTo: 100, increment: 10 },
|
|
695
697
|
bidIncrementTiers[bidIncrementTiers.length - 1] ?? { upTo: null, increment: 1000 },
|
|
696
|
-
]), children: "+ Add tier" })] }), _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(TabsContent, { value: "limits", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(Text, { size: "xs", color: "muted", children: "Approved Meta message templates for the buyer-facing WhatsApp order-updates addon (Fees tab). Meta rejects business-initiated free-text WhatsApp messages outside a 24h customer-service window \u2014 a real template name here is required for that notification type to actually deliver. Leave blank to skip WhatsApp for that type (in-app + email still fire)." }), _jsx(Input, { label: "Template language code", value: waTemplateLanguage, onChange: (e) => setWaTemplateLanguage(e.target.value), placeholder: "en", helperText: "BCP-47 code the templates below were approved in, e.g. en or en_US." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Order placed template name", value: waTemplateOrderPlaced, onChange: (e) => setWaTemplateOrderPlaced(e.target.value), placeholder: "order_placed_update" }), _jsx(Input, { label: "Order confirmed template name", value: waTemplateOrderConfirmed, onChange: (e) => setWaTemplateOrderConfirmed(e.target.value), placeholder: "order_confirmed_update" }), _jsx(Input, { label: "Order shipped template name", value: waTemplateOrderShipped, onChange: (e) => setWaTemplateOrderShipped(e.target.value), placeholder: "order_shipped_update" }), _jsx(Input, { label: "Order delivered template name", value: waTemplateOrderDelivered, onChange: (e) => setWaTemplateOrderDelivered(e.target.value), placeholder: "order_delivered_update" }), _jsx(Input, { label: "Order cancelled template name", value: waTemplateOrderCancelled, onChange: (e) => setWaTemplateOrderCancelled(e.target.value), placeholder: "order_cancelled_update" }), _jsx(Input, { label: "Refund initiated template name", value: waTemplateRefundInitiated, onChange: (e) => setWaTemplateRefundInitiated(e.target.value), placeholder: "refund_initiated_update" })] })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.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" })] }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifEmailTypes, onChange: (values) => setNotifEmailTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Daily status digest", checked: digestEnabled, onChange: setDigestEnabled }), digestEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Text, { size: "xs", color: "muted", children: "Sent every morning at 10:00 IST with the previous 24 hours of order activity. Its arrival doubles as the platform health check \u2014 if it stops landing, something needs a look." }), _jsx(Textarea, { label: "Recipients (To)", value: digestRecipients, onChange: (e) => setDigestRecipients(e.target.value), placeholder: "support@letitrip.in\nmohasin@letitrip.in", rows: 4, helperText: "One address per line." }), _jsx(Textarea, { label: "CC recipients", value: digestCcRecipients, onChange: (e) => setDigestCcRecipients(e.target.value), placeholder: "One address per line", rows: 3, helperText: "Optional \u2014 anyone else who should receive a copy." })] }))] }), _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." }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifWhatsappTypes, onChange: (values) => setNotifWhatsappTypes(values), placeholder: "All notification types" })] }))] }), _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(TabsContent, { value: "legal", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [
|
|
698
|
+
]), children: "+ Add tier" })] }), _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(TabsContent, { value: "limits", children: _jsx(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(TabsContent, { value: "whatsapp", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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(Text, { size: "xs", color: "muted", children: "Approved Meta message templates for the buyer-facing WhatsApp order-updates addon (Fees tab). Meta rejects business-initiated free-text WhatsApp messages outside a 24h customer-service window \u2014 a real template name here is required for that notification type to actually deliver. Leave blank to skip WhatsApp for that type (in-app + email still fire)." }), _jsx(Input, { label: "Template language code", value: waTemplateLanguage, onChange: (e) => setWaTemplateLanguage(e.target.value), placeholder: "en", helperText: "BCP-47 code the templates below were approved in, e.g. en or en_US." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Order placed template name", value: waTemplateOrderPlaced, onChange: (e) => setWaTemplateOrderPlaced(e.target.value), placeholder: "order_placed_update" }), _jsx(Input, { label: "Order confirmed template name", value: waTemplateOrderConfirmed, onChange: (e) => setWaTemplateOrderConfirmed(e.target.value), placeholder: "order_confirmed_update" }), _jsx(Input, { label: "Order shipped template name", value: waTemplateOrderShipped, onChange: (e) => setWaTemplateOrderShipped(e.target.value), placeholder: "order_shipped_update" }), _jsx(Input, { label: "Order delivered template name", value: waTemplateOrderDelivered, onChange: (e) => setWaTemplateOrderDelivered(e.target.value), placeholder: "order_delivered_update" }), _jsx(Input, { label: "Order cancelled template name", value: waTemplateOrderCancelled, onChange: (e) => setWaTemplateOrderCancelled(e.target.value), placeholder: "order_cancelled_update" }), _jsx(Input, { label: "Refund initiated template name", value: waTemplateRefundInitiated, onChange: (e) => setWaTemplateRefundInitiated(e.target.value), placeholder: "refund_initiated_update" })] })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.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" })] }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifEmailTypes, onChange: (values) => setNotifEmailTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Daily status digest", checked: digestEnabled, onChange: setDigestEnabled }), digestEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Text, { size: "xs", color: "muted", children: "Sent every morning at 10:00 IST with the previous 24 hours of order activity. Its arrival doubles as the platform health check \u2014 if it stops landing, something needs a look." }), _jsx(Textarea, { label: "Recipients (To)", value: digestRecipients, onChange: (e) => setDigestRecipients(e.target.value), placeholder: "support@letitrip.in\nmohasin@letitrip.in", rows: 4, helperText: "One address per line." }), _jsx(Textarea, { label: "CC recipients", value: digestCcRecipients, onChange: (e) => setDigestCcRecipients(e.target.value), placeholder: "One address per line", rows: 3, helperText: "Optional \u2014 anyone else who should receive a copy." })] }))] }), _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." }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifWhatsappTypes, onChange: (values) => setNotifWhatsappTypes(values), placeholder: "All notification types" })] }))] }), _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(TabsContent, { value: "legal", children: _jsx(Form, { schema: siteSettingsFormSchema, onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [
|
|
697
699
|
["Terms of Service", termsHtml, setTermsHtml],
|
|
698
700
|
["Privacy Policy", privacyHtml, setPrivacyHtml],
|
|
699
701
|
["Refund Policy", refundHtml, setRefundHtml],
|