@mohasinac/appkit 2.7.50 → 2.7.52
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/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
- package/dist/client.d.ts +29 -6
- package/dist/client.js +16 -4
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/_guide-cls.d.ts +13 -0
- package/dist/features/_guide-cls.js +14 -0
- package/dist/features/about/components/HelpPageView.js +29 -26
- package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
- package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
- package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +100 -77
- package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
- package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
- package/dist/features/admin/components/AdminBundlesView.js +173 -52
- package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
- package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminContentGuideView.js +36 -0
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
- package/dist/features/admin/components/AdminGuideHubView.js +79 -0
- package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
- package/dist/features/admin/components/AdminProductsView.js +8 -3
- package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
- package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
- package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
- package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
- package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
- package/dist/features/categories/components/CategoryBundlesListing.js +3 -33
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/events/components/AdminEventEditorView.js +223 -269
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -1
- package/dist/features/products/components/AuctionsIndexListing.js +3 -1
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
- package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
- package/dist/features/products/components/ProductsIndexListing.js +3 -1
- package/dist/features/products/components/index.d.ts +2 -4
- package/dist/features/products/components/index.js +1 -2
- package/dist/features/promotions/schemas/firestore.js +1 -1
- package/dist/features/reviews/components/index.d.ts +0 -2
- package/dist/features/reviews/components/index.js +0 -1
- package/dist/features/seller/components/BrandInlineSelect.js +23 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
- package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
- package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
- package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
- package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
- package/dist/features/seller/components/SellerBundlesView.js +104 -0
- package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
- package/dist/features/seller/components/SellerClassifiedView.js +113 -0
- package/dist/features/seller/components/SellerDashboardView.js +19 -7
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
- package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
- package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
- package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
- package/dist/features/seller/components/SellerLiveView.js +113 -0
- package/dist/features/seller/components/SellerOrdersView.js +18 -2
- package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
- package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +9 -7
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -1
- package/dist/features/seller/components/SellerProductsView.js +48 -54
- package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
- package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
- package/dist/features/seller/components/SellerShippingView.js +37 -10
- package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
- package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
- package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
- package/dist/features/seller/components/SellerStorefrontView.js +26 -29
- package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
- package/dist/features/seller/components/SellerTemplatesView.js +265 -0
- package/dist/features/seller/components/index.d.ts +26 -0
- package/dist/features/seller/components/index.js +13 -0
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
- package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
- package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
- package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
- package/dist/features/stores/components/StoreGuideHubView.js +89 -0
- package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
- package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
- package/dist/features/stores/components/StoreReviewsListing.js +100 -23
- package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
- package/dist/features/stores/components/index.d.ts +12 -1
- package/dist/features/stores/components/index.js +6 -1
- package/dist/features/stores/hooks/useStores.d.ts +5 -0
- package/dist/features/stores/hooks/useStores.js +10 -0
- package/dist/features/stores/manifest.js +1 -1
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
- package/dist/index.d.ts +54 -9
- package/dist/index.js +58 -15
- package/dist/next/routing/route-map.d.ts +55 -0
- package/dist/next/routing/route-map.js +25 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
- package/dist/providers/db-firebase/sieve.d.ts +49 -0
- package/dist/providers/db-firebase/sieve.js +61 -8
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +2 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/ListingToolbar.d.ts +8 -1
- package/dist/ui/components/ListingToolbar.js +4 -2
- package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
- package/dist/ui/index.d.ts +1 -1
- package/package.json +8 -2
|
@@ -2,34 +2,13 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
5
|
-
import { Alert, Button,
|
|
5
|
+
import { Alert, Button, Div, Heading, Input, RichTextEditor, Select, StackedViewShell, TagInput, Text, Toggle, } from "../../../ui";
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
import { StepForm } from "../../shell";
|
|
9
|
+
// --- Constants ---------------------------------------------------------------
|
|
8
10
|
const CLS_PANEL_SM = "rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800 p-3 space-y-2";
|
|
9
11
|
const CLS_PANEL_LG = "rounded-xl border border-zinc-200 dark:border-slate-700 p-4 space-y-4";
|
|
10
|
-
function toLocalDatetime(iso) {
|
|
11
|
-
if (!iso)
|
|
12
|
-
return "";
|
|
13
|
-
try {
|
|
14
|
-
const d = new Date(iso);
|
|
15
|
-
const offset = d.getTimezoneOffset();
|
|
16
|
-
const local = new Date(d.getTime() - offset * 60000);
|
|
17
|
-
return local.toISOString().slice(0, 16);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return "";
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function toISOString(local) {
|
|
24
|
-
if (!local)
|
|
25
|
-
return "";
|
|
26
|
-
try {
|
|
27
|
-
return new Date(local).toISOString();
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return "";
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
12
|
const EVENT_TYPE_OPTIONS = [
|
|
34
13
|
{ label: "Sale", value: "sale" },
|
|
35
14
|
{ label: "Offer / Coupon", value: "offer" },
|
|
@@ -66,54 +45,105 @@ const FORM_FIELD_TYPE_OPTIONS = [
|
|
|
66
45
|
{ label: "File upload", value: "file" },
|
|
67
46
|
];
|
|
68
47
|
const FIELD_TYPES_WITH_OPTIONS = ["select", "multiselect", "radio", "checkbox"];
|
|
48
|
+
function FormFieldBuilder({ fields, setFields }) {
|
|
49
|
+
const addField = () => setFields([...fields, { id: `field-${Date.now()}`, type: "text", label: "", required: false, order: fields.length }]);
|
|
50
|
+
const removeField = (id) => setFields(fields.filter((f) => f.id !== id));
|
|
51
|
+
const updateField = (id, patch) => setFields(fields.map((f) => (f.id === id ? { ...f, ...patch } : f)));
|
|
52
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-600 dark:text-zinc-400 uppercase tracking-wide", children: "Form fields" }), fields.length === 0 && (_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "No fields yet. Add fields to collect responses from participants." })), fields.map((field, idx) => {
|
|
53
|
+
const hasOptions = FIELD_TYPES_WITH_OPTIONS.includes(field.type);
|
|
54
|
+
const isNumeric = field.type === "number" || field.type === "rating";
|
|
55
|
+
const isText = field.type === "text" || field.type === "textarea" || field.type === "email";
|
|
56
|
+
return (_jsxs("div", { className: "rounded-lg border border-zinc-200 dark:border-zinc-600 p-3 space-y-2 bg-white dark:bg-zinc-900", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs(Text, { className: "text-xs font-medium text-zinc-500 dark:text-zinc-400", children: ["Field ", idx + 1] }), _jsx("button", { type: "button", onClick: () => removeField(field.id), className: "text-zinc-400 hover:text-red-500 transition-colors text-lg leading-none px-1", "aria-label": "Remove field", children: "\u00D7" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-2", children: [_jsx(Select, { label: "Type", value: field.type, options: FORM_FIELD_TYPE_OPTIONS, onChange: (e) => updateField(field.id, { type: e.target.value, options: undefined }) }), _jsx(Input, { label: "Label", value: field.label, onChange: (e) => updateField(field.id, { label: e.target.value }), placeholder: "e.g. Describe your entry", required: true })] }), !hasOptions && field.type !== "date" && field.type !== "file" && field.type !== "rating" && (_jsx(Input, { label: "Placeholder (optional)", value: field.placeholder ?? "", onChange: (e) => updateField(field.id, { placeholder: e.target.value || undefined }), placeholder: "Hint shown inside the field" })), hasOptions && (_jsxs("div", { className: "space-y-1", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Options \u2014 one per line" }), _jsx("textarea", { className: "w-full rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 text-sm text-zinc-800 dark:text-zinc-200 px-3 py-2 resize-y min-h-[72px] focus:outline-none focus:ring-2 focus:ring-primary/50", value: (field.options ?? []).join("\n"), onChange: (e) => updateField(field.id, { options: e.target.value.split("\n").map((s) => s.trim()).filter(Boolean) }), placeholder: "Option A\nOption B\nOption C" })] })), (isText || isNumeric) && (_jsxs("div", { className: "rounded bg-zinc-50 dark:bg-zinc-800 p-2 space-y-2", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Validation (optional)" }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [isText && (_jsxs(_Fragment, { children: [_jsx(Input, { label: "Min length", type: "number", value: String(field.validation?.minLength ?? ""), onChange: (e) => updateField(field.id, { validation: { ...field.validation, minLength: e.target.value ? Number(e.target.value) : undefined } }), placeholder: "0" }), _jsx(Input, { label: "Max length", type: "number", value: String(field.validation?.maxLength ?? ""), onChange: (e) => updateField(field.id, { validation: { ...field.validation, maxLength: e.target.value ? Number(e.target.value) : undefined } }), placeholder: "500" })] })), isNumeric && (_jsxs(_Fragment, { children: [_jsx(Input, { label: "Min value", type: "number", value: String(field.validation?.min ?? ""), onChange: (e) => updateField(field.id, { validation: { ...field.validation, min: e.target.value ? Number(e.target.value) : undefined } }), placeholder: "0" }), _jsx(Input, { label: "Max value", type: "number", value: String(field.validation?.max ?? ""), onChange: (e) => updateField(field.id, { validation: { ...field.validation, max: e.target.value ? Number(e.target.value) : undefined } }), placeholder: "100" })] }))] }), field.type === "text" && (_jsx(Input, { label: "Pattern (regex, optional)", value: field.validation?.pattern ?? "", onChange: (e) => updateField(field.id, { validation: { ...field.validation, pattern: e.target.value || undefined } }), placeholder: "^[A-Z].*" }))] })), _jsx(Toggle, { checked: field.required, onChange: (v) => updateField(field.id, { required: v }), label: "Required" })] }, field.id));
|
|
57
|
+
}), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: addField, children: "+ Add field" })] }));
|
|
58
|
+
}
|
|
69
59
|
const POLL_VISIBILITY_OPTIONS = [
|
|
70
60
|
{ label: "Always visible", value: "always" },
|
|
71
61
|
{ label: "After voting", value: "after_vote" },
|
|
72
62
|
{ label: "After event ends", value: "after_end" },
|
|
73
63
|
];
|
|
64
|
+
// --- Helpers -----------------------------------------------------------------
|
|
65
|
+
function toEventSlug(str) {
|
|
66
|
+
const base = str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
67
|
+
return base.startsWith("event-") ? base : `event-${base}`;
|
|
68
|
+
}
|
|
69
|
+
function toLocalDatetime(iso) {
|
|
70
|
+
if (!iso)
|
|
71
|
+
return "";
|
|
72
|
+
try {
|
|
73
|
+
const d = new Date(iso);
|
|
74
|
+
const offset = d.getTimezoneOffset();
|
|
75
|
+
const local = new Date(d.getTime() - offset * 60000);
|
|
76
|
+
return local.toISOString().slice(0, 16);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function toISOString(local) {
|
|
83
|
+
if (!local)
|
|
84
|
+
return "";
|
|
85
|
+
try {
|
|
86
|
+
return new Date(local).toISOString();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const DEFAULT_DRAFT = {
|
|
93
|
+
type: "sale",
|
|
94
|
+
title: "",
|
|
95
|
+
slug: "",
|
|
96
|
+
description: "",
|
|
97
|
+
startsAt: "",
|
|
98
|
+
endsAt: "",
|
|
99
|
+
coverImageUrl: "",
|
|
100
|
+
tags: [],
|
|
101
|
+
status: "draft",
|
|
102
|
+
createdBy: "",
|
|
103
|
+
discountPercent: "10",
|
|
104
|
+
saleBannerText: "",
|
|
105
|
+
couponId: "",
|
|
106
|
+
displayCode: "",
|
|
107
|
+
offerBannerText: "",
|
|
108
|
+
pollOptions: [{ id: "opt1", label: "" }, { id: "opt2", label: "" }],
|
|
109
|
+
allowMultiSelect: false,
|
|
110
|
+
allowComment: false,
|
|
111
|
+
resultsVisibility: "always",
|
|
112
|
+
requireLogin: true,
|
|
113
|
+
maxEntriesPerUser: "1",
|
|
114
|
+
hasLeaderboard: false,
|
|
115
|
+
hasPointSystem: false,
|
|
116
|
+
pointsLabel: "Points",
|
|
117
|
+
entryReviewRequired: false,
|
|
118
|
+
surveyFields: [],
|
|
119
|
+
anonymous: false,
|
|
120
|
+
feedbackFields: [],
|
|
121
|
+
hasRaffle: false,
|
|
122
|
+
raffleType: "open_raffle",
|
|
123
|
+
raffleTopN: "10",
|
|
124
|
+
rafflePrize: "",
|
|
125
|
+
rafflePrizeCouponId: "",
|
|
126
|
+
spinPrizes: [],
|
|
127
|
+
spinMaxPerUser: "1",
|
|
128
|
+
spinWindowStart: "",
|
|
129
|
+
spinWindowEnd: "",
|
|
130
|
+
};
|
|
131
|
+
// --- Component ---------------------------------------------------------------
|
|
74
132
|
export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
75
|
-
const
|
|
76
|
-
const [
|
|
77
|
-
const [
|
|
78
|
-
const [
|
|
79
|
-
const [
|
|
80
|
-
const [
|
|
81
|
-
const [status, setStatus] = React.useState("draft");
|
|
82
|
-
const [discountPercent, setDiscountPercent] = React.useState("10");
|
|
83
|
-
const [saleBannerText, setSaleBannerText] = React.useState("");
|
|
84
|
-
const [couponId, setCouponId] = React.useState("");
|
|
85
|
-
const [displayCode, setDisplayCode] = React.useState("");
|
|
86
|
-
const [offerBannerText, setOfferBannerText] = React.useState("");
|
|
87
|
-
const [pollOptions, setPollOptions] = React.useState([
|
|
88
|
-
{ id: "opt1", label: "" },
|
|
89
|
-
{ id: "opt2", label: "" },
|
|
90
|
-
]);
|
|
91
|
-
const [allowMultiSelect, setAllowMultiSelect] = React.useState(false);
|
|
92
|
-
const [allowComment, setAllowComment] = React.useState(false);
|
|
93
|
-
const [resultsVisibility, setResultsVisibility] = React.useState("always");
|
|
94
|
-
const [requireLogin, setRequireLogin] = React.useState(true);
|
|
95
|
-
const [maxEntriesPerUser, setMaxEntriesPerUser] = React.useState("1");
|
|
96
|
-
const [hasLeaderboard, setHasLeaderboard] = React.useState(false);
|
|
97
|
-
const [hasPointSystem, setHasPointSystem] = React.useState(false);
|
|
98
|
-
const [pointsLabel, setPointsLabel] = React.useState("Points");
|
|
99
|
-
const [entryReviewRequired, setEntryReviewRequired] = React.useState(false);
|
|
100
|
-
const [surveyFields, setSurveyFields] = React.useState([]);
|
|
101
|
-
const [anonymous, setAnonymous] = React.useState(false);
|
|
102
|
-
const [feedbackFields, setFeedbackFields] = React.useState([]);
|
|
103
|
-
const [hasRaffle, setHasRaffle] = React.useState(false);
|
|
104
|
-
const [raffleType, setRaffleType] = React.useState("open_raffle");
|
|
105
|
-
const [raffleTopN, setRaffleTopN] = React.useState("10");
|
|
106
|
-
const [rafflePrize, setRafflePrize] = React.useState("");
|
|
107
|
-
const [rafflePrizeCouponId, setRafflePrizeCouponId] = React.useState("");
|
|
108
|
-
const [spinPrizes, setSpinPrizes] = React.useState([]);
|
|
109
|
-
const [spinMaxPerUser, setSpinMaxPerUser] = React.useState("1");
|
|
110
|
-
const [spinWindowStart, setSpinWindowStart] = React.useState("");
|
|
111
|
-
const [spinWindowEnd, setSpinWindowEnd] = React.useState("");
|
|
133
|
+
const isEdit = Boolean(eventId);
|
|
134
|
+
const [draft, setDraft] = React.useState(DEFAULT_DRAFT);
|
|
135
|
+
const [slugManual, setSlugManual] = React.useState(false);
|
|
136
|
+
const [currentStep, setCurrentStep] = React.useState(0);
|
|
137
|
+
const [saveMessage, setSaveMessage] = React.useState(null);
|
|
138
|
+
const [triggerMessage, setTriggerMessage] = React.useState(null);
|
|
112
139
|
const [raffleWinnerName, setRaffleWinnerName] = React.useState("");
|
|
113
140
|
const [raffleEntryCount, setRaffleEntryCount] = React.useState(null);
|
|
114
141
|
const [raffleGithubUrl, setRaffleGithubUrl] = React.useState("");
|
|
115
|
-
const
|
|
116
|
-
|
|
142
|
+
const update = React.useCallback((partial) => {
|
|
143
|
+
setDraft((prev) => ({ ...prev, ...partial }));
|
|
144
|
+
setSaveMessage(null);
|
|
145
|
+
}, []);
|
|
146
|
+
// --- load existing event ---
|
|
117
147
|
const eventQuery = useQuery({
|
|
118
148
|
queryKey: ["admin-event-by-id", eventId],
|
|
119
149
|
queryFn: () => apiClient.get(`${ADMIN_ENDPOINTS.EVENTS}/${eventId}`),
|
|
@@ -124,134 +154,103 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
|
124
154
|
const event = eventQuery.data?.data ?? eventQuery.data;
|
|
125
155
|
if (!event?.id)
|
|
126
156
|
return;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
setSpinMaxPerUser(String(event.spinMaxPerUser ?? 1));
|
|
170
|
-
setSpinWindowStart(toLocalDatetime(event.spinWindowStart));
|
|
171
|
-
setSpinWindowEnd(toLocalDatetime(event.spinWindowEnd));
|
|
157
|
+
setDraft({
|
|
158
|
+
type: event.type || "sale",
|
|
159
|
+
title: event.title || "",
|
|
160
|
+
slug: event.slug || "",
|
|
161
|
+
description: event.description || "",
|
|
162
|
+
startsAt: toLocalDatetime(event.startsAt),
|
|
163
|
+
endsAt: toLocalDatetime(event.endsAt),
|
|
164
|
+
coverImageUrl: event.coverImageUrl || event.coverImage?.url || "",
|
|
165
|
+
tags: Array.isArray(event.tags) ? event.tags : [],
|
|
166
|
+
status: event.status || "draft",
|
|
167
|
+
createdBy: event.createdBy || "",
|
|
168
|
+
discountPercent: String(event.saleConfig?.discountPercent ?? 10),
|
|
169
|
+
saleBannerText: event.saleConfig?.bannerText || "",
|
|
170
|
+
couponId: event.offerConfig?.couponId || "",
|
|
171
|
+
displayCode: event.offerConfig?.displayCode || "",
|
|
172
|
+
offerBannerText: event.offerConfig?.bannerText || "",
|
|
173
|
+
pollOptions: event.pollConfig?.options?.length
|
|
174
|
+
? event.pollConfig.options
|
|
175
|
+
: [{ id: "opt1", label: "" }, { id: "opt2", label: "" }],
|
|
176
|
+
allowMultiSelect: Boolean(event.pollConfig?.allowMultiSelect),
|
|
177
|
+
allowComment: Boolean(event.pollConfig?.allowComment),
|
|
178
|
+
resultsVisibility: event.pollConfig?.resultsVisibility || "always",
|
|
179
|
+
requireLogin: event.surveyConfig?.requireLogin !== false,
|
|
180
|
+
maxEntriesPerUser: String(event.surveyConfig?.maxEntriesPerUser ?? 1),
|
|
181
|
+
hasLeaderboard: Boolean(event.surveyConfig?.hasLeaderboard),
|
|
182
|
+
hasPointSystem: Boolean(event.surveyConfig?.hasPointSystem),
|
|
183
|
+
pointsLabel: event.surveyConfig?.pointsLabel ?? "Points",
|
|
184
|
+
entryReviewRequired: Boolean(event.surveyConfig?.entryReviewRequired),
|
|
185
|
+
surveyFields: Array.isArray(event.surveyConfig?.formFields) ? event.surveyConfig.formFields : [],
|
|
186
|
+
anonymous: Boolean(event.feedbackConfig?.anonymous),
|
|
187
|
+
feedbackFields: Array.isArray(event.feedbackConfig?.formFields) ? event.feedbackConfig.formFields : [],
|
|
188
|
+
hasRaffle: Boolean(event.hasRaffle),
|
|
189
|
+
raffleType: event.raffleType || "open_raffle",
|
|
190
|
+
raffleTopN: String(event.raffleTopN ?? 10),
|
|
191
|
+
rafflePrize: event.rafflePrize || "",
|
|
192
|
+
rafflePrizeCouponId: event.rafflePrizeCouponId || "",
|
|
193
|
+
spinPrizes: Array.isArray(event.spinPrizes) ? event.spinPrizes : [],
|
|
194
|
+
spinMaxPerUser: String(event.spinMaxPerUser ?? 1),
|
|
195
|
+
spinWindowStart: toLocalDatetime(event.spinWindowStart),
|
|
196
|
+
spinWindowEnd: toLocalDatetime(event.spinWindowEnd),
|
|
197
|
+
});
|
|
198
|
+
setSlugManual(true);
|
|
172
199
|
setRaffleWinnerName(event.raffleWinnerDisplayName || "");
|
|
173
200
|
setRaffleEntryCount(typeof event.raffleEntryCount === "number" ? event.raffleEntryCount : null);
|
|
174
201
|
setRaffleGithubUrl(event.raffleGithubFunctionUrl || "");
|
|
175
202
|
}, [eventQuery.data]);
|
|
203
|
+
// --- save ---
|
|
176
204
|
const saveMutation = useMutation({
|
|
177
205
|
mutationFn: async () => {
|
|
178
206
|
const buildTypeConfig = () => {
|
|
179
|
-
if (type === "sale")
|
|
180
|
-
return {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
};
|
|
190
|
-
if (type === "poll")
|
|
191
|
-
return {
|
|
192
|
-
pollConfig: {
|
|
193
|
-
options: pollOptions.filter((o) => o.label.trim()),
|
|
194
|
-
allowMultiSelect,
|
|
195
|
-
allowComment,
|
|
196
|
-
resultsVisibility,
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
if (type === "survey")
|
|
200
|
-
return {
|
|
201
|
-
surveyConfig: {
|
|
202
|
-
requireLogin,
|
|
203
|
-
maxEntriesPerUser: Number(maxEntriesPerUser) || 1,
|
|
204
|
-
hasLeaderboard,
|
|
205
|
-
hasPointSystem,
|
|
206
|
-
pointsLabel: hasPointSystem ? (pointsLabel.trim() || "Points") : undefined,
|
|
207
|
-
entryReviewRequired,
|
|
208
|
-
formFields: surveyFields.map((f, i) => ({ ...f, order: i })),
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
if (type === "feedback")
|
|
212
|
-
return {
|
|
213
|
-
feedbackConfig: {
|
|
214
|
-
anonymous,
|
|
215
|
-
formFields: feedbackFields.map((f, i) => ({ ...f, order: i })),
|
|
216
|
-
},
|
|
217
|
-
};
|
|
207
|
+
if (draft.type === "sale")
|
|
208
|
+
return { saleConfig: { discountPercent: Number(draft.discountPercent) || 10, bannerText: draft.saleBannerText || undefined } };
|
|
209
|
+
if (draft.type === "offer")
|
|
210
|
+
return { offerConfig: { couponId: draft.couponId, displayCode: draft.displayCode, bannerText: draft.offerBannerText || undefined } };
|
|
211
|
+
if (draft.type === "poll")
|
|
212
|
+
return { pollConfig: { options: draft.pollOptions.filter((o) => o.label.trim()), allowMultiSelect: draft.allowMultiSelect, allowComment: draft.allowComment, resultsVisibility: draft.resultsVisibility } };
|
|
213
|
+
if (draft.type === "survey")
|
|
214
|
+
return { surveyConfig: { requireLogin: draft.requireLogin, maxEntriesPerUser: Number(draft.maxEntriesPerUser) || 1, hasLeaderboard: draft.hasLeaderboard, hasPointSystem: draft.hasPointSystem, pointsLabel: draft.hasPointSystem ? (draft.pointsLabel.trim() || "Points") : undefined, entryReviewRequired: draft.entryReviewRequired, formFields: draft.surveyFields.map((f, i) => ({ ...f, order: i })) } };
|
|
215
|
+
if (draft.type === "feedback")
|
|
216
|
+
return { feedbackConfig: { anonymous: draft.anonymous, formFields: draft.feedbackFields.map((f, i) => ({ ...f, order: i })) } };
|
|
218
217
|
return {};
|
|
219
218
|
};
|
|
220
|
-
const isRaffleType = type === "raffle" || type === "spin_wheel";
|
|
219
|
+
const isRaffleType = draft.type === "raffle" || draft.type === "spin_wheel";
|
|
221
220
|
const raffleFields = {};
|
|
222
|
-
if (hasRaffle || isRaffleType) {
|
|
221
|
+
if (draft.hasRaffle || isRaffleType) {
|
|
223
222
|
raffleFields.hasRaffle = true;
|
|
224
|
-
raffleFields.raffleType = isRaffleType && type === "spin_wheel"
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (
|
|
228
|
-
raffleFields.
|
|
229
|
-
if (
|
|
230
|
-
raffleFields.
|
|
231
|
-
if (
|
|
232
|
-
raffleFields.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (
|
|
236
|
-
raffleFields.
|
|
237
|
-
if (
|
|
238
|
-
raffleFields.
|
|
239
|
-
if (spinWindowEnd)
|
|
240
|
-
raffleFields.spinWindowEnd = toISOString(spinWindowEnd);
|
|
223
|
+
raffleFields.raffleType = isRaffleType && draft.type === "spin_wheel" ? "spin_wheel" : draft.raffleType;
|
|
224
|
+
if (Number(draft.raffleTopN) > 0)
|
|
225
|
+
raffleFields.raffleTopN = Number(draft.raffleTopN);
|
|
226
|
+
if (draft.rafflePrize)
|
|
227
|
+
raffleFields.rafflePrize = draft.rafflePrize;
|
|
228
|
+
if (draft.rafflePrizeCouponId)
|
|
229
|
+
raffleFields.rafflePrizeCouponId = draft.rafflePrizeCouponId;
|
|
230
|
+
if (draft.raffleType === "spin_wheel" || draft.type === "spin_wheel") {
|
|
231
|
+
raffleFields.spinPrizes = draft.spinPrizes;
|
|
232
|
+
if (Number(draft.spinMaxPerUser) > 0)
|
|
233
|
+
raffleFields.spinMaxPerUser = Number(draft.spinMaxPerUser);
|
|
234
|
+
if (draft.spinWindowStart)
|
|
235
|
+
raffleFields.spinWindowStart = toISOString(draft.spinWindowStart);
|
|
236
|
+
if (draft.spinWindowEnd)
|
|
237
|
+
raffleFields.spinWindowEnd = toISOString(draft.spinWindowEnd);
|
|
241
238
|
}
|
|
242
239
|
}
|
|
243
240
|
const payload = {
|
|
244
|
-
type,
|
|
245
|
-
title,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
241
|
+
type: draft.type,
|
|
242
|
+
title: draft.title,
|
|
243
|
+
slug: draft.slug || toEventSlug(draft.title),
|
|
244
|
+
description: draft.description,
|
|
245
|
+
tags: draft.tags,
|
|
246
|
+
startsAt: toISOString(draft.startsAt),
|
|
247
|
+
endsAt: toISOString(draft.endsAt),
|
|
248
|
+
coverImageUrl: draft.coverImageUrl || undefined,
|
|
250
249
|
...buildTypeConfig(),
|
|
251
250
|
...raffleFields,
|
|
252
251
|
};
|
|
253
252
|
if (eventId) {
|
|
254
|
-
payload.status = status;
|
|
253
|
+
payload.status = draft.status;
|
|
255
254
|
await apiClient.patch(`${ADMIN_ENDPOINTS.EVENTS}/${eventId}`, payload);
|
|
256
255
|
return eventId;
|
|
257
256
|
}
|
|
@@ -267,68 +266,7 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
|
267
266
|
setSaveMessage(error instanceof Error ? error.message : "Save failed");
|
|
268
267
|
},
|
|
269
268
|
});
|
|
270
|
-
|
|
271
|
-
...prev,
|
|
272
|
-
{ id: `field-${Date.now()}`, type: "text", label: "", required: false, order: prev.length },
|
|
273
|
-
]);
|
|
274
|
-
const makeRemoveField = (setter) => (id) => setter((prev) => prev.filter((f) => f.id !== id));
|
|
275
|
-
const makeUpdateField = (setter) => (id, patch) => setter((prev) => prev.map((f) => (f.id === id ? { ...f, ...patch } : f)));
|
|
276
|
-
const renderFormFieldBuilder = (fields, setter) => {
|
|
277
|
-
const addField = makeAddField(setter);
|
|
278
|
-
const removeField = makeRemoveField(setter);
|
|
279
|
-
const updateField = makeUpdateField(setter);
|
|
280
|
-
return (_jsxs("div", { className: "space-y-3", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-600 dark:text-zinc-400 uppercase tracking-wide", children: "Form fields" }), fields.length === 0 && (_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "No fields yet. Add fields to collect responses from participants." })), fields.map((field, idx) => {
|
|
281
|
-
const hasOptions = FIELD_TYPES_WITH_OPTIONS.includes(field.type);
|
|
282
|
-
const isNumeric = field.type === "number" || field.type === "rating";
|
|
283
|
-
const isText = field.type === "text" || field.type === "textarea" || field.type === "email";
|
|
284
|
-
return (_jsxs("div", { className: "rounded-lg border border-zinc-200 dark:border-zinc-600 p-3 space-y-2 bg-white dark:bg-zinc-900", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs(Text, { className: "text-xs font-medium text-zinc-500 dark:text-zinc-400", children: ["Field ", idx + 1] }), _jsx("button", { type: "button", onClick: () => removeField(field.id), className: "text-zinc-400 hover:text-red-500 transition-colors text-lg leading-none px-1", "aria-label": "Remove field", children: "\u00D7" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-2", children: [_jsx(Select, { label: "Type", value: field.type, options: FORM_FIELD_TYPE_OPTIONS, onChange: (e) => updateField(field.id, { type: e.target.value, options: undefined }) }), _jsx(Input, { label: "Label", value: field.label, onChange: (e) => updateField(field.id, { label: e.target.value }), placeholder: "e.g. Describe your entry", required: true })] }), !hasOptions && field.type !== "date" && field.type !== "file" && field.type !== "rating" && (_jsx(Input, { label: "Placeholder (optional)", value: field.placeholder ?? "", onChange: (e) => updateField(field.id, { placeholder: e.target.value || undefined }), placeholder: "Hint shown inside the field" })), hasOptions && (_jsxs("div", { className: "space-y-1", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Options \u2014 one per line" }), _jsx("textarea", { className: "w-full rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 text-sm text-zinc-800 dark:text-zinc-200 px-3 py-2 resize-y min-h-[72px] focus:outline-none focus:ring-2 focus:ring-primary/50", value: (field.options ?? []).join("\n"), onChange: (e) => updateField(field.id, {
|
|
285
|
-
options: e.target.value.split("\n").map((s) => s.trim()).filter(Boolean),
|
|
286
|
-
}), placeholder: "Option A\nOption B\nOption C" })] })), (isText || isNumeric) && (_jsxs("div", { className: "rounded bg-zinc-50 dark:bg-zinc-800 p-2 space-y-2", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Validation (optional)" }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [isText && (_jsxs(_Fragment, { children: [_jsx(Input, { label: "Min length", type: "number", value: String(field.validation?.minLength ?? ""), onChange: (e) => updateField(field.id, {
|
|
287
|
-
validation: {
|
|
288
|
-
...field.validation,
|
|
289
|
-
minLength: e.target.value ? Number(e.target.value) : undefined,
|
|
290
|
-
},
|
|
291
|
-
}), placeholder: "0" }), _jsx(Input, { label: "Max length", type: "number", value: String(field.validation?.maxLength ?? ""), onChange: (e) => updateField(field.id, {
|
|
292
|
-
validation: {
|
|
293
|
-
...field.validation,
|
|
294
|
-
maxLength: e.target.value ? Number(e.target.value) : undefined,
|
|
295
|
-
},
|
|
296
|
-
}), placeholder: "500" })] })), isNumeric && (_jsxs(_Fragment, { children: [_jsx(Input, { label: "Min value", type: "number", value: String(field.validation?.min ?? ""), onChange: (e) => updateField(field.id, {
|
|
297
|
-
validation: {
|
|
298
|
-
...field.validation,
|
|
299
|
-
min: e.target.value ? Number(e.target.value) : undefined,
|
|
300
|
-
},
|
|
301
|
-
}), placeholder: "0" }), _jsx(Input, { label: "Max value", type: "number", value: String(field.validation?.max ?? ""), onChange: (e) => updateField(field.id, {
|
|
302
|
-
validation: {
|
|
303
|
-
...field.validation,
|
|
304
|
-
max: e.target.value ? Number(e.target.value) : undefined,
|
|
305
|
-
},
|
|
306
|
-
}), placeholder: "100" })] }))] }), field.type === "text" && (_jsx(Input, { label: "Pattern (regex, optional)", value: field.validation?.pattern ?? "", onChange: (e) => updateField(field.id, {
|
|
307
|
-
validation: {
|
|
308
|
-
...field.validation,
|
|
309
|
-
pattern: e.target.value || undefined,
|
|
310
|
-
},
|
|
311
|
-
}), placeholder: "^[A-Z].*" }))] })), _jsx(Toggle, { checked: field.required, onChange: (v) => updateField(field.id, { required: v }), label: "Required" })] }, field.id));
|
|
312
|
-
}), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: addField, children: "+ Add field" })] }));
|
|
313
|
-
};
|
|
314
|
-
const addSpinPrize = () => {
|
|
315
|
-
setSpinPrizes((prev) => [
|
|
316
|
-
...prev,
|
|
317
|
-
{
|
|
318
|
-
id: `prize-${Date.now()}`,
|
|
319
|
-
label: "",
|
|
320
|
-
couponId: undefined,
|
|
321
|
-
weight: 1,
|
|
322
|
-
isActive: true,
|
|
323
|
-
},
|
|
324
|
-
]);
|
|
325
|
-
};
|
|
326
|
-
const removeSpinPrize = (id) => {
|
|
327
|
-
setSpinPrizes((prev) => prev.filter((p) => p.id !== id));
|
|
328
|
-
};
|
|
329
|
-
const updateSpinPrize = (id, patch) => {
|
|
330
|
-
setSpinPrizes((prev) => prev.map((p) => (p.id === id ? { ...p, ...patch } : p)));
|
|
331
|
-
};
|
|
269
|
+
// --- raffle trigger ---
|
|
332
270
|
const triggerRaffleMutation = useMutation({
|
|
333
271
|
mutationFn: async () => {
|
|
334
272
|
if (!eventId)
|
|
@@ -337,9 +275,7 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
|
337
275
|
},
|
|
338
276
|
onSuccess: (res) => {
|
|
339
277
|
const data = res.data ?? res;
|
|
340
|
-
setTriggerMessage(data?.raffleWinnerDisplayName
|
|
341
|
-
? `Winner: ${data.raffleWinnerDisplayName}`
|
|
342
|
-
: "Raffle triggered.");
|
|
278
|
+
setTriggerMessage(data?.raffleWinnerDisplayName ? `Winner: ${data.raffleWinnerDisplayName}` : "Raffle triggered.");
|
|
343
279
|
if (data?.raffleWinnerDisplayName)
|
|
344
280
|
setRaffleWinnerName(data.raffleWinnerDisplayName);
|
|
345
281
|
if (typeof data?.raffleEntryCount === "number")
|
|
@@ -349,38 +285,56 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
|
349
285
|
setTriggerMessage(err instanceof Error ? err.message : "Trigger failed");
|
|
350
286
|
},
|
|
351
287
|
});
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
288
|
+
const isLoading = saveMutation.isPending || eventQuery.isLoading;
|
|
289
|
+
// --- steps ---
|
|
290
|
+
const steps = [
|
|
291
|
+
{
|
|
292
|
+
label: "Details",
|
|
293
|
+
validate: (values) => {
|
|
294
|
+
if (!values.title.trim())
|
|
295
|
+
return "Title is required";
|
|
296
|
+
if (!values.startsAt)
|
|
297
|
+
return "Start date is required";
|
|
298
|
+
if (!values.endsAt)
|
|
299
|
+
return "End date is required";
|
|
300
|
+
return null;
|
|
301
|
+
},
|
|
302
|
+
render: ({ values, onChange }) => (_jsxs("div", { className: "space-y-5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Event Details" }), !isEdit && (_jsx(Select, { label: "Event type", value: values.type, options: EVENT_TYPE_OPTIONS, onChange: (e) => onChange({ type: e.target.value }) })), _jsx(Input, { label: "Title", value: values.title, onChange: (e) => {
|
|
303
|
+
const v = e.target.value;
|
|
304
|
+
onChange({ title: v, ...(!slugManual && { slug: toEventSlug(v) }) });
|
|
305
|
+
}, placeholder: "Charizard Flash Sale 2026", required: true }), _jsx(Input, { label: "Slug", value: values.slug, onChange: (e) => { setSlugManual(true); onChange({ slug: e.target.value }); }, placeholder: "event-charizard-flash-sale-2026", helperText: "Auto-generated from title. Must start with 'event-'." }), _jsxs(Div, { children: [_jsx("label", { className: "block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Description" }), _jsx(RichTextEditor, { value: values.description, onChange: (v) => onChange({ description: v }), minHeightClassName: "min-h-[120px]", placeholder: "Describe this event\u2026" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Starts at", type: "datetime-local", value: values.startsAt, onChange: (e) => onChange({ startsAt: e.target.value }), required: true }), _jsx(Input, { label: "Ends at", type: "datetime-local", value: values.endsAt, onChange: (e) => onChange({ endsAt: e.target.value }), required: true })] }), _jsx(TagInput, { label: "Tags", value: values.tags, onChange: (t) => onChange({ tags: t }), placeholder: "e.g. pokemon, sale, 2026" })] })),
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
label: "Media",
|
|
309
|
+
render: ({ values, onChange }) => (_jsxs("div", { className: "space-y-5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Media" }), _jsx(Input, { label: "Cover Image URL (optional)", value: values.coverImageUrl, onChange: (e) => onChange({ coverImageUrl: e.target.value }), placeholder: "https://\u2026", helperText: "Direct link to the event cover image." })] })),
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
label: "Settings",
|
|
313
|
+
validate: (values) => {
|
|
314
|
+
if (values.type === "offer" && !values.couponId.trim())
|
|
315
|
+
return "Coupon ID is required for offer events";
|
|
316
|
+
if (values.type === "offer" && !values.displayCode.trim())
|
|
317
|
+
return "Display code is required for offer events";
|
|
318
|
+
if (values.type === "poll" && values.pollOptions.filter((o) => o.label.trim()).length < 2)
|
|
319
|
+
return "Poll events require at least 2 options";
|
|
320
|
+
return null;
|
|
321
|
+
},
|
|
322
|
+
render: ({ values, onChange }) => (_jsxs("div", { className: "space-y-5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Event Settings" }), isEdit && (_jsx(Select, { label: "Status", value: values.status, options: EVENT_STATUS_OPTIONS, onChange: (e) => onChange({ status: e.target.value }) })), values.createdBy && (_jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium mb-1", children: "Created by" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: values.createdBy })] })), values.type === "sale" && (_jsxs("div", { className: CLS_PANEL_SM, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Sale configuration" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Discount %", type: "number", value: values.discountPercent, onChange: (e) => onChange({ discountPercent: e.target.value }), placeholder: "10" }), _jsx(Input, { label: "Banner text (optional)", value: values.saleBannerText, onChange: (e) => onChange({ saleBannerText: e.target.value }), placeholder: "Limited time \u2014 ends Sunday!" })] })] })), values.type === "offer" && (_jsxs("div", { className: CLS_PANEL_SM, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Offer configuration" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Coupon ID", value: values.couponId, onChange: (e) => onChange({ couponId: e.target.value }), placeholder: "Firestore coupon document ID", required: true }), _jsx(Input, { label: "Display code", value: values.displayCode, onChange: (e) => onChange({ displayCode: e.target.value }), placeholder: "CHARIZARD25", required: true })] }), _jsx(Input, { label: "Banner text (optional)", value: values.offerBannerText, onChange: (e) => onChange({ offerBannerText: e.target.value }), placeholder: "Use CHARIZARD25 at checkout" })] })), values.type === "poll" && (_jsxs("div", { className: CLS_PANEL_LG, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Poll configuration" }), _jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Options (minimum 2)" }), values.pollOptions.map((opt, idx) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex-1", children: _jsx(Input, { value: opt.label, onChange: (e) => onChange({ pollOptions: values.pollOptions.map((o) => o.id === opt.id ? { ...o, label: e.target.value } : o) }), placeholder: `Option ${idx + 1}` }) }), values.pollOptions.length > 2 && (_jsx("button", { type: "button", onClick: () => onChange({ pollOptions: values.pollOptions.filter((o) => o.id !== opt.id) }), className: "text-zinc-400 hover:text-red-500 transition-colors px-2 py-1 text-lg leading-none", "aria-label": "Remove option", children: "\u00D7" }))] }, opt.id))), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onChange({ pollOptions: [...values.pollOptions, { id: `opt${Date.now()}`, label: "" }] }), children: "+ Add option" })] }), _jsx(Select, { label: "Results visibility", value: values.resultsVisibility, options: POLL_VISIBILITY_OPTIONS, onChange: (e) => onChange({ resultsVisibility: e.target.value }) }), _jsxs("div", { className: "space-y-3", children: [_jsx(Toggle, { checked: values.allowMultiSelect, onChange: (v) => onChange({ allowMultiSelect: v }), label: "Allow multi-select" }), _jsx(Toggle, { checked: values.allowComment, onChange: (v) => onChange({ allowComment: v }), label: "Allow comment with vote" })] })] })), values.type === "survey" && (_jsxs("div", { className: CLS_PANEL_LG, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Survey configuration" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Max entries per user", type: "number", value: values.maxEntriesPerUser, onChange: (e) => onChange({ maxEntriesPerUser: e.target.value }), placeholder: "1" }), values.hasPointSystem && (_jsx(Input, { label: "Points label", value: values.pointsLabel, onChange: (e) => onChange({ pointsLabel: e.target.value }), placeholder: "Points" }))] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Toggle, { checked: values.requireLogin, onChange: (v) => onChange({ requireLogin: v }), label: "Require login to participate" }), _jsx(Toggle, { checked: values.hasLeaderboard, onChange: (v) => onChange({ hasLeaderboard: v }), label: "Show leaderboard" }), _jsx(Toggle, { checked: values.hasPointSystem, onChange: (v) => onChange({ hasPointSystem: v }), label: "Enable point system" }), _jsx(Toggle, { checked: values.entryReviewRequired, onChange: (v) => onChange({ entryReviewRequired: v }), label: "Require employee review before entry is approved" })] }), _jsx(FormFieldBuilder, { fields: values.surveyFields, setFields: (f) => onChange({ surveyFields: f }) })] })), values.type === "feedback" && (_jsxs("div", { className: CLS_PANEL_LG, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Feedback configuration" }), _jsx(Toggle, { checked: values.anonymous, onChange: (v) => onChange({ anonymous: v }), label: "Allow anonymous submissions" }), _jsx(FormFieldBuilder, { fields: values.feedbackFields, setFields: (f) => onChange({ feedbackFields: f }) })] }))] })),
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
label: "Raffle / Spin",
|
|
326
|
+
render: ({ values, onChange }) => {
|
|
327
|
+
const isRaffleType = values.type === "raffle" || values.type === "spin_wheel";
|
|
328
|
+
const showRaffleConfig = isRaffleType || values.hasRaffle;
|
|
329
|
+
const isSpinMode = values.type === "spin_wheel" || values.raffleType === "spin_wheel";
|
|
330
|
+
return (_jsxs("div", { className: "space-y-5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Raffle & Spin Wheel" }), !isRaffleType && (_jsx(Toggle, { checked: values.hasRaffle, onChange: (v) => onChange({ hasRaffle: v }), label: "Attach a raffle to this event" })), !isRaffleType && !values.hasRaffle && (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Enable the toggle above to attach a raffle draw to this event. Not applicable for this event type by default." })), showRaffleConfig && (_jsxs("div", { className: CLS_PANEL_LG, children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Select, { label: "Raffle type", value: values.type === "spin_wheel" ? "spin_wheel" : values.raffleType, options: RAFFLE_TYPE_OPTIONS, onChange: (e) => onChange({ raffleType: e.target.value }), disabled: values.type === "spin_wheel" }), _jsx(Input, { label: "Top N (for top-N raffle types)", type: "number", value: values.raffleTopN, onChange: (e) => onChange({ raffleTopN: e.target.value }), placeholder: "10" })] }), _jsx(Input, { label: "Prize description", value: values.rafflePrize, onChange: (e) => onChange({ rafflePrize: e.target.value }), placeholder: "\u20B92,000 store credit + exclusive Pikachu sticker" }), _jsx(Input, { label: "Coupon ID for winner (optional)", value: values.rafflePrizeCouponId, onChange: (e) => onChange({ rafflePrizeCouponId: e.target.value }), placeholder: "coupon-vip-winner-2026" }), isSpinMode && (_jsxs("div", { className: "rounded-lg border border-zinc-200 dark:border-slate-700 p-3 space-y-3", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-600 dark:text-zinc-300", children: "Spin prizes (weighted random)" }), values.spinPrizes.length === 0 && (_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "No spin prizes yet. Add at least one to enable spinning." })), values.spinPrizes.map((p) => (_jsxs("div", { className: "grid grid-cols-12 gap-2 items-end", children: [_jsx("div", { className: "col-span-5", children: _jsx(Input, { label: "Label", value: p.label, onChange: (e) => onChange({ spinPrizes: values.spinPrizes.map((sp) => sp.id === p.id ? { ...sp, label: e.target.value } : sp) }), placeholder: "\u20B9100 off" }) }), _jsx("div", { className: "col-span-3", children: _jsx(Input, { label: "Coupon ID", value: p.couponId ?? "", onChange: (e) => onChange({ spinPrizes: values.spinPrizes.map((sp) => sp.id === p.id ? { ...sp, couponId: e.target.value || undefined } : sp) }) }) }), _jsx("div", { className: "col-span-2", children: _jsx(Input, { label: "Weight", type: "number", value: String(p.weight), onChange: (e) => onChange({ spinPrizes: values.spinPrizes.map((sp) => sp.id === p.id ? { ...sp, weight: Number(e.target.value) || 0 } : sp) }) }) }), _jsx("div", { className: "col-span-1 flex items-center justify-center pb-2", children: _jsx(Toggle, { checked: p.isActive, onChange: (v) => onChange({ spinPrizes: values.spinPrizes.map((sp) => sp.id === p.id ? { ...sp, isActive: v } : sp) }), label: "" }) }), _jsx("div", { className: "col-span-1 flex items-center justify-center pb-2", children: _jsx("button", { type: "button", onClick: () => onChange({ spinPrizes: values.spinPrizes.filter((sp) => sp.id !== p.id) }), className: "text-zinc-400 hover:text-red-500 text-lg leading-none px-2", "aria-label": "Remove prize", children: "\u00D7" }) })] }, p.id))), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onChange({ spinPrizes: [...values.spinPrizes, { id: `prize-${Date.now()}`, label: "", couponId: undefined, weight: 1, isActive: true }] }), children: "+ Add spin prize" }), _jsx(Input, { label: "Max spins per user", type: "number", value: values.spinMaxPerUser, onChange: (e) => onChange({ spinMaxPerUser: e.target.value }), placeholder: "1" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Spin window start", type: "datetime-local", value: values.spinWindowStart, onChange: (e) => onChange({ spinWindowStart: e.target.value }) }), _jsx(Input, { label: "Spin window end", type: "datetime-local", value: values.spinWindowEnd, onChange: (e) => onChange({ spinWindowEnd: e.target.value }) })] })] })), isEdit && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20 p-3 space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Text, { className: "text-sm font-medium text-amber-900 dark:text-amber-100", children: raffleWinnerName ? `Winner: ${raffleWinnerName}` : "Raffle not yet triggered" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: () => triggerRaffleMutation.mutate(), disabled: triggerRaffleMutation.isPending || Boolean(raffleWinnerName), children: triggerRaffleMutation.isPending ? "Triggering…" : raffleWinnerName ? "Already triggered" : "Trigger Raffle Now" })] }), raffleEntryCount !== null && _jsxs(Text, { className: "text-xs text-amber-800 dark:text-amber-200", children: ["Pool size: ", raffleEntryCount] }), raffleGithubUrl && _jsxs(Text, { className: "text-xs text-amber-800 dark:text-amber-200 break-all", children: ["Fairness proof: ", raffleGithubUrl] }), triggerMessage && _jsx(Text, { className: "text-xs text-amber-900 dark:text-amber-100", children: triggerMessage })] }))] }))] }));
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
];
|
|
366
334
|
const alertSection = eventQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load event", children: eventQuery.error instanceof Error ? eventQuery.error.message : "Unknown error" })) : null;
|
|
367
|
-
const
|
|
368
|
-
e.preventDefault();
|
|
369
|
-
saveMutation.mutate();
|
|
370
|
-
}, className: "space-y-6", children: [!eventId && (_jsx(Select, { label: "Event type", value: type, options: EVENT_TYPE_OPTIONS, onChange: (e) => setType(e.target.value) })), _jsx(Input, { label: "Title", value: title, onChange: (e) => setTitle(e.target.value), placeholder: "Charizard Flash Sale 2026", required: true }), _jsxs(_Fragment, { children: [_jsx("label", { className: "block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Description" }), _jsx(RichTextEditor, { value: description, onChange: setDescription, minHeightClassName: "min-h-[120px]", placeholder: "Describe this event\u2026" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Starts at", type: "datetime-local", value: startsAt, onChange: (e) => setStartsAt(e.target.value), required: true }), _jsx(Input, { label: "Ends at", type: "datetime-local", value: endsAt, onChange: (e) => setEndsAt(e.target.value), required: true })] }), _jsx(Input, { label: "Cover image URL (optional)", value: coverImageUrl, onChange: (e) => setCoverImageUrl(e.target.value), placeholder: "https://\u2026" }), eventId && (_jsx(Select, { label: "Status", value: status, options: EVENT_STATUS_OPTIONS, onChange: (e) => setStatus(e.target.value) })), type === "sale" && (_jsxs("div", { className: CLS_PANEL_SM, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Sale configuration" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Discount %", type: "number", value: discountPercent, onChange: (e) => setDiscountPercent(e.target.value), placeholder: "10" }), _jsx(Input, { label: "Banner text (optional)", value: saleBannerText, onChange: (e) => setSaleBannerText(e.target.value), placeholder: "Limited time \u2014 ends Sunday!" })] })] })), type === "offer" && (_jsxs("div", { className: CLS_PANEL_SM, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Offer configuration" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Coupon ID", value: couponId, onChange: (e) => setCouponId(e.target.value), placeholder: "Firestore coupon document ID", required: true }), _jsx(Input, { label: "Display code", value: displayCode, onChange: (e) => setDisplayCode(e.target.value), placeholder: "CHARIZARD25", required: true })] }), _jsx(Input, { label: "Banner text (optional)", value: offerBannerText, onChange: (e) => setOfferBannerText(e.target.value), placeholder: "Use CHARIZARD25 at checkout" })] })), type === "poll" && (_jsxs("div", { className: CLS_PANEL_LG, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Poll configuration" }), _jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Options (minimum 2)" }), pollOptions.map((opt, idx) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex-1", children: _jsx(Input, { value: opt.label, onChange: (e) => updatePollOption(opt.id, e.target.value), placeholder: `Option ${idx + 1}` }) }), pollOptions.length > 2 && (_jsx("button", { type: "button", onClick: () => removePollOption(opt.id), className: "text-zinc-400 hover:text-red-500 transition-colors px-2 py-1 text-lg leading-none", "aria-label": "Remove option", children: "\u00D7" }))] }, opt.id))), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: addPollOption, children: "+ Add option" })] }), _jsx(Select, { label: "Results visibility", value: resultsVisibility, options: POLL_VISIBILITY_OPTIONS, onChange: (e) => setResultsVisibility(e.target.value) }), _jsxs("div", { className: "space-y-3", children: [_jsx(Toggle, { checked: allowMultiSelect, onChange: setAllowMultiSelect, label: "Allow multi-select" }), _jsx(Toggle, { checked: allowComment, onChange: setAllowComment, label: "Allow comment with vote" })] })] })), type === "survey" && (_jsxs("div", { className: CLS_PANEL_LG, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Survey configuration" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Max entries per user", type: "number", value: maxEntriesPerUser, onChange: (e) => setMaxEntriesPerUser(e.target.value), placeholder: "1" }), hasPointSystem && (_jsx(Input, { label: "Points label", value: pointsLabel, onChange: (e) => setPointsLabel(e.target.value), placeholder: "Points" }))] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Toggle, { checked: requireLogin, onChange: setRequireLogin, label: "Require login to participate" }), _jsx(Toggle, { checked: hasLeaderboard, onChange: setHasLeaderboard, label: "Show leaderboard" }), _jsx(Toggle, { checked: hasPointSystem, onChange: setHasPointSystem, label: "Enable point system (employees assign points per entry)" }), _jsx(Toggle, { checked: entryReviewRequired, onChange: setEntryReviewRequired, label: "Require employee review before entry is approved" })] }), renderFormFieldBuilder(surveyFields, setSurveyFields)] })), type === "feedback" && (_jsxs("div", { className: CLS_PANEL_LG, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Feedback configuration" }), _jsx(Toggle, { checked: anonymous, onChange: setAnonymous, label: "Allow anonymous submissions" }), renderFormFieldBuilder(feedbackFields, setFeedbackFields)] })), _jsxs("div", { className: CLS_PANEL_LG, children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Raffle & spin wheel" }), type !== "raffle" && type !== "spin_wheel" && (_jsx(Toggle, { checked: hasRaffle, onChange: setHasRaffle, label: "Attach raffle" }))] }), (hasRaffle || type === "raffle" || type === "spin_wheel") && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Select, { label: "Raffle type", value: type === "spin_wheel" ? "spin_wheel" : raffleType, options: RAFFLE_TYPE_OPTIONS, onChange: (e) => setRaffleType(e.target.value), disabled: type === "spin_wheel" }), _jsx(Input, { label: "Top N (for top-N raffle types)", type: "number", value: raffleTopN, onChange: (e) => setRaffleTopN(e.target.value), placeholder: "10" })] }), _jsx(Input, { label: "Prize description", value: rafflePrize, onChange: (e) => setRafflePrize(e.target.value), placeholder: "\u20B92,000 store credit + exclusive Pikachu sticker" }), _jsx(Input, { label: "Coupon ID for winner (optional)", value: rafflePrizeCouponId, onChange: (e) => setRafflePrizeCouponId(e.target.value), placeholder: "coupon-vip-winner-2026" }), (raffleType === "spin_wheel" || type === "spin_wheel") && (_jsxs("div", { className: "rounded-lg border border-zinc-200 dark:border-slate-700 p-3 space-y-3", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-600 dark:text-zinc-300", children: "Spin prizes (weighted random)" }), spinPrizes.length === 0 && (_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "No spin prizes yet. Add at least one to enable spinning." })), spinPrizes.map((p) => (_jsxs("div", { className: "grid grid-cols-12 gap-2 items-end", children: [_jsx("div", { className: "col-span-5", children: _jsx(Input, { label: "Label", value: p.label, onChange: (e) => updateSpinPrize(p.id, { label: e.target.value }), placeholder: "\u20B9100 off" }) }), _jsx("div", { className: "col-span-3", children: _jsx(Input, { label: "Coupon ID", value: p.couponId ?? "", onChange: (e) => updateSpinPrize(p.id, {
|
|
371
|
-
couponId: e.target.value || undefined,
|
|
372
|
-
}) }) }), _jsx("div", { className: "col-span-2", children: _jsx(Input, { label: "Weight", type: "number", value: String(p.weight), onChange: (e) => updateSpinPrize(p.id, {
|
|
373
|
-
weight: Number(e.target.value) || 0,
|
|
374
|
-
}) }) }), _jsx("div", { className: "col-span-1 flex items-center justify-center pb-2", children: _jsx(Toggle, { checked: p.isActive, onChange: (v) => updateSpinPrize(p.id, { isActive: v }), label: "" }) }), _jsx("div", { className: "col-span-1 flex items-center justify-center pb-2", children: _jsx("button", { type: "button", onClick: () => removeSpinPrize(p.id), className: "text-zinc-400 hover:text-red-500 text-lg leading-none px-2", "aria-label": "Remove prize", children: "\u00D7" }) })] }, p.id))), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: addSpinPrize, children: "+ Add spin prize" }), _jsx(Input, { label: "Max spins per user", type: "number", value: spinMaxPerUser, onChange: (e) => setSpinMaxPerUser(e.target.value), placeholder: "1" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Input, { label: "Spin window start", type: "datetime-local", value: spinWindowStart, onChange: (e) => setSpinWindowStart(e.target.value) }), _jsx(Input, { label: "Spin window end", type: "datetime-local", value: spinWindowEnd, onChange: (e) => setSpinWindowEnd(e.target.value) })] })] })), eventId && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20 p-3 space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Text, { className: "text-sm font-medium text-amber-900 dark:text-amber-100", children: raffleWinnerName
|
|
375
|
-
? `Winner: ${raffleWinnerName}`
|
|
376
|
-
: "Raffle not yet triggered" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: () => triggerRaffleMutation.mutate(), disabled: triggerRaffleMutation.isPending ||
|
|
377
|
-
Boolean(raffleWinnerName), children: triggerRaffleMutation.isPending
|
|
378
|
-
? "Triggering…"
|
|
379
|
-
: raffleWinnerName
|
|
380
|
-
? "Already triggered"
|
|
381
|
-
: "Trigger Raffle Now" })] }), raffleEntryCount !== null && (_jsxs(Text, { className: "text-xs text-amber-800 dark:text-amber-200", children: ["Pool size: ", raffleEntryCount] })), raffleGithubUrl && (_jsxs(Text, { className: "text-xs text-amber-800 dark:text-amber-200 break-all", children: ["Fairness proof: ", raffleGithubUrl] })), triggerMessage && (_jsx(Text, { className: "text-xs text-amber-900 dark:text-amber-100", children: triggerMessage }))] }))] }))] }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => { window.location.href = "/admin/events"; }, children: "Cancel" }), _jsx(Button, { type: "submit", disabled: saveMutation.isPending || !isValid, children: saveMutation.isPending ? "Saving…" : eventId ? "Save changes" : "Create event" })] }), saveMessage && (_jsx(Alert, { variant: saveMessage.toLowerCase().includes("fail") ? "error" : "success", title: "Save status", children: saveMessage }))] }));
|
|
335
|
+
const formContent = (_jsxs("div", { className: "space-y-4", children: [alertSection, _jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: () => { saveMutation.mutate(); }, formId: "admin-event", currentStep: currentStep, onStepChange: setCurrentStep, completeLabel: isEdit ? "Save Changes" : "Create Event", isLoading: isLoading }), saveMessage && (_jsx(Alert, { variant: saveMessage.toLowerCase().includes("fail") || saveMessage.toLowerCase().includes("error") ? "error" : "success", title: "Save status", children: saveMessage }))] }));
|
|
382
336
|
if (embedded) {
|
|
383
|
-
return (
|
|
337
|
+
return _jsx("div", { className: "overflow-y-auto p-4", children: formContent });
|
|
384
338
|
}
|
|
385
|
-
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title:
|
|
339
|
+
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Event" : "Create Event", sections: [formContent] }));
|
|
386
340
|
}
|