@mohasinac/appkit 2.7.46 → 2.7.48
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/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +1 -0
- package/dist/core/unit-of-work.js +3 -4
- package/dist/features/admin/components/DashboardStats.js +1 -1
- package/dist/features/auth/actions/profile-actions.js +4 -1
- package/dist/features/auth/repository/user.repository.d.ts +1 -0
- package/dist/features/auth/repository/user.repository.js +3 -0
- package/dist/features/blog/api/route.js +3 -0
- package/dist/features/blog/components/BlogIndexListing.js +2 -0
- package/dist/features/blog/components/BlogPostView.js +2 -1
- package/dist/features/categories/api/route.js +1 -1
- package/dist/features/categories/components/BundleDetailView.js +5 -3
- package/dist/features/categories/components/CategoryBundlesListing.js +77 -41
- package/dist/features/categories/components/CategoryDetailPageView.js +12 -9
- package/dist/features/categories/components/CategoryProductsListing.js +3 -5
- package/dist/features/classified/components/ClassifiedIndexListing.js +3 -4
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +3 -4
- package/dist/features/events/actions/event-actions.d.ts +2 -3
- package/dist/features/events/actions/event-actions.js +3 -5
- package/dist/features/events/api/route.js +7 -7
- package/dist/features/events/components/AdminEventEditorView.js +80 -6
- package/dist/features/events/components/AdminEventEntriesView.js +33 -4
- package/dist/features/events/components/EventFilters.d.ts +3 -0
- package/dist/features/events/components/EventFilters.js +1 -0
- package/dist/features/events/components/EventLeaderboard.js +1 -1
- package/dist/features/events/components/EventsIndexListing.js +10 -7
- package/dist/features/events/repository/event-entry.repository.d.ts +2 -2
- package/dist/features/events/repository/event-entry.repository.js +32 -7
- package/dist/features/events/types/index.d.ts +3 -1
- package/dist/features/filters/SwitchFilter.js +2 -4
- package/dist/features/homepage/components/BrandsSection.js +2 -2
- package/dist/features/homepage/components/CustomCardsSection.js +1 -1
- package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
- package/dist/features/live/components/LiveItemsIndexListing.js +3 -4
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -4
- package/dist/features/products/components/AuctionsIndexListing.js +3 -4
- package/dist/features/products/components/PrizeDrawsIndexListing.js +3 -8
- package/dist/features/products/components/ProductDetailPageView.js +17 -2
- package/dist/features/products/components/ProductsIndexListing.js +10 -6
- package/dist/features/products/schemas/firestore.d.ts +2 -0
- package/dist/features/reviews/actions/review-actions.js +2 -0
- package/dist/features/reviews/components/ReviewDetailShell.js +3 -2
- package/dist/features/reviews/components/ReviewFilters.js +1 -1
- package/dist/features/reviews/components/ReviewsIndexListing.js +9 -2
- package/dist/features/reviews/hooks/useReviews.js +2 -0
- package/dist/features/reviews/schemas/firestore.d.ts +4 -0
- package/dist/features/reviews/types/index.d.ts +3 -0
- package/dist/features/scams/components/ScamRegistryView.js +41 -8
- package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
- package/dist/features/stores/components/StoreAuctionsListing.js +3 -4
- package/dist/features/stores/components/StoreDetailLayoutView.js +13 -5
- package/dist/features/stores/components/StorePreOrdersListing.js +3 -4
- package/dist/features/stores/components/StoreProductsListing.js +3 -4
- package/dist/index.d.ts +1 -0
- package/dist/providers/db-firebase/helpers.d.ts +4 -6
- package/dist/providers/db-firebase/helpers.js +18 -5
- package/dist/providers/db-firebase/sieve.js +26 -1
- package/dist/react/hooks/usePendingFilters.d.ts +2 -0
- package/dist/react/hooks/usePendingFilters.js +9 -0
- package/dist/react/hooks/usePendingTable.d.ts +5 -0
- package/dist/react/hooks/usePendingTable.js +1 -0
- package/dist/seed/homepage-sections-seed-data.js +2 -2
- package/dist/seed/products-standard-seed-data.js +1 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Drawer.style.css +13 -0
- package/dist/ui/components/FilterDrawer.d.ts +4 -2
- package/dist/ui/components/FilterDrawer.js +2 -2
- package/package.json +1 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx,
|
|
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
5
|
import { Alert, Button, Form, FormActions, Input, RichTextEditor, Select, StackedViewShell, Text, Toggle, } from "../../../ui";
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
8
|
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
|
+
const CLS_PANEL_LG = "rounded-xl border border-zinc-200 dark:border-slate-700 p-4 space-y-4";
|
|
9
10
|
function toLocalDatetime(iso) {
|
|
10
11
|
if (!iso)
|
|
11
12
|
return "";
|
|
@@ -50,6 +51,21 @@ const EVENT_STATUS_OPTIONS = [
|
|
|
50
51
|
{ label: "Paused", value: "paused" },
|
|
51
52
|
{ label: "Ended", value: "ended" },
|
|
52
53
|
];
|
|
54
|
+
const FORM_FIELD_TYPE_OPTIONS = [
|
|
55
|
+
{ label: "Short text", value: "text" },
|
|
56
|
+
{ label: "Long text", value: "textarea" },
|
|
57
|
+
{ label: "Email", value: "email" },
|
|
58
|
+
{ label: "Phone", value: "phone" },
|
|
59
|
+
{ label: "Number", value: "number" },
|
|
60
|
+
{ label: "Dropdown (select)", value: "select" },
|
|
61
|
+
{ label: "Multi-select", value: "multiselect" },
|
|
62
|
+
{ label: "Checkbox", value: "checkbox" },
|
|
63
|
+
{ label: "Radio", value: "radio" },
|
|
64
|
+
{ label: "Date", value: "date" },
|
|
65
|
+
{ label: "Rating (1–5)", value: "rating" },
|
|
66
|
+
{ label: "File upload", value: "file" },
|
|
67
|
+
];
|
|
68
|
+
const FIELD_TYPES_WITH_OPTIONS = ["select", "multiselect", "radio", "checkbox"];
|
|
53
69
|
const POLL_VISIBILITY_OPTIONS = [
|
|
54
70
|
{ label: "Always visible", value: "always" },
|
|
55
71
|
{ label: "After voting", value: "after_vote" },
|
|
@@ -78,7 +94,12 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
|
78
94
|
const [requireLogin, setRequireLogin] = React.useState(true);
|
|
79
95
|
const [maxEntriesPerUser, setMaxEntriesPerUser] = React.useState("1");
|
|
80
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([]);
|
|
81
101
|
const [anonymous, setAnonymous] = React.useState(false);
|
|
102
|
+
const [feedbackFields, setFeedbackFields] = React.useState([]);
|
|
82
103
|
const [hasRaffle, setHasRaffle] = React.useState(false);
|
|
83
104
|
const [raffleType, setRaffleType] = React.useState("open_raffle");
|
|
84
105
|
const [raffleTopN, setRaffleTopN] = React.useState("10");
|
|
@@ -130,9 +151,14 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
|
130
151
|
setRequireLogin(event.surveyConfig.requireLogin !== false);
|
|
131
152
|
setMaxEntriesPerUser(String(event.surveyConfig.maxEntriesPerUser ?? 1));
|
|
132
153
|
setHasLeaderboard(Boolean(event.surveyConfig.hasLeaderboard));
|
|
154
|
+
setHasPointSystem(Boolean(event.surveyConfig.hasPointSystem));
|
|
155
|
+
setPointsLabel(event.surveyConfig.pointsLabel ?? "Points");
|
|
156
|
+
setEntryReviewRequired(Boolean(event.surveyConfig.entryReviewRequired));
|
|
157
|
+
setSurveyFields(Array.isArray(event.surveyConfig.formFields) ? event.surveyConfig.formFields : []);
|
|
133
158
|
}
|
|
134
159
|
if (event.feedbackConfig) {
|
|
135
160
|
setAnonymous(Boolean(event.feedbackConfig.anonymous));
|
|
161
|
+
setFeedbackFields(Array.isArray(event.feedbackConfig.formFields) ? event.feedbackConfig.formFields : []);
|
|
136
162
|
}
|
|
137
163
|
setHasRaffle(Boolean(event.hasRaffle));
|
|
138
164
|
setRaffleType(event.raffleType || "open_raffle");
|
|
@@ -176,14 +202,18 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
|
176
202
|
requireLogin,
|
|
177
203
|
maxEntriesPerUser: Number(maxEntriesPerUser) || 1,
|
|
178
204
|
hasLeaderboard,
|
|
179
|
-
hasPointSystem
|
|
180
|
-
|
|
181
|
-
|
|
205
|
+
hasPointSystem,
|
|
206
|
+
pointsLabel: hasPointSystem ? (pointsLabel.trim() || "Points") : undefined,
|
|
207
|
+
entryReviewRequired,
|
|
208
|
+
formFields: surveyFields.map((f, i) => ({ ...f, order: i })),
|
|
182
209
|
},
|
|
183
210
|
};
|
|
184
211
|
if (type === "feedback")
|
|
185
212
|
return {
|
|
186
|
-
feedbackConfig: {
|
|
213
|
+
feedbackConfig: {
|
|
214
|
+
anonymous,
|
|
215
|
+
formFields: feedbackFields.map((f, i) => ({ ...f, order: i })),
|
|
216
|
+
},
|
|
187
217
|
};
|
|
188
218
|
return {};
|
|
189
219
|
};
|
|
@@ -237,6 +267,50 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
|
237
267
|
setSaveMessage(error instanceof Error ? error.message : "Save failed");
|
|
238
268
|
},
|
|
239
269
|
});
|
|
270
|
+
const makeAddField = (setter) => () => setter((prev) => [
|
|
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
|
+
};
|
|
240
314
|
const addSpinPrize = () => {
|
|
241
315
|
setSpinPrizes((prev) => [
|
|
242
316
|
...prev,
|
|
@@ -293,7 +367,7 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, ...rest }) {
|
|
|
293
367
|
const formSection = (_jsxs(Form, { onSubmit: (e) => {
|
|
294
368
|
e.preventDefault();
|
|
295
369
|
saveMutation.mutate();
|
|
296
|
-
}, 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:
|
|
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, {
|
|
297
371
|
couponId: e.target.value || undefined,
|
|
298
372
|
}) }) }), _jsx("div", { className: "col-span-2", children: _jsx(Input, { label: "Weight", type: "number", value: String(p.weight), onChange: (e) => updateSpinPrize(p.id, {
|
|
299
373
|
weight: Number(e.target.value) || 0,
|
|
@@ -57,13 +57,15 @@ export function AdminEventEntriesView({ eventId, entriesEndpoint, entryReviewEnd
|
|
|
57
57
|
enabled: Boolean(eventId && resolvedStatsEndpoint),
|
|
58
58
|
staleTime: 15000,
|
|
59
59
|
});
|
|
60
|
+
const [expandedEntryId, setExpandedEntryId] = React.useState(null);
|
|
61
|
+
const [pointsInputs, setPointsInputs] = React.useState({});
|
|
60
62
|
const reviewMutation = useMutation({
|
|
61
|
-
mutationFn: async ({ entryId, status, }) => {
|
|
63
|
+
mutationFn: async ({ entryId, status, points, }) => {
|
|
62
64
|
if (!eventId)
|
|
63
65
|
throw new Error("eventId is required");
|
|
64
66
|
const endpoint = entryReviewEndpoint?.(eventId, entryId) ??
|
|
65
67
|
ADMIN_ENDPOINTS.EVENT_ENTRY_BY_ID(eventId, entryId);
|
|
66
|
-
await apiClient.patch(endpoint, { status });
|
|
68
|
+
await apiClient.patch(endpoint, { status, ...(points !== undefined ? { points } : {}) });
|
|
67
69
|
},
|
|
68
70
|
onSuccess: async () => {
|
|
69
71
|
await Promise.all([
|
|
@@ -72,6 +74,19 @@ export function AdminEventEntriesView({ eventId, entriesEndpoint, entryReviewEnd
|
|
|
72
74
|
]);
|
|
73
75
|
},
|
|
74
76
|
});
|
|
77
|
+
const pointsMutation = useMutation({
|
|
78
|
+
mutationFn: async ({ entryId, points, currentStatus }) => {
|
|
79
|
+
if (!eventId)
|
|
80
|
+
throw new Error("eventId is required");
|
|
81
|
+
const endpoint = entryReviewEndpoint?.(eventId, entryId) ??
|
|
82
|
+
ADMIN_ENDPOINTS.EVENT_ENTRY_BY_ID(eventId, entryId);
|
|
83
|
+
const status = currentStatus === "flagged" ? "flagged" : "approved";
|
|
84
|
+
await apiClient.patch(endpoint, { status, points });
|
|
85
|
+
},
|
|
86
|
+
onSuccess: async () => {
|
|
87
|
+
await queryClient.invalidateQueries({ queryKey: ["admin-event-entries", eventId] });
|
|
88
|
+
},
|
|
89
|
+
});
|
|
75
90
|
const rows = entriesQuery.data?.items ?? [];
|
|
76
91
|
const columns = React.useMemo(() => [
|
|
77
92
|
{
|
|
@@ -89,12 +104,23 @@ export function AdminEventEntriesView({ eventId, entriesEndpoint, entryReviewEnd
|
|
|
89
104
|
header: "Status",
|
|
90
105
|
render: (row) => (_jsx(Text, { className: "text-xs font-medium uppercase tracking-wide", children: row.reviewStatus })),
|
|
91
106
|
},
|
|
107
|
+
{
|
|
108
|
+
key: "points",
|
|
109
|
+
header: "Points",
|
|
110
|
+
render: (row) => (_jsxs("div", { className: "flex items-center gap-1.5", "data-section": "adminevententriesview-points", children: [_jsx("input", { type: "number", min: 0, className: "w-20 rounded border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 text-sm px-2 py-1 text-zinc-800 dark:text-zinc-200 focus:outline-none focus:ring-2 focus:ring-primary/50", placeholder: "0", value: pointsInputs[row.id] ?? (row.points !== undefined ? String(row.points) : ""), onChange: (e) => setPointsInputs((prev) => ({ ...prev, [row.id]: e.target.value })) }), _jsx(Button, { size: "sm", variant: "outline", disabled: pointsMutation.isPending || !(row.id in pointsInputs), onClick: () => {
|
|
111
|
+
const val = Number(pointsInputs[row.id]);
|
|
112
|
+
if (!isNaN(val)) {
|
|
113
|
+
pointsMutation.mutate({ entryId: row.id, points: val, currentStatus: row.reviewStatus });
|
|
114
|
+
setPointsInputs((prev) => { const next = { ...prev }; delete next[row.id]; return next; });
|
|
115
|
+
}
|
|
116
|
+
}, children: "Save" })] })),
|
|
117
|
+
},
|
|
92
118
|
{
|
|
93
119
|
key: "actions",
|
|
94
120
|
header: "Actions",
|
|
95
|
-
render: (row) => (_jsxs("div", { className: "flex items-center gap-2", "data-section": "adminevententriesview-div-274", children: [_jsx(Button, { size: "sm", variant: "secondary", disabled: reviewMutation.isPending || row.reviewStatus === "approved", onClick: () => reviewMutation.mutate({ entryId: row.id, status: "approved" }), children: "Approve" }), _jsx(Button, { size: "sm", variant: "outline", disabled: reviewMutation.isPending || row.reviewStatus === "flagged", onClick: () => reviewMutation.mutate({ entryId: row.id, status: "flagged" }), children: "Flag" })] })),
|
|
121
|
+
render: (row) => (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", "data-section": "adminevententriesview-div-274", children: [_jsx(Button, { size: "sm", variant: "secondary", disabled: reviewMutation.isPending || row.reviewStatus === "approved", onClick: () => reviewMutation.mutate({ entryId: row.id, status: "approved" }), children: "Approve" }), _jsx(Button, { size: "sm", variant: "outline", disabled: reviewMutation.isPending || row.reviewStatus === "flagged", onClick: () => reviewMutation.mutate({ entryId: row.id, status: "flagged" }), children: "Flag" }), row.formResponses && Object.keys(row.formResponses).length > 0 && (_jsx(Button, { size: "sm", variant: "outline", onClick: () => setExpandedEntryId((prev) => (prev === row.id ? null : row.id)), children: expandedEntryId === row.id ? "Hide" : "Responses" }))] })),
|
|
96
122
|
},
|
|
97
|
-
], [reviewMutation]);
|
|
123
|
+
], [reviewMutation, pointsMutation, pointsInputs, expandedEntryId]);
|
|
98
124
|
const statsSection = (_jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-3", "data-section": "adminevententriesview-div-275", children: [_jsx(Alert, { variant: "info", title: "Total entries", children: String(statsQuery.data?.stats?.totalEntries ?? 0) }), _jsx(Alert, { variant: "success", title: "Approved", children: String(statsQuery.data?.stats?.approvedEntries ?? 0) }), _jsx(Alert, { variant: "warning", title: "Flagged", children: String(statsQuery.data?.stats?.flaggedEntries ?? 0) })] }));
|
|
99
125
|
const filtersSection = (_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-3", "data-section": "adminevententriesview-div-276", children: [_jsx(Input, { label: "Search entries", value: searchQuery, onChange: (event) => {
|
|
100
126
|
setPage(1);
|
|
@@ -103,6 +129,8 @@ export function AdminEventEntriesView({ eventId, entriesEndpoint, entryReviewEnd
|
|
|
103
129
|
setPage(1);
|
|
104
130
|
setStatusFilter(event.target.value);
|
|
105
131
|
} })] }));
|
|
132
|
+
const expandedEntry = expandedEntryId ? rows.find((r) => r.id === expandedEntryId) : null;
|
|
133
|
+
const responsesPanelSection = expandedEntry?.formResponses && Object.keys(expandedEntry.formResponses).length > 0 ? (_jsxs("div", { className: "rounded-xl border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800 p-4 space-y-3", children: [_jsxs(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: ["Responses \u2014 ", expandedEntry.userDisplayName || expandedEntry.userId || "Anonymous"] }), _jsx("div", { className: "space-y-2", children: Object.entries(expandedEntry.formResponses).map(([key, value]) => (_jsxs("div", { className: "space-y-0.5", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-500 dark:text-zinc-400", children: key }), _jsx(Text, { className: "text-sm text-zinc-800 dark:text-zinc-200 whitespace-pre-wrap break-words", children: Array.isArray(value) ? value.join(", ") : String(value ?? "—") })] }, key))) })] })) : null;
|
|
106
134
|
const tableSection = (_jsx(DataTable, { columns: columns, rows: rows, isLoading: entriesQuery.isLoading, currentPage: entriesQuery.data?.page ?? page, totalPages: entriesQuery.data?.totalPages ?? 1, onPageChange: setPage, emptyLabel: "No entries found" }));
|
|
107
135
|
const eventTitle = statsQuery.data?.event?.title;
|
|
108
136
|
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: eventTitle ? `${eventTitle} Entries` : "Event Entries", renderHeader: renderHeader, sections: [
|
|
@@ -112,6 +140,7 @@ export function AdminEventEntriesView({ eventId, entriesEndpoint, entryReviewEnd
|
|
|
112
140
|
renderStats?.() ?? statsSection,
|
|
113
141
|
renderFilters?.() ?? filtersSection,
|
|
114
142
|
renderTable?.() ?? tableSection,
|
|
143
|
+
responsesPanelSection,
|
|
115
144
|
renderPagination?.(),
|
|
116
145
|
], overlays: renderReviewDrawer?.() }));
|
|
117
146
|
}
|
|
@@ -64,6 +64,9 @@ export declare const EVENT_PUBLIC_SORT_OPTIONS: readonly [{
|
|
|
64
64
|
}, {
|
|
65
65
|
readonly value: string;
|
|
66
66
|
readonly label: "Title A–Z";
|
|
67
|
+
}, {
|
|
68
|
+
readonly value: string;
|
|
69
|
+
readonly label: "Title Z–A";
|
|
67
70
|
}, {
|
|
68
71
|
readonly value: "-stats.totalEntries";
|
|
69
72
|
readonly label: "Most Entries";
|
|
@@ -28,6 +28,7 @@ export const EVENT_PUBLIC_SORT_OPTIONS = [
|
|
|
28
28
|
{ value: sortBy(EVENT_FIELDS.STARTS_AT, "ASC"), label: "Starts Soonest" },
|
|
29
29
|
{ value: sortBy(EVENT_FIELDS.STARTS_AT), label: "Starts Latest" },
|
|
30
30
|
{ value: sortBy(EVENT_FIELDS.TITLE, "ASC"), label: "Title A–Z" },
|
|
31
|
+
{ value: sortBy(EVENT_FIELDS.TITLE), label: "Title Z–A" },
|
|
31
32
|
{ value: "-stats.totalEntries", label: "Most Entries" },
|
|
32
33
|
];
|
|
33
34
|
// Backward-compatible alias.
|
|
@@ -14,5 +14,5 @@ export function EventLeaderboard({ isLoading = false, isEmpty = false, renderLis
|
|
|
14
14
|
}
|
|
15
15
|
if (renderList)
|
|
16
16
|
return _jsx("div", { className: className, "data-section": "eventleaderboard-div-280", children: renderList() });
|
|
17
|
-
return (_jsx("div", { className: `space-y-2 ${className}`, "data-section": "eventleaderboard-div-281", children: entries.map((entry, i) => renderEntry ? (_jsx(React.Fragment, { children: renderEntry(entry, i) }, entry.userId)) : (_jsxs("div", { className: "flex items-center justify-between p-3 rounded-xl border", "data-section": "eventleaderboard-div-282", children: [_jsxs(Span, { className: "font-medium", children: ["#", entry.rank, " ", entry.userDisplayName] }), _jsxs(Span, { children: [entry.
|
|
17
|
+
return (_jsx("div", { className: `space-y-2 ${className}`, "data-section": "eventleaderboard-div-281", children: entries.map((entry, i) => renderEntry ? (_jsx(React.Fragment, { children: renderEntry(entry, i) }, entry.userId)) : (_jsxs("div", { className: "flex items-center justify-between p-3 rounded-xl border", "data-section": "eventleaderboard-div-282", children: [_jsxs(Span, { className: "font-medium", children: ["#", entry.rank, " ", entry.userDisplayName] }), _jsxs(Span, { children: [entry.totalPoints, " ", labels.points ?? "pts"] })] }, entry.userId))) }));
|
|
18
18
|
}
|
|
@@ -81,6 +81,7 @@ export function EventsIndexListing({ initialData }) {
|
|
|
81
81
|
const statusRaw = table.get(TABLE_KEYS.STATUS);
|
|
82
82
|
const dateFrom = table.get(TABLE_KEYS.DATE_FROM);
|
|
83
83
|
const dateTo = table.get(TABLE_KEYS.DATE_TO);
|
|
84
|
+
const showExpired = table.get(TABLE_KEYS.SHOW_EXPIRED) === "true";
|
|
84
85
|
const filterParts = [];
|
|
85
86
|
if (typeRaw) {
|
|
86
87
|
const types = typeRaw.split("|").filter(Boolean);
|
|
@@ -90,13 +91,15 @@ export function EventsIndexListing({ initialData }) {
|
|
|
90
91
|
else if (types.length > 1)
|
|
91
92
|
filterParts.push(sieveMultiEq(EVENT_FIELDS.TYPE, types));
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
const statusValues = statusRaw
|
|
95
|
+
? statusRaw.split("|").filter(Boolean)
|
|
96
|
+
: showExpired
|
|
97
|
+
? ["active", "ended", "paused"]
|
|
98
|
+
: [];
|
|
99
|
+
if (statusValues.length === 1)
|
|
100
|
+
filterParts.push(sieveFilter(EVENT_FIELDS.STATUS, SIEVE_OP.EQ, statusValues[0]));
|
|
101
|
+
else if (statusValues.length > 1)
|
|
102
|
+
filterParts.push(sieveMultiEq(EVENT_FIELDS.STATUS, statusValues));
|
|
100
103
|
if (dateFrom)
|
|
101
104
|
filterParts.push(sieveFilter(EVENT_FIELDS.STARTS_AT, SIEVE_OP.GTE, dateFrom));
|
|
102
105
|
if (dateTo)
|
|
@@ -9,9 +9,9 @@ declare class EventEntryRepository extends BaseRepository<EventEntryDocument> {
|
|
|
9
9
|
listForUser(userId: string, model: SieveModel): Promise<FirebaseSieveResult<EventEntryDocument>>;
|
|
10
10
|
hasUserEntered(eventId: string, userId: string): Promise<boolean>;
|
|
11
11
|
countUserEntries(eventId: string, userId: string): Promise<number>;
|
|
12
|
-
getLeaderboard(eventId: string, limit?: number): Promise<
|
|
12
|
+
getLeaderboard(eventId: string, limit?: number): Promise<import("../types").LeaderboardEntry[]>;
|
|
13
13
|
createEntry(input: EventEntryCreateInput): Promise<EventEntryDocument>;
|
|
14
|
-
reviewEntry(id: string, reviewStatus: EventEntryDocument["reviewStatus"], reviewedBy: string, reviewNote?: string): Promise<EventEntryDocument>;
|
|
14
|
+
reviewEntry(id: string, reviewStatus: EventEntryDocument["reviewStatus"], reviewedBy: string, reviewNote?: string, points?: number): Promise<EventEntryDocument>;
|
|
15
15
|
}
|
|
16
16
|
declare const eventEntryRepository: EventEntryRepository;
|
|
17
17
|
export { EventEntryRepository, eventEntryRepository };
|
|
@@ -53,13 +53,37 @@ class EventEntryRepository extends BaseRepository {
|
|
|
53
53
|
const snapshot = await this.getCollection()
|
|
54
54
|
.where(EVENT_ENTRY_FIELDS.EVENT_ID, "==", eventId)
|
|
55
55
|
.where(EVENT_ENTRY_FIELDS.REVIEW_STATUS, "==", EVENT_ENTRY_FIELDS.REVIEW_STATUS_VALUES.APPROVED)
|
|
56
|
-
.orderBy(EVENT_ENTRY_FIELDS.POINTS, "desc")
|
|
57
|
-
.limit(limit)
|
|
58
56
|
.get();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
// Aggregate per user: sum all approved entry points
|
|
58
|
+
const byUser = new Map();
|
|
59
|
+
for (const doc of snapshot.docs) {
|
|
60
|
+
const raw = decryptPiiFields({ id: doc.id, ...doc.data() }, [...EVENT_ENTRY_PII_FIELDS]);
|
|
61
|
+
if (!raw.userId)
|
|
62
|
+
continue;
|
|
63
|
+
const existing = byUser.get(raw.userId);
|
|
64
|
+
const pts = typeof raw.points === "number" ? raw.points : 0;
|
|
65
|
+
if (existing) {
|
|
66
|
+
existing.totalPoints += pts;
|
|
67
|
+
existing.entryCount += 1;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
byUser.set(raw.userId, {
|
|
71
|
+
displayName: raw.userDisplayName ?? raw.userId,
|
|
72
|
+
totalPoints: pts,
|
|
73
|
+
entryCount: 1,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return Array.from(byUser.entries())
|
|
78
|
+
.sort((a, b) => b[1].totalPoints - a[1].totalPoints)
|
|
79
|
+
.slice(0, limit)
|
|
80
|
+
.map(([userId, data], idx) => ({
|
|
81
|
+
rank: idx + 1,
|
|
82
|
+
userId,
|
|
83
|
+
userDisplayName: data.displayName,
|
|
84
|
+
totalPoints: data.totalPoints,
|
|
85
|
+
entryCount: data.entryCount,
|
|
86
|
+
}));
|
|
63
87
|
}
|
|
64
88
|
catch (error) {
|
|
65
89
|
throw new DatabaseError(`Failed to get leaderboard for event ${eventId}`, error);
|
|
@@ -88,7 +112,7 @@ class EventEntryRepository extends BaseRepository {
|
|
|
88
112
|
throw new DatabaseError("Failed to create event entry", error);
|
|
89
113
|
}
|
|
90
114
|
}
|
|
91
|
-
async reviewEntry(id, reviewStatus, reviewedBy, reviewNote) {
|
|
115
|
+
async reviewEntry(id, reviewStatus, reviewedBy, reviewNote, points) {
|
|
92
116
|
try {
|
|
93
117
|
const now = new Date();
|
|
94
118
|
await this.getCollection()
|
|
@@ -98,6 +122,7 @@ class EventEntryRepository extends BaseRepository {
|
|
|
98
122
|
[EVENT_ENTRY_FIELDS.REVIEWED_BY]: reviewedBy,
|
|
99
123
|
[EVENT_ENTRY_FIELDS.REVIEWED_AT]: now,
|
|
100
124
|
...(reviewNote !== undefined ? { reviewNote } : {}),
|
|
125
|
+
...(points !== undefined ? { points } : {}),
|
|
101
126
|
}));
|
|
102
127
|
serverLogger.info("Event entry reviewed", { entryId: id, reviewStatus });
|
|
103
128
|
return this.findByIdOrFail(id);
|
|
@@ -127,7 +127,9 @@ export interface LeaderboardEntry {
|
|
|
127
127
|
rank: number;
|
|
128
128
|
userId: string;
|
|
129
129
|
userDisplayName: string;
|
|
130
|
-
points
|
|
130
|
+
/** Sum of all approved entry points for this user in the event */
|
|
131
|
+
totalPoints: number;
|
|
132
|
+
entryCount: number;
|
|
131
133
|
}
|
|
132
134
|
export interface EventListResponse {
|
|
133
135
|
items: EventItem[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { Button, Div, Row, Span, Text } from "../../ui";
|
|
4
|
+
import { Button, Div, Row, Span, Text, Toggle } from "../../ui";
|
|
5
5
|
import { cn } from "./filterUtils";
|
|
6
6
|
export function SwitchFilter({ title, label, checked, onChange, defaultCollapsed = false, className = "", isOpen: controlledOpen, onToggle, onClear, }) {
|
|
7
7
|
const isControlled = controlledOpen !== undefined;
|
|
@@ -13,7 +13,5 @@ export function SwitchFilter({ title, label, checked, onChange, defaultCollapsed
|
|
|
13
13
|
else
|
|
14
14
|
setInternalCollapsed((c) => !c);
|
|
15
15
|
};
|
|
16
|
-
return (_jsxs(Div, { role: "group", "aria-labelledby": `sf-${title}`, className: cn("p-4 border-b border-zinc-200 dark:border-slate-700 last:border-b-0", className), children: [_jsxs(Row, { className: "gap-2", children: [_jsxs(Button, { type: "button", id: `sf-${title}`, onClick: handleToggle, variant: "ghost", size: "sm", className: "flex flex-1 items-center justify-between text-sm font-semibold text-zinc-900 dark:text-zinc-50 py-1 hover:opacity-80 transition-opacity", "aria-expanded": !isCollapsed, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-50", children: title }), _jsx("svg", { className: cn("w-4 h-4 text-zinc-500 dark:text-zinc-400 transition-transform duration-200", isCollapsed ? "" : "rotate-180"), fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }), checked && onClear && (_jsx(Button, { type: "button", onClick: onClear, variant: "ghost", size: "sm", className: "inline-flex items-center justify-center w-5 h-5 text-zinc-500 dark:text-zinc-400 hover:text-red-600 dark:hover:text-red-400 rounded-full transition-colors", "aria-label": "Clear", children: _jsx("svg", { className: "w-3 h-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2.5, "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) }))] }), !isCollapsed && (
|
|
17
|
-
? "bg-primary-600 dark:bg-secondary-500"
|
|
18
|
-
: "bg-zinc-200 dark:bg-slate-700"), children: _jsx(Span, { className: cn("pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200", checked ? "translate-x-4" : "translate-x-0") }) })] }) }))] }));
|
|
16
|
+
return (_jsxs(Div, { role: "group", "aria-labelledby": `sf-${title}`, className: cn("p-4 border-b border-zinc-200 dark:border-slate-700 last:border-b-0", className), children: [_jsxs(Row, { className: "gap-2", children: [_jsxs(Button, { type: "button", id: `sf-${title}`, onClick: handleToggle, variant: "ghost", size: "sm", className: "flex flex-1 items-center justify-between text-sm font-semibold text-zinc-900 dark:text-zinc-50 py-1 hover:opacity-80 transition-opacity", "aria-expanded": !isCollapsed, children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-50", children: title }), _jsx("svg", { className: cn("w-4 h-4 text-zinc-500 dark:text-zinc-400 transition-transform duration-200", isCollapsed ? "" : "rotate-180"), fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }), checked && onClear && (_jsx(Button, { type: "button", onClick: onClear, variant: "ghost", size: "sm", className: "inline-flex items-center justify-center w-5 h-5 text-zinc-500 dark:text-zinc-400 hover:text-red-600 dark:hover:text-red-400 rounded-full transition-colors", "aria-label": "Clear", children: _jsx("svg", { className: "w-3 h-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2.5, "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) }))] }), !isCollapsed && (_jsxs(Div, { className: "mt-3 flex items-center justify-between gap-3", children: [_jsx(Span, { className: "text-sm text-zinc-700 dark:text-zinc-300", children: label }), _jsx(Toggle, { checked: checked, onChange: onChange, size: "sm" })] }))] }));
|
|
19
17
|
}
|
|
@@ -24,7 +24,7 @@ function BrandLogo({ brand }) {
|
|
|
24
24
|
const iconSrc = brand.display?.icon;
|
|
25
25
|
const coverImage = brand.display?.coverImage;
|
|
26
26
|
const initial = brand.name[0]?.toUpperCase() ?? "?";
|
|
27
|
-
return (_jsxs(Link, { href: ROUTES.PUBLIC.CATEGORY_DETAIL(brand.slug), className: "flex h-
|
|
27
|
+
return (_jsxs(Link, { href: ROUTES.PUBLIC.CATEGORY_DETAIL(brand.slug), className: "flex h-32 w-36 flex-col items-center justify-center gap-2 rounded-xl border border-zinc-200 bg-white p-3 shadow-sm transition-all hover:border-primary-300 hover:shadow-md dark:border-slate-700 dark:bg-slate-900 md:h-40", children: [iconSrc || coverImage ? (_jsx(Image, { src: iconSrc ?? coverImage, alt: brand.name, width: 64, height: 64, className: "h-16 w-16 rounded object-contain" })) : (_jsx("div", { className: "flex h-16 w-16 items-center justify-center rounded-lg bg-primary-100 text-sm font-bold text-primary-700 dark:bg-primary-900 dark:text-primary-300", children: initial })), _jsx(Text, { className: "w-full truncate text-center text-xs font-medium text-zinc-700 dark:text-zinc-300", children: brand.name })] }));
|
|
28
28
|
}
|
|
29
29
|
export function BrandsSection({ title = "Top Brands", subtitle, limit = 12, viewMoreHref, viewMoreLabel = "All brands →", className = "", initialItems, cta, filters, }) {
|
|
30
30
|
const { themed } = THEME_CONSTANTS;
|
|
@@ -39,5 +39,5 @@ export function BrandsSection({ title = "Top Brands", subtitle, limit = 12, view
|
|
|
39
39
|
});
|
|
40
40
|
if (!isLoading && allBrands.length === 0)
|
|
41
41
|
return null;
|
|
42
|
-
return (_jsx(Section, { className: `py-12 px-4 ${themed.bgSecondary} ${className}`, children: _jsxs("div", { className: "mx-auto max-w-7xl", children: [_jsxs("div", { className: "mb-6 flex items-center justify-between", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: `text-2xl font-bold md:text-3xl ${themed.textPrimary}`, children: title }), subtitle && (_jsx(Text, { variant: "secondary", className: "mt-1 text-sm", children: subtitle }))] }), !cta && viewMoreHref && (_jsx(Link, { href: viewMoreHref, className: "text-sm font-medium text-[var(--appkit-color-primary)] hover:underline", children: viewMoreLabel }))] }), showFeaturedChip && !isLoading && (_jsxs("div", { className: "mb-4 flex flex-wrap gap-2", children: [_jsx(BrandFilterChip, { label: "All", active: activeFilter === "all", onClick: () => setActiveFilter("all") }), _jsx(BrandFilterChip, { label: "Featured", active: activeFilter === "featured", onClick: () => setActiveFilter(activeFilter === "featured" ? "all" : "featured") })] })), isLoading ? (_jsx("div", { className: "flex gap-3 overflow-hidden", children: Array.from({ length: 8 }).map((_, i) => (_jsx("div", { className: "h-
|
|
42
|
+
return (_jsx(Section, { className: `py-12 px-4 ${themed.bgSecondary} ${className}`, children: _jsxs("div", { className: "mx-auto max-w-7xl", children: [_jsxs("div", { className: "mb-6 flex items-center justify-between", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: `text-2xl font-bold md:text-3xl ${themed.textPrimary}`, children: title }), subtitle && (_jsx(Text, { variant: "secondary", className: "mt-1 text-sm", children: subtitle }))] }), !cta && viewMoreHref && (_jsx(Link, { href: viewMoreHref, className: "text-sm font-medium text-[var(--appkit-color-primary)] hover:underline", children: viewMoreLabel }))] }), showFeaturedChip && !isLoading && (_jsxs("div", { className: "mb-4 flex flex-wrap gap-2", children: [_jsx(BrandFilterChip, { label: "All", active: activeFilter === "all", onClick: () => setActiveFilter("all") }), _jsx(BrandFilterChip, { label: "Featured", active: activeFilter === "featured", onClick: () => setActiveFilter(activeFilter === "featured" ? "all" : "featured") })] })), isLoading ? (_jsx("div", { className: "flex gap-3 overflow-hidden", children: Array.from({ length: 8 }).map((_, i) => (_jsx("div", { className: "h-32 w-36 flex-none animate-pulse rounded-xl bg-zinc-200 dark:bg-slate-700 md:h-40" }, i))) })) : (_jsx(HorizontalScroller, { items: brands, renderItem: (brand) => _jsx(BrandLogo, { brand: brand }), keyExtractor: (brand) => brand.id, gap: 12, showArrows: true, showScrollbar: false, loop: true })), cta && !isLoading && (_jsx("div", { className: "mt-6 text-center", children: _jsx(Link, { href: cta.href, className: CTA_CLASSES[cta.variant], children: cta.label }) }))] }) }));
|
|
43
43
|
}
|
|
@@ -61,7 +61,7 @@ export function CustomCardsSection(config) {
|
|
|
61
61
|
}
|
|
62
62
|
// Row layout: horizontal scroller
|
|
63
63
|
if (layout === "row") {
|
|
64
|
-
return (_jsx(Section, { className: `py-12 ${themed.bgPrimary}`, children: _jsxs("div", { className: CLS_CONTAINER, children: [_jsx(SectionHeader, { title: title }), _jsx(HorizontalScroller, { gap: 16, showArrows: true, snapToItems: true, children: cards.map((card) => (_jsx("div", { className: "w-72 flex-shrink-0", children: _jsx(CardItem, { card: card }) }, card.id))) })] }) }));
|
|
64
|
+
return (_jsx(Section, { className: `py-12 ${themed.bgPrimary}`, children: _jsxs("div", { className: CLS_CONTAINER, children: [_jsx(SectionHeader, { title: title }), _jsx(HorizontalScroller, { gap: 16, showArrows: true, snapToItems: true, loop: true, children: cards.map((card) => (_jsx("div", { className: "w-72 flex-shrink-0", children: _jsx(CardItem, { card: card }) }, card.id))) })] }) }));
|
|
65
65
|
}
|
|
66
66
|
// Masonry layout: CSS columns
|
|
67
67
|
if (layout === "masonry") {
|
|
@@ -9,5 +9,5 @@ export function CustomerReviewsSection({ title, subtitle, items, renderItem, vie
|
|
|
9
9
|
}
|
|
10
10
|
if (items.length === 0)
|
|
11
11
|
return null;
|
|
12
|
-
return (_jsx(Section, { className: `py-12 ${themed.bgPrimary} ${className}`, children: _jsxs("div", { className: "w-full max-w-7xl mx-auto px-4 md:px-8", "data-section": "customerreviewssection-div-317", children: [_jsxs("div", { className: "text-center mb-10", "data-section": "customerreviewssection-div-318", children: [_jsx(Heading, { level: 2, className: `text-3xl md:text-4xl font-bold ${themed.textPrimary} mb-3`, children: title }), subtitle && (_jsx(Text, { className: `text-base ${themed.textSecondary}`, children: subtitle }))] }), _jsx(HorizontalScroller, { items: items, renderItem: (item) => renderItem(item), perView: THEME_CONSTANTS.carousel.perView.reviews, gap: 24, autoScroll: true, autoScrollInterval: 4500, pauseOnHover: true, snapToItems: true, showArrows: true, keyExtractor: keyExtractor }), viewMoreHref && (_jsx("div", { className: "text-center mt-8", "data-section": "customerreviewssection-div-319", children: _jsx(TextLink, { href: viewMoreHref, className: "text-sm font-medium text-primary hover:text-primary/80", children: viewMoreLabel }) }))] }) }));
|
|
12
|
+
return (_jsx(Section, { className: `py-12 ${themed.bgPrimary} ${className}`, children: _jsxs("div", { className: "w-full max-w-7xl mx-auto px-4 md:px-8", "data-section": "customerreviewssection-div-317", children: [_jsxs("div", { className: "text-center mb-10", "data-section": "customerreviewssection-div-318", children: [_jsx(Heading, { level: 2, className: `text-3xl md:text-4xl font-bold ${themed.textPrimary} mb-3`, children: title }), subtitle && (_jsx(Text, { className: `text-base ${themed.textSecondary}`, children: subtitle }))] }), _jsx(HorizontalScroller, { items: items, renderItem: (item) => renderItem(item), perView: THEME_CONSTANTS.carousel.perView.reviews, gap: 24, autoScroll: true, autoScrollInterval: 4500, pauseOnHover: true, snapToItems: true, showArrows: true, keyExtractor: keyExtractor, loop: true }), viewMoreHref && (_jsx("div", { className: "text-center mt-8", "data-section": "customerreviewssection-div-319", children: _jsx(TextLink, { href: viewMoreHref, className: "text-sm font-medium text-primary hover:text-primary/80", children: viewMoreLabel }) }))] }) }));
|
|
13
13
|
}
|
|
@@ -14,5 +14,5 @@ export function FeaturedResultsSection({ title, subtitle, pillLabel, ornamentLab
|
|
|
14
14
|
afterImage: item.afterImage,
|
|
15
15
|
caption: item.caption ?? "",
|
|
16
16
|
sortOrder: item.sortOrder ?? i,
|
|
17
|
-
} }, item.id ?? `result-${i}`) })), keyExtractor: (item, i) => item.id ?? `result-${i}`, perView: THEME_CONSTANTS.carousel.perView.cards, gap: 24, minItemWidth: 260, snapToItems: true, showArrows: true, showFadeEdges: true }) })] }));
|
|
17
|
+
} }, item.id ?? `result-${i}`) })), keyExtractor: (item, i) => item.id ?? `result-${i}`, perView: THEME_CONSTANTS.carousel.perView.cards, gap: 24, minItemWidth: 260, snapToItems: true, showArrows: true, showFadeEdges: true, loop: true }) })] }));
|
|
18
18
|
}
|
|
@@ -36,7 +36,7 @@ export function SectionCarousel({ title, description, headingVariant = "editoria
|
|
|
36
36
|
headingClass,
|
|
37
37
|
]
|
|
38
38
|
.filter(Boolean)
|
|
39
|
-
.join(" "), children: title }), headingVariant === "editorial" && (_jsxs("div", { className: `${flex.center} gap-2 mt-1 text-zinc-400 dark:text-zinc-500 text-xs select-none`, "aria-hidden": "true", "data-section": "sectioncarousel-div-355", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { className: "text-xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `text-base ${descVariant} mt-2`, children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop:
|
|
39
|
+
.join(" "), children: title }), headingVariant === "editorial" && (_jsxs("div", { className: `${flex.center} gap-2 mt-1 text-zinc-400 dark:text-zinc-500 text-xs select-none`, "aria-hidden": "true", "data-section": "sectioncarousel-div-355", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { className: "text-xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `text-base ${descVariant} mt-2`, children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop: rows === 1, keyExtractor: keyExtractor, rows: rows, minItemWidth: minItemWidth, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx("div", { className: "mt-6 flex justify-center", "data-section": "sectioncarousel-div-356", children: _jsx(TextLink, { href: viewMoreHref, className: `inline-flex items-center gap-1.5 rounded-lg border px-6 py-2.5 text-sm font-medium transition-colors ${useLightText
|
|
40
40
|
? "border-white/40 text-white hover:bg-white/10"
|
|
41
41
|
: "border-zinc-200 dark:border-zinc-700 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-zinc-800"}`, children: viewMoreLabel }) }))] })] }));
|
|
42
42
|
}
|
|
@@ -61,5 +61,5 @@ export function ShopByCategorySection({ title = "Shop by Category", subtitle, li
|
|
|
61
61
|
if (!cat)
|
|
62
62
|
return null;
|
|
63
63
|
return (_jsx(FilterChip, { label: cat.name, active: activeFilter === id, onClick: () => setActiveFilter(activeFilter === id ? "all" : id) }, id));
|
|
64
|
-
})] })), isLoading ? (_jsx("div", { className: "flex gap-3 overflow-hidden px-1", children: Array.from({ length: 6 }).map((_, i) => (_jsx("div", { className: "flex-none h-[104px] w-[108px] animate-pulse rounded-xl bg-zinc-200 dark:bg-slate-700" }, i))) })) : (_jsx(HorizontalScroller, { items: categories, renderItem: (cat) => _jsx(CategoryChip, { category: cat }), perView: THEME_CONSTANTS.carousel.perView.standard, gap: 16, keyExtractor: (cat) => cat.id, autoScroll: true, autoScrollInterval: 3500, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), cta && !isLoading && (_jsx("div", { className: "mt-6 text-center", children: _jsx(Link, { href: cta.href, className: CTA_CLASSES[cta.variant], children: cta.label }) })), !cta && viewMoreHref && !isLoading && (_jsx("div", { className: "mt-6 text-center", children: _jsx(Link, { href: viewMoreHref, className: "inline-flex items-center gap-1 text-sm font-medium text-[var(--appkit-color-primary)] hover:opacity-80", children: viewMoreLabel }) }))] }) }));
|
|
64
|
+
})] })), isLoading ? (_jsx("div", { className: "flex gap-3 overflow-hidden px-1", children: Array.from({ length: 6 }).map((_, i) => (_jsx("div", { className: "flex-none h-[104px] w-[108px] animate-pulse rounded-xl bg-zinc-200 dark:bg-slate-700" }, i))) })) : (_jsx(HorizontalScroller, { items: categories, renderItem: (cat) => _jsx(CategoryChip, { category: cat }), perView: THEME_CONSTANTS.carousel.perView.standard, gap: 16, keyExtractor: (cat) => cat.id, autoScroll: true, autoScrollInterval: 3500, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, loop: true, pauseOnHover: true })), cta && !isLoading && (_jsx("div", { className: "mt-6 text-center", children: _jsx(Link, { href: cta.href, className: CTA_CLASSES[cta.variant], children: cta.label }) })), !cta && viewMoreHref && !isLoading && (_jsx("div", { className: "mt-6 text-center", children: _jsx(Link, { href: viewMoreHref, className: "inline-flex items-center gap-1 text-sm font-medium text-[var(--appkit-color-primary)] hover:opacity-80", children: viewMoreLabel }) }))] }) }));
|
|
65
65
|
}
|
|
@@ -42,7 +42,7 @@ export function LiveItemsIndexListing({ initialData }) {
|
|
|
42
42
|
const { categories } = useCategoryTree();
|
|
43
43
|
const categoryOptions = categoriesToFacetOptions(categories);
|
|
44
44
|
const wishlistedIds = new Set(localWishlist.items.filter((i) => i.type === "live").map((i) => i.itemId));
|
|
45
|
-
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
45
|
+
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onResetAll, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
46
46
|
const openFilters = useCallback(() => {
|
|
47
47
|
onFilterReset();
|
|
48
48
|
setFilterOpen(true);
|
|
@@ -52,10 +52,9 @@ export function LiveItemsIndexListing({ initialData }) {
|
|
|
52
52
|
setFilterOpen(false);
|
|
53
53
|
}, [onFilterApply]);
|
|
54
54
|
const resetAll = useCallback(() => {
|
|
55
|
-
|
|
56
|
-
onFilterClear();
|
|
55
|
+
onResetAll({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
|
|
57
56
|
setSearchInput("");
|
|
58
|
-
}, [
|
|
57
|
+
}, [onResetAll]);
|
|
59
58
|
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
60
59
|
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
61
60
|
filterActiveCount > 0;
|
|
@@ -46,7 +46,7 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
|
|
|
46
46
|
const categoryOptions = categoriesToFacetOptions(categories);
|
|
47
47
|
const { brandOptions } = useBrands();
|
|
48
48
|
const wishlistedIds = new Set(localWishlist.items.filter((i) => i.type === "preorder").map((i) => i.itemId));
|
|
49
|
-
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
49
|
+
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onResetAll, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
50
50
|
const openFilters = useCallback(() => {
|
|
51
51
|
onFilterReset();
|
|
52
52
|
setFilterOpen(true);
|
|
@@ -56,10 +56,9 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
|
|
|
56
56
|
setFilterOpen(false);
|
|
57
57
|
}, [onFilterApply]);
|
|
58
58
|
const resetAll = useCallback(() => {
|
|
59
|
-
|
|
60
|
-
onFilterClear();
|
|
59
|
+
onResetAll({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "", [TABLE_KEYS.SHOW_CLOSED]: "" });
|
|
61
60
|
setSearchInput("");
|
|
62
|
-
}, [
|
|
61
|
+
}, [onResetAll]);
|
|
63
62
|
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
64
63
|
table.get(TABLE_KEYS.SHOW_CLOSED) === "true" ||
|
|
65
64
|
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
@@ -40,7 +40,7 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
|
|
|
40
40
|
const { categories } = useCategoryTree();
|
|
41
41
|
const categoryOptions = categoriesToFacetOptions(categories);
|
|
42
42
|
const { brandOptions } = useBrands();
|
|
43
|
-
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
43
|
+
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onResetAll, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
44
44
|
const openFilters = useCallback(() => {
|
|
45
45
|
onFilterReset();
|
|
46
46
|
setFilterOpen(true);
|
|
@@ -50,10 +50,9 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
|
|
|
50
50
|
setFilterOpen(false);
|
|
51
51
|
}, [onFilterApply]);
|
|
52
52
|
const resetAll = useCallback(() => {
|
|
53
|
-
|
|
54
|
-
onFilterClear();
|
|
53
|
+
onResetAll({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "", [TABLE_KEYS.SHOW_ENDED]: "" });
|
|
55
54
|
setSearchInput("");
|
|
56
|
-
}, [
|
|
55
|
+
}, [onResetAll]);
|
|
57
56
|
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
58
57
|
table.get(TABLE_KEYS.SHOW_ENDED) === "true" ||
|
|
59
58
|
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|