@mohasinac/appkit 2.3.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +12 -8
- package/dist/client.js +7 -4
- package/dist/constants/api-endpoints.d.ts +4 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/core/contact-submissions.repository.d.ts +32 -0
- package/dist/core/contact-submissions.repository.js +49 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +1 -0
- package/dist/features/about/components/HowPayoutsWorkView.js +1 -1
- package/dist/features/account/components/AddressFilters.d.ts +5 -0
- package/dist/features/account/components/AddressFilters.js +20 -0
- package/dist/features/account/components/AddressesIndexListing.d.ts +6 -0
- package/dist/features/account/components/AddressesIndexListing.js +51 -0
- package/dist/features/account/components/UserSidebar.d.ts +7 -3
- package/dist/features/account/components/UserSidebar.js +55 -7
- package/dist/features/account/hooks/useAddresses.d.ts +7 -0
- package/dist/features/account/hooks/useAddresses.js +12 -1
- package/dist/features/admin/actions/admin-read-actions.d.ts +12 -0
- package/dist/features/admin/actions/admin-read-actions.js +18 -0
- package/dist/features/admin/components/AdminBlogView.js +26 -2
- package/dist/features/admin/components/AdminCarouselView.js +18 -2
- package/dist/features/admin/components/AdminCategoriesView.js +27 -2
- package/dist/features/admin/components/AdminContactView.d.ts +4 -0
- package/dist/features/admin/components/AdminContactView.js +50 -0
- package/dist/features/admin/components/AdminFaqsView.js +19 -2
- package/dist/features/admin/components/AdminListingScaffold.d.ts +11 -2
- package/dist/features/admin/components/AdminListingScaffold.js +14 -3
- package/dist/features/admin/components/AdminNewsletterView.d.ts +4 -0
- package/dist/features/admin/components/AdminNewsletterView.js +50 -0
- package/dist/features/admin/components/AdminProductsView.js +30 -2
- package/dist/features/admin/components/AdminReviewsView.js +26 -2
- package/dist/features/admin/components/AdminStoresView.js +17 -2
- package/dist/features/admin/components/AdminUsersView.js +27 -2
- package/dist/features/admin/components/DataTable.d.ts +2 -1
- package/dist/features/admin/components/DataTable.js +18 -4
- package/dist/features/admin/components/index.d.ts +6 -0
- package/dist/features/admin/components/index.js +3 -0
- package/dist/features/admin/hooks/useAdminListingData.d.ts +3 -1
- package/dist/features/admin/hooks/useAdminListingData.js +12 -7
- package/dist/features/admin/server.d.ts +3 -0
- package/dist/features/admin/server.js +2 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +93 -47
- package/dist/features/auctions/components/AuctionFilters.d.ts +8 -0
- package/dist/features/auctions/components/AuctionFilters.js +12 -0
- package/dist/features/auctions/components/AuctionsListView.d.ts +6 -1
- package/dist/features/auctions/components/AuctionsListView.js +37 -5
- package/dist/features/auctions/schemas/index.d.ts +4 -4
- package/dist/features/blog/components/BlogFeaturedCard.d.ts +1 -7
- package/dist/features/blog/components/BlogFeaturedCard.js +4 -5
- package/dist/features/blog/components/BlogFilters.js +2 -1
- package/dist/features/blog/components/BlogIndexListing.js +14 -9
- package/dist/features/blog/components/BlogIndexPageView.d.ts +6 -1
- package/dist/features/blog/components/BlogIndexPageView.js +10 -2
- package/dist/features/blog/components/BlogListView.d.ts +2 -1
- package/dist/features/blog/components/BlogListView.js +10 -3
- package/dist/features/categories/components/CategoriesIndexListing.d.ts +1 -1
- package/dist/features/categories/components/CategoriesIndexListing.js +41 -38
- package/dist/features/categories/components/CategoriesIndexPageView.d.ts +6 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +41 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +13 -6
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -0
- package/dist/features/categories/components/CategoryDetailTabs.js +17 -0
- package/dist/features/categories/components/CategoryFilters.js +2 -1
- package/dist/features/categories/components/CategoryGrid.d.ts +2 -1
- package/dist/features/categories/components/CategoryGrid.js +8 -6
- package/dist/features/categories/components/CategoryProductsListing.js +22 -11
- package/dist/features/categories/components/index.d.ts +2 -0
- package/dist/features/categories/components/index.js +1 -0
- package/dist/features/categories/hooks/useCategories.d.ts +20 -0
- package/dist/features/categories/hooks/useCategories.js +50 -1
- package/dist/features/categories/hooks/useCategoryTree.d.ts +17 -0
- package/dist/features/categories/hooks/useCategoryTree.js +65 -0
- package/dist/features/events/components/AdminEventEditorView.d.ts +6 -0
- package/dist/features/events/components/AdminEventEditorView.js +203 -0
- package/dist/features/events/components/AdminEventsView.js +28 -2
- package/dist/features/events/components/EventCard.js +4 -2
- package/dist/features/events/components/EventFilters.js +2 -1
- package/dist/features/events/components/EventsIndexListing.js +40 -10
- package/dist/features/events/components/EventsListPageView.d.ts +6 -1
- package/dist/features/events/components/EventsListPageView.js +40 -7
- package/dist/features/events/components/index.d.ts +2 -0
- package/dist/features/events/components/index.js +1 -0
- package/dist/features/events/hooks/useEvents.js +2 -0
- package/dist/features/events/types/index.d.ts +1 -0
- package/dist/features/homepage/components/BlogArticlesSection.js +1 -1
- package/dist/features/homepage/components/EventsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedAuctionsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedPreOrdersSection.js +1 -1
- package/dist/features/homepage/components/FeaturedProductsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedStoresSection.js +1 -1
- package/dist/features/homepage/components/HeroCarousel.js +1 -1
- package/dist/features/homepage/components/MarketplaceHomepageView.js +27 -17
- package/dist/features/homepage/components/SectionCarousel.js +4 -4
- package/dist/features/homepage/components/ShopByCategorySection.js +2 -2
- package/dist/features/homepage/schemas/firestore.d.ts +1 -1
- package/dist/features/homepage/schemas/firestore.js +2 -1
- package/dist/features/layout/AppLayoutShell.d.ts +6 -2
- package/dist/features/layout/AppLayoutShell.js +7 -3
- package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +3 -1
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +6 -2
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +80 -12
- package/dist/features/pre-orders/components/PreOrderFilters.d.ts +8 -0
- package/dist/features/pre-orders/components/PreOrderFilters.js +21 -0
- package/dist/features/pre-orders/components/PreOrdersIndexListing.d.ts +2 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +69 -10
- package/dist/features/pre-orders/components/PreOrdersListView.d.ts +6 -1
- package/dist/features/pre-orders/components/PreOrdersListView.js +26 -7
- package/dist/features/pre-orders/components/index.d.ts +2 -0
- package/dist/features/pre-orders/components/index.js +1 -0
- package/dist/features/products/components/AuctionsIndexListing.d.ts +2 -1
- package/dist/features/products/components/AuctionsIndexListing.js +61 -9
- package/dist/features/products/components/InteractiveProductCard.d.ts +2 -4
- package/dist/features/products/components/InteractiveProductCard.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.d.ts +1 -1
- package/dist/features/products/components/ProductDetailPageView.js +116 -25
- package/dist/features/products/components/ProductFilters.d.ts +6 -11
- package/dist/features/products/components/ProductFilters.js +5 -3
- package/dist/features/products/components/ProductGrid.d.ts +8 -2
- package/dist/features/products/components/ProductGrid.js +20 -5
- package/dist/features/products/components/ProductTabsShell.d.ts +3 -11
- package/dist/features/products/components/ProductTabsShell.js +14 -14
- package/dist/features/products/components/ProductsIndexListing.js +73 -9
- package/dist/features/products/components/ProductsIndexPageView.d.ts +6 -1
- package/dist/features/products/components/ProductsIndexPageView.js +39 -6
- package/dist/features/products/components/RelatedProductsCarousel.d.ts +7 -0
- package/dist/features/products/components/RelatedProductsCarousel.js +11 -0
- package/dist/features/products/components/index.d.ts +1 -0
- package/dist/features/products/components/index.js +1 -0
- package/dist/features/products/hooks/useProducts.js +16 -0
- package/dist/features/products/repository/products.repository.d.ts +8 -0
- package/dist/features/products/repository/products.repository.js +2 -0
- package/dist/features/products/schemas/index.d.ts +8 -8
- package/dist/features/products/types/index.d.ts +12 -0
- package/dist/features/promotions/components/CouponsIndexListing.d.ts +9 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +86 -0
- package/dist/features/promotions/components/PromotionsView.d.ts +11 -5
- package/dist/features/promotions/components/PromotionsView.js +6 -1
- package/dist/features/promotions/components/index.d.ts +4 -2
- package/dist/features/promotions/components/index.js +2 -1
- package/dist/features/reviews/components/ReviewDetailPageView.d.ts +4 -0
- package/dist/features/reviews/components/ReviewDetailPageView.js +20 -0
- package/dist/features/reviews/components/ReviewDetailShell.d.ts +7 -0
- package/dist/features/reviews/components/ReviewDetailShell.js +80 -0
- package/dist/features/reviews/components/ReviewFilters.d.ts +3 -3
- package/dist/features/reviews/components/ReviewFilters.js +5 -4
- package/dist/features/reviews/components/ReviewsIndexListing.d.ts +4 -3
- package/dist/features/reviews/components/ReviewsIndexListing.js +35 -51
- package/dist/features/reviews/components/ReviewsIndexPageView.d.ts +6 -1
- package/dist/features/reviews/components/ReviewsIndexPageView.js +49 -3
- package/dist/features/reviews/components/ReviewsList.js +9 -1
- package/dist/features/reviews/components/index.d.ts +1 -0
- package/dist/features/reviews/hooks/useReviews.js +15 -1
- package/dist/features/reviews/types/index.d.ts +6 -1
- package/dist/features/seller/components/SellerSidebar.d.ts +8 -4
- package/dist/features/seller/components/SellerSidebar.js +6 -4
- package/dist/features/seller/components/index.d.ts +30 -0
- package/dist/features/seller/components/index.js +17 -0
- package/dist/features/seller/hooks/useSellerStore.d.ts +2 -0
- package/dist/features/seller/hooks/useSellerStore.js +2 -0
- package/dist/features/seller/permission-map.d.ts +4 -2
- package/dist/features/seller/permission-map.js +16 -14
- package/dist/features/seller/schemas/index.d.ts +2 -2
- package/dist/features/stores/api/[storeSlug]/reviews/route.d.ts +1 -1
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +24 -19
- package/dist/features/stores/components/InteractiveStoreCard.d.ts +0 -5
- package/dist/features/stores/components/InteractiveStoreCard.js +9 -9
- package/dist/features/stores/components/StoreAuctionsListing.js +27 -9
- package/dist/features/stores/components/StoreDetailLayoutView.js +2 -0
- package/dist/features/stores/components/StoreFilters.d.ts +5 -0
- package/dist/features/stores/components/StoreFilters.js +20 -0
- package/dist/features/stores/components/StoreHeader.js +2 -2
- package/dist/features/stores/components/StorePreOrdersListing.d.ts +5 -0
- package/dist/features/stores/components/StorePreOrdersListing.js +40 -0
- package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -0
- package/dist/features/stores/components/StorePreOrdersPageView.js +21 -0
- package/dist/features/stores/components/StoreProductsListing.js +21 -11
- package/dist/features/stores/components/StoreReviewsListing.js +2 -7
- package/dist/features/stores/components/StoresIndexListing.js +42 -8
- package/dist/features/stores/components/StoresIndexPageView.d.ts +6 -1
- package/dist/features/stores/components/StoresIndexPageView.js +9 -2
- package/dist/features/stores/components/index.d.ts +3 -0
- package/dist/features/stores/components/index.js +1 -0
- package/dist/features/stores/hooks/useStores.d.ts +7 -1
- package/dist/features/stores/hooks/useStores.js +16 -3
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/stores/types/index.d.ts +3 -0
- package/dist/features/wishlist/hooks/useGuestWishlist.d.ts +20 -0
- package/dist/features/wishlist/hooks/useGuestWishlist.js +49 -0
- package/dist/features/wishlist/hooks/useWishlistCount.d.ts +7 -0
- package/dist/features/wishlist/hooks/useWishlistCount.js +31 -0
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +56 -0
- package/dist/features/wishlist/hooks/useWishlistWithGuest.js +57 -0
- package/dist/features/wishlist/index.d.ts +3 -0
- package/dist/features/wishlist/index.js +3 -0
- package/dist/features/wishlist/utils/guest-wishlist.d.ts +22 -0
- package/dist/features/wishlist/utils/guest-wishlist.js +70 -0
- package/dist/index.d.ts +50 -1
- package/dist/index.js +63 -1
- package/dist/next/routing/route-map.d.ts +70 -36
- package/dist/next/routing/route-map.js +30 -22
- package/dist/seed/addresses-seed-data.js +62 -261
- package/dist/seed/beyblade-seed-data.d.ts +7 -0
- package/dist/seed/beyblade-seed-data.js +947 -0
- package/dist/seed/bids-seed-data.d.ts +10 -2
- package/dist/seed/bids-seed-data.js +220 -1071
- package/dist/seed/blog-posts-seed-data.d.ts +2 -2
- package/dist/seed/blog-posts-seed-data.js +455 -117
- package/dist/seed/cart-seed-data.d.ts +9 -9
- package/dist/seed/cart-seed-data.js +73 -74
- package/dist/seed/coupons-seed-data.d.ts +3 -4
- package/dist/seed/coupons-seed-data.js +3 -509
- package/dist/seed/events-seed-data.d.ts +2 -2
- package/dist/seed/events-seed-data.js +315 -476
- package/dist/seed/faq-seed-data.d.ts +18 -41
- package/dist/seed/faq-seed-data.js +1059 -1172
- package/dist/seed/hot-wheels-seed-data.d.ts +7 -0
- package/dist/seed/hot-wheels-seed-data.js +1365 -0
- package/dist/seed/index.d.ts +6 -1
- package/dist/seed/index.js +6 -1
- package/dist/seed/pokemon-carousel-slides-seed-data.d.ts +4 -2
- package/dist/seed/pokemon-carousel-slides-seed-data.js +152 -268
- package/dist/seed/pokemon-categories-seed-data.d.ts +18 -21
- package/dist/seed/pokemon-categories-seed-data.js +424 -1004
- package/dist/seed/pokemon-coupons-seed-data.d.ts +6 -0
- package/dist/seed/pokemon-coupons-seed-data.js +465 -0
- package/dist/seed/pokemon-homepage-sections-seed-data.d.ts +3 -2
- package/dist/seed/pokemon-homepage-sections-seed-data.js +67 -289
- package/dist/seed/pokemon-products-seed-data.js +662 -0
- package/dist/seed/pokemon-seed-bundle.d.ts +32 -11
- package/dist/seed/pokemon-seed-bundle.js +41 -11
- package/dist/seed/pokemon-stores-seed-data.d.ts +2 -3
- package/dist/seed/pokemon-stores-seed-data.js +56 -31
- package/dist/seed/pokemon-users-seed-data.d.ts +2 -2
- package/dist/seed/pokemon-users-seed-data.js +245 -261
- package/dist/seed/reviews-seed-data.d.ts +17 -2
- package/dist/seed/reviews-seed-data.js +519 -483
- package/dist/seed/site-settings-seed-data.js +14 -14
- package/dist/seed/store-addresses-seed-data.js +68 -50
- package/dist/seed/transformers-seed-data.d.ts +7 -0
- package/dist/seed/transformers-seed-data.js +510 -0
- package/dist/seed/wishlists-seed-data.d.ts +5 -1
- package/dist/seed/wishlists-seed-data.js +82 -4
- package/dist/server.d.ts +1 -0
- package/dist/server.js +2 -0
- package/dist/tokens/index.d.ts +6 -0
- package/dist/tokens/index.js +2 -0
- package/dist/ui/components/BaseListingCard.js +24 -26
- package/dist/ui/components/BaseListingCard.style.css +5 -5
- package/dist/ui/components/HorizontalScroller.d.ts +1 -1
- package/dist/ui/components/HorizontalScroller.js +19 -5
- package/dist/ui/components/SideDrawer.style.css +3 -11
- package/dist/ui/rich-text/RichText.js +19 -1
- package/package.json +1 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
5
|
+
import { Alert, Button, Form, FormActions, Input, Select, StackedViewShell, Text, Toggle, } from "../../../ui";
|
|
6
|
+
import { apiClient } from "../../../http";
|
|
7
|
+
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
function toLocalDatetime(iso) {
|
|
9
|
+
if (!iso)
|
|
10
|
+
return "";
|
|
11
|
+
try {
|
|
12
|
+
const d = new Date(iso);
|
|
13
|
+
const offset = d.getTimezoneOffset();
|
|
14
|
+
const local = new Date(d.getTime() - offset * 60000);
|
|
15
|
+
return local.toISOString().slice(0, 16);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function toISOString(local) {
|
|
22
|
+
if (!local)
|
|
23
|
+
return "";
|
|
24
|
+
try {
|
|
25
|
+
return new Date(local).toISOString();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const EVENT_TYPE_OPTIONS = [
|
|
32
|
+
{ label: "Sale", value: "sale" },
|
|
33
|
+
{ label: "Offer / Coupon", value: "offer" },
|
|
34
|
+
{ label: "Poll", value: "poll" },
|
|
35
|
+
{ label: "Survey", value: "survey" },
|
|
36
|
+
{ label: "Feedback", value: "feedback" },
|
|
37
|
+
];
|
|
38
|
+
const EVENT_STATUS_OPTIONS = [
|
|
39
|
+
{ label: "Draft", value: "draft" },
|
|
40
|
+
{ label: "Active", value: "active" },
|
|
41
|
+
{ label: "Paused", value: "paused" },
|
|
42
|
+
{ label: "Ended", value: "ended" },
|
|
43
|
+
];
|
|
44
|
+
const POLL_VISIBILITY_OPTIONS = [
|
|
45
|
+
{ label: "Always visible", value: "always" },
|
|
46
|
+
{ label: "After voting", value: "after_vote" },
|
|
47
|
+
{ label: "After event ends", value: "after_end" },
|
|
48
|
+
];
|
|
49
|
+
export function AdminEventEditorView({ eventId, onSaved, ...rest }) {
|
|
50
|
+
const [type, setType] = React.useState("sale");
|
|
51
|
+
const [title, setTitle] = React.useState("");
|
|
52
|
+
const [description, setDescription] = React.useState("");
|
|
53
|
+
const [startsAt, setStartsAt] = React.useState("");
|
|
54
|
+
const [endsAt, setEndsAt] = React.useState("");
|
|
55
|
+
const [coverImageUrl, setCoverImageUrl] = React.useState("");
|
|
56
|
+
const [status, setStatus] = React.useState("draft");
|
|
57
|
+
const [discountPercent, setDiscountPercent] = React.useState("10");
|
|
58
|
+
const [saleBannerText, setSaleBannerText] = React.useState("");
|
|
59
|
+
const [couponId, setCouponId] = React.useState("");
|
|
60
|
+
const [displayCode, setDisplayCode] = React.useState("");
|
|
61
|
+
const [offerBannerText, setOfferBannerText] = React.useState("");
|
|
62
|
+
const [pollOptions, setPollOptions] = React.useState([
|
|
63
|
+
{ id: "opt1", label: "" },
|
|
64
|
+
{ id: "opt2", label: "" },
|
|
65
|
+
]);
|
|
66
|
+
const [allowMultiSelect, setAllowMultiSelect] = React.useState(false);
|
|
67
|
+
const [allowComment, setAllowComment] = React.useState(false);
|
|
68
|
+
const [resultsVisibility, setResultsVisibility] = React.useState("always");
|
|
69
|
+
const [requireLogin, setRequireLogin] = React.useState(true);
|
|
70
|
+
const [maxEntriesPerUser, setMaxEntriesPerUser] = React.useState("1");
|
|
71
|
+
const [hasLeaderboard, setHasLeaderboard] = React.useState(false);
|
|
72
|
+
const [anonymous, setAnonymous] = React.useState(false);
|
|
73
|
+
const [saveMessage, setSaveMessage] = React.useState(null);
|
|
74
|
+
const eventQuery = useQuery({
|
|
75
|
+
queryKey: ["admin-event-by-id", eventId],
|
|
76
|
+
queryFn: () => apiClient.get(`${ADMIN_ENDPOINTS.EVENTS}/${eventId}`),
|
|
77
|
+
enabled: Boolean(eventId),
|
|
78
|
+
staleTime: 15000,
|
|
79
|
+
});
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
const event = eventQuery.data?.data ?? eventQuery.data;
|
|
82
|
+
if (!event?.id)
|
|
83
|
+
return;
|
|
84
|
+
setType(event.type || "sale");
|
|
85
|
+
setTitle(event.title || "");
|
|
86
|
+
setDescription(event.description || "");
|
|
87
|
+
setStartsAt(toLocalDatetime(event.startsAt));
|
|
88
|
+
setEndsAt(toLocalDatetime(event.endsAt));
|
|
89
|
+
setCoverImageUrl(event.coverImageUrl || event.coverImage?.url || "");
|
|
90
|
+
setStatus(event.status || "draft");
|
|
91
|
+
if (event.saleConfig) {
|
|
92
|
+
setDiscountPercent(String(event.saleConfig.discountPercent ?? 10));
|
|
93
|
+
setSaleBannerText(event.saleConfig.bannerText || "");
|
|
94
|
+
}
|
|
95
|
+
if (event.offerConfig) {
|
|
96
|
+
setCouponId(event.offerConfig.couponId || "");
|
|
97
|
+
setDisplayCode(event.offerConfig.displayCode || "");
|
|
98
|
+
setOfferBannerText(event.offerConfig.bannerText || "");
|
|
99
|
+
}
|
|
100
|
+
if (event.pollConfig) {
|
|
101
|
+
if (event.pollConfig.options?.length)
|
|
102
|
+
setPollOptions(event.pollConfig.options);
|
|
103
|
+
setAllowMultiSelect(Boolean(event.pollConfig.allowMultiSelect));
|
|
104
|
+
setAllowComment(Boolean(event.pollConfig.allowComment));
|
|
105
|
+
setResultsVisibility(event.pollConfig.resultsVisibility || "always");
|
|
106
|
+
}
|
|
107
|
+
if (event.surveyConfig) {
|
|
108
|
+
setRequireLogin(event.surveyConfig.requireLogin !== false);
|
|
109
|
+
setMaxEntriesPerUser(String(event.surveyConfig.maxEntriesPerUser ?? 1));
|
|
110
|
+
setHasLeaderboard(Boolean(event.surveyConfig.hasLeaderboard));
|
|
111
|
+
}
|
|
112
|
+
if (event.feedbackConfig) {
|
|
113
|
+
setAnonymous(Boolean(event.feedbackConfig.anonymous));
|
|
114
|
+
}
|
|
115
|
+
}, [eventQuery.data]);
|
|
116
|
+
const saveMutation = useMutation({
|
|
117
|
+
mutationFn: async () => {
|
|
118
|
+
const buildTypeConfig = () => {
|
|
119
|
+
if (type === "sale")
|
|
120
|
+
return {
|
|
121
|
+
saleConfig: {
|
|
122
|
+
discountPercent: Number(discountPercent) || 10,
|
|
123
|
+
bannerText: saleBannerText || undefined,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
if (type === "offer")
|
|
127
|
+
return {
|
|
128
|
+
offerConfig: { couponId, displayCode, bannerText: offerBannerText || undefined },
|
|
129
|
+
};
|
|
130
|
+
if (type === "poll")
|
|
131
|
+
return {
|
|
132
|
+
pollConfig: {
|
|
133
|
+
options: pollOptions.filter((o) => o.label.trim()),
|
|
134
|
+
allowMultiSelect,
|
|
135
|
+
allowComment,
|
|
136
|
+
resultsVisibility,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
if (type === "survey")
|
|
140
|
+
return {
|
|
141
|
+
surveyConfig: {
|
|
142
|
+
requireLogin,
|
|
143
|
+
maxEntriesPerUser: Number(maxEntriesPerUser) || 1,
|
|
144
|
+
hasLeaderboard,
|
|
145
|
+
hasPointSystem: false,
|
|
146
|
+
entryReviewRequired: false,
|
|
147
|
+
formFields: [],
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
if (type === "feedback")
|
|
151
|
+
return {
|
|
152
|
+
feedbackConfig: { anonymous, formFields: [] },
|
|
153
|
+
};
|
|
154
|
+
return {};
|
|
155
|
+
};
|
|
156
|
+
const payload = {
|
|
157
|
+
type,
|
|
158
|
+
title,
|
|
159
|
+
description,
|
|
160
|
+
startsAt: toISOString(startsAt),
|
|
161
|
+
endsAt: toISOString(endsAt),
|
|
162
|
+
coverImageUrl: coverImageUrl || undefined,
|
|
163
|
+
...buildTypeConfig(),
|
|
164
|
+
};
|
|
165
|
+
if (eventId) {
|
|
166
|
+
payload.status = status;
|
|
167
|
+
await apiClient.patch(`${ADMIN_ENDPOINTS.EVENTS}/${eventId}`, payload);
|
|
168
|
+
return eventId;
|
|
169
|
+
}
|
|
170
|
+
const created = await apiClient.post(ADMIN_ENDPOINTS.EVENTS, payload);
|
|
171
|
+
return created.data?.id ?? created.id ?? "";
|
|
172
|
+
},
|
|
173
|
+
onSuccess: (savedId) => {
|
|
174
|
+
setSaveMessage("Saved successfully.");
|
|
175
|
+
if (savedId)
|
|
176
|
+
onSaved?.(savedId);
|
|
177
|
+
},
|
|
178
|
+
onError: (error) => {
|
|
179
|
+
setSaveMessage(error instanceof Error ? error.message : "Save failed");
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
const addPollOption = () => {
|
|
183
|
+
setPollOptions((prev) => [...prev, { id: `opt${Date.now()}`, label: "" }]);
|
|
184
|
+
};
|
|
185
|
+
const removePollOption = (id) => {
|
|
186
|
+
setPollOptions((prev) => prev.filter((o) => o.id !== id));
|
|
187
|
+
};
|
|
188
|
+
const updatePollOption = (id, label) => {
|
|
189
|
+
setPollOptions((prev) => prev.map((o) => (o.id === id ? { ...o, label } : o)));
|
|
190
|
+
};
|
|
191
|
+
const isValid = !!title.trim() &&
|
|
192
|
+
!!startsAt &&
|
|
193
|
+
!!endsAt &&
|
|
194
|
+
(type !== "poll" || pollOptions.filter((o) => o.label.trim()).length >= 2) &&
|
|
195
|
+
(type !== "offer" || (!!couponId.trim() && !!displayCode.trim()));
|
|
196
|
+
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: eventId ? "Edit Event" : "Create Event", sections: [
|
|
197
|
+
eventQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load event", children: eventQuery.error instanceof Error ? eventQuery.error.message : "Unknown error" })) : null,
|
|
198
|
+
_jsxs(Form, { onSubmit: (e) => {
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
saveMutation.mutate();
|
|
201
|
+
}, 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("div", { children: [_jsx("label", { className: "block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Description" }), _jsx("textarea", { value: description, onChange: (e) => setDescription(e.target.value), rows: 3, className: "w-full rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary/30", 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: "rounded-xl border border-zinc-200 dark:border-slate-700 p-4 space-y-3", 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: "rounded-xl border border-zinc-200 dark:border-slate-700 p-4 space-y-3", 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: "rounded-xl border border-zinc-200 dark:border-slate-700 p-4 space-y-4", 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: "rounded-xl border border-zinc-200 dark:border-slate-700 p-4 space-y-3", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Survey configuration" }), _jsx(Input, { label: "Max entries per user", type: "number", value: maxEntriesPerUser, onChange: (e) => setMaxEntriesPerUser(e.target.value), placeholder: "1" }), _jsx(Toggle, { checked: requireLogin, onChange: setRequireLogin, label: "Require login to participate" }), _jsx(Toggle, { checked: hasLeaderboard, onChange: setHasLeaderboard, label: "Show leaderboard" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Form fields can be configured from the event detail page after saving." })] })), type === "feedback" && (_jsxs("div", { className: "rounded-xl border border-zinc-200 dark:border-slate-700 p-4 space-y-3", 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" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Form fields can be configured from the event detail page after saving." })] })), _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 }))] }),
|
|
202
|
+
] }));
|
|
203
|
+
}
|
|
@@ -7,9 +7,22 @@ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } fr
|
|
|
7
7
|
import { AdminListingScaffold } from "../../admin/components/AdminListingScaffold";
|
|
8
8
|
export function AdminEventsView({ children, ...props }) {
|
|
9
9
|
const hasChildren = React.Children.count(children) > 0;
|
|
10
|
+
const [q, setQ] = React.useState("");
|
|
11
|
+
const [statusFilter, setStatusFilter] = React.useState("");
|
|
12
|
+
const [typeFilter, setTypeFilter] = React.useState("");
|
|
13
|
+
const filterParts = [];
|
|
14
|
+
if (statusFilter && statusFilter !== "All") {
|
|
15
|
+
filterParts.push(`status==${statusFilter}`);
|
|
16
|
+
}
|
|
17
|
+
if (typeFilter && typeFilter !== "All") {
|
|
18
|
+
filterParts.push(`type==${typeFilter}`);
|
|
19
|
+
}
|
|
20
|
+
const filters = filterParts.join(",") || undefined;
|
|
10
21
|
const { rows, total, isLoading, errorMessage } = useAdminListingData({
|
|
11
|
-
queryKey: ["admin", "events", "listing"],
|
|
22
|
+
queryKey: ["admin", "events", "listing", q, filters ?? ""],
|
|
12
23
|
endpoint: ADMIN_ENDPOINTS.EVENTS,
|
|
24
|
+
filters,
|
|
25
|
+
q: q || undefined,
|
|
13
26
|
mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
|
|
14
27
|
id: toStringValue(item.id, `event-${index}`),
|
|
15
28
|
primary: toStringValue(item.title, "Untitled event"),
|
|
@@ -31,5 +44,18 @@ export function AdminEventsView({ children, ...props }) {
|
|
|
31
44
|
if (hasChildren) {
|
|
32
45
|
return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
|
|
33
46
|
}
|
|
34
|
-
return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Events", subtitle: "Manage sales events, offers, polls, surveys, and feedback campaigns.", actionLabel: "New event", searchPlaceholder: "Search events by title or type", rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No events found", resultSummary: `Showing ${rows.length} of ${total} events
|
|
47
|
+
return (_jsx(AdminListingScaffold, { portal: "admin", ...props, title: "Events", subtitle: "Manage sales events, offers, polls, surveys, and feedback campaigns.", actionLabel: "New event", actionHref: "/admin/events/new", getRowHref: (row) => `/admin/events/${row.id}/edit`, searchPlaceholder: "Search events by title or type", rows: rows, isLoading: isLoading, errorMessage: errorMessage, emptyLabel: "No events found", resultSummary: `Showing ${rows.length} of ${total} events`, onSearch: setQ, searchValue: q, filterGroups: [
|
|
48
|
+
{
|
|
49
|
+
title: "Status",
|
|
50
|
+
options: ["All", "published", "draft", "active", "ended"],
|
|
51
|
+
active: statusFilter || "All",
|
|
52
|
+
onSelect: (opt) => setStatusFilter(opt === "All" ? "" : opt),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
title: "Type",
|
|
56
|
+
options: ["All", "contest", "giveaway", "sale", "poll", "survey", "flash-sale"],
|
|
57
|
+
active: typeFilter || "All",
|
|
58
|
+
onSelect: (opt) => setTypeFilter(opt === "All" ? "" : opt),
|
|
59
|
+
},
|
|
60
|
+
] }));
|
|
35
61
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Link from "next/link";
|
|
2
3
|
import { Article, Button, Div, Heading, RichText, Span, TextLink } from "../../../ui";
|
|
3
4
|
import { THEME_CONSTANTS, LAYOUT } from "../../../tokens";
|
|
4
5
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
@@ -18,6 +19,7 @@ export function EventCard({ event, labels = {}, onParticipate, className = "", }
|
|
|
18
19
|
const endsAt = new Date(event.endsAt);
|
|
19
20
|
const msLeft = endsAt.getTime() - now.getTime();
|
|
20
21
|
const daysLeft = Math.max(0, Math.ceil(msLeft / (1000 * 60 * 60 * 24)));
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
const detailHref = String(ROUTES.PUBLIC.EVENT_DETAIL(event.id));
|
|
23
|
+
return (_jsxs(Article, { className: `group flex h-full ${LAYOUT.cardHeight.event} flex-col overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-sm transition-shadow hover:shadow-md dark:border-slate-700 dark:bg-slate-900 ${className}`, children: [_jsx(Link, { href: detailHref, className: "block flex-shrink-0", children: event.coverImageUrl ? (_jsx(Div, { className: "aspect-video overflow-hidden", children: _jsx(Div, { role: "img", "aria-label": safeTitle, className: "h-full w-full bg-cover bg-center transition-transform duration-300 group-hover:scale-105", style: { backgroundImage: `url(${event.coverImageUrl})` } }) })) : (_jsx(Div, { className: "aspect-video bg-gradient-to-br from-zinc-100 to-zinc-200 dark:from-slate-800 dark:to-slate-700 flex items-center justify-center", children: _jsx("span", { className: "text-5xl opacity-40", "aria-hidden": "true", children: TYPE_ICONS[event.type] }) })) }), _jsxs(Div, { className: "flex flex-1 flex-col p-4", children: [_jsxs(Div, { className: "flex items-start justify-between gap-2 mb-2", children: [_jsx(Span, { className: "text-lg", "aria-hidden": "true", children: TYPE_ICONS[event.type] }), _jsx(EventStatusBadge, { status: event.status })] }), _jsx(Link, { href: detailHref, className: "block", children: _jsx(Heading, { level: 3, className: "font-semibold text-gray-900 dark:text-zinc-100 text-base leading-snug mb-1 group-hover:text-primary transition-colors", children: safeTitle }) }), _jsx(RichText, { html: normalizeRichTextHtml(event.description ?? ""), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: `mb-3 ${THEME_CONSTANTS.utilities.textClamp3} text-sm text-zinc-600 dark:text-zinc-400` }), _jsxs(Div, { className: "mb-3 mt-auto flex items-center justify-between text-xs text-zinc-500 dark:text-zinc-500", children: [event.status === EVENT_FIELDS.STATUS_VALUES.ACTIVE &&
|
|
24
|
+
daysLeft > 0 && _jsxs(Span, { children: ["\u23F1 ", daysLeft, "d remaining"] }), _jsxs(Span, { children: ["\uD83D\uDC65 ", event.stats.totalEntries, " ", labels.entries ?? "entries"] })] }), event.status === EVENT_FIELDS.STATUS_VALUES.ACTIVE && onParticipate ? (_jsx(Button, { type: "button", onClick: () => onParticipate(event), className: "w-full rounded-lg bg-primary py-2 text-sm font-medium text-white transition-colors hover:bg-primary-600", children: labels.participate ?? "Participate" })) : (_jsxs(TextLink, { href: detailHref, className: "inline-flex w-full items-center justify-center gap-1.5 rounded-lg border border-zinc-300 px-3 py-2 text-sm font-medium text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-slate-600 dark:text-zinc-200 dark:hover:bg-slate-800", children: [labels.viewDetails ?? "View details", " \u2192"] }))] })] }));
|
|
23
25
|
}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTranslations } from "next-intl";
|
|
3
3
|
import { FilterFacetSection } from "../../filters/FilterFacetSection";
|
|
4
4
|
import { RangeFilter } from "../../filters/RangeFilter";
|
|
5
|
+
import { SwitchFilter } from "../../filters/SwitchFilter";
|
|
5
6
|
import { Div } from "../../../ui";
|
|
6
7
|
export const EVENT_FILTER_KEYS = {
|
|
7
8
|
admin: ["type", "status", "dateFrom", "dateTo"],
|
|
@@ -68,5 +69,5 @@ export function EventFilters({ table, variant = "admin" }) {
|
|
|
68
69
|
const selectedStatus = table.get("status")
|
|
69
70
|
? table.get("status").split("|").filter(Boolean)
|
|
70
71
|
: [];
|
|
71
|
-
return (_jsxs(Div, { children: [_jsx(FilterFacetSection, { title: t("type"), options: typeOptions, selected: selectedType, onChange: (vals) => table.set("type", vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(FilterFacetSection, { title: t("status"), options: statusOptions, selected: selectedStatus, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(RangeFilter, { title: t("dateRange"), type: "date", minValue: table.get("dateFrom"), maxValue: table.get("dateTo"), onMinChange: (v) => table.set("dateFrom", v), onMaxChange: (v) => table.set("dateTo", v), minPlaceholder: t("minDate"), maxPlaceholder: t("maxDate"), defaultCollapsed: true })] }));
|
|
72
|
+
return (_jsxs(Div, { children: [_jsx(FilterFacetSection, { title: t("type"), options: typeOptions, selected: selectedType, onChange: (vals) => table.set("type", vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(FilterFacetSection, { title: t("status"), options: statusOptions, selected: selectedStatus, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(RangeFilter, { title: t("dateRange"), type: "date", minValue: table.get("dateFrom"), maxValue: table.get("dateTo"), onMinChange: (v) => table.set("dateFrom", v), onMaxChange: (v) => table.set("dateTo", v), minPlaceholder: t("minDate"), maxPlaceholder: t("maxDate"), defaultCollapsed: true }), _jsx(SwitchFilter, { title: t("expired"), label: t("showExpiredOnly"), checked: table.get("showExpired") === "true", onChange: (v) => table.set("showExpired", v ? "true" : ""), defaultCollapsed: true })] }));
|
|
72
73
|
}
|
|
@@ -1,24 +1,54 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Search, SlidersHorizontal, X } from "lucide-react";
|
|
3
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
4
6
|
import { useEvents } from "../hooks/useEvents";
|
|
5
|
-
import {
|
|
7
|
+
import { Pagination, SortDropdown } from "../../../ui";
|
|
6
8
|
import { EventCard } from "./EventCard";
|
|
7
|
-
import { EventFilters, EVENT_PUBLIC_SORT_OPTIONS
|
|
9
|
+
import { EventFilters, EVENT_PUBLIC_SORT_OPTIONS } from "./EventFilters";
|
|
8
10
|
const PAGE_SIZE = 24;
|
|
9
11
|
export function EventsIndexListing({ initialData }) {
|
|
10
12
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: "startsAt" } });
|
|
13
|
+
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
14
|
+
const [filterOpen, setFilterOpen] = useState(false);
|
|
15
|
+
// Build client-side filter string from URL params
|
|
16
|
+
const typeRaw = table.get("type");
|
|
17
|
+
const statusRaw = table.get("status");
|
|
18
|
+
const dateFrom = table.get("dateFrom");
|
|
19
|
+
const dateTo = table.get("dateTo");
|
|
20
|
+
const filterParts = [];
|
|
21
|
+
if (typeRaw) {
|
|
22
|
+
const types = typeRaw.split("|").filter(Boolean);
|
|
23
|
+
if (types.length === 1)
|
|
24
|
+
filterParts.push(`type==${types[0]}`);
|
|
25
|
+
else if (types.length > 1)
|
|
26
|
+
filterParts.push(`type==${types.join("|")}`);
|
|
27
|
+
}
|
|
28
|
+
if (statusRaw) {
|
|
29
|
+
const statuses = statusRaw.split("|").filter(Boolean);
|
|
30
|
+
if (statuses.length === 1)
|
|
31
|
+
filterParts.push(`status==${statuses[0]}`);
|
|
32
|
+
else if (statuses.length > 1)
|
|
33
|
+
filterParts.push(`status==${statuses.join("|")}`);
|
|
34
|
+
}
|
|
35
|
+
if (dateFrom)
|
|
36
|
+
filterParts.push(`startsAt>=${dateFrom}`);
|
|
37
|
+
if (dateTo)
|
|
38
|
+
filterParts.push(`endsAt<=${dateTo}`);
|
|
11
39
|
const params = {
|
|
40
|
+
q: table.get("q") || undefined,
|
|
12
41
|
page: table.getNumber("page", 1),
|
|
13
42
|
pageSize: table.getNumber("pageSize", PAGE_SIZE),
|
|
14
43
|
sort: table.get("sort") || "startsAt",
|
|
15
|
-
|
|
16
|
-
status: table.get("status") || "published",
|
|
17
|
-
filters: table.get("filters") || undefined,
|
|
44
|
+
filters: filterParts.length > 0 ? filterParts.join(",") : undefined,
|
|
18
45
|
};
|
|
19
|
-
const { events, total, totalPages, isLoading } = useEvents(params, {
|
|
20
|
-
initialData,
|
|
21
|
-
});
|
|
46
|
+
const { events, total, totalPages, isLoading } = useEvents(params, { initialData });
|
|
22
47
|
const currentPage = table.getNumber("page", 1);
|
|
23
|
-
|
|
48
|
+
const commitSearch = useCallback(() => {
|
|
49
|
+
table.set("q", searchInput.trim());
|
|
50
|
+
table.setPage(1);
|
|
51
|
+
}, [searchInput, table]);
|
|
52
|
+
const closeFilters = () => setFilterOpen(false);
|
|
53
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx("div", { className: "sticky top-0 z-20 border-b border-zinc-200 dark:border-slate-700 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm py-2.5 px-4", children: _jsxs("div", { className: "flex items-center gap-2.5 max-w-full", children: [_jsxs("button", { type: "button", onClick: () => setFilterOpen(true), className: "flex shrink-0 items-center gap-2 rounded-lg border border-zinc-300 dark:border-slate-600 px-3.5 py-2 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: "Filters" })] }), _jsxs("div", { className: "flex flex-1 items-center overflow-hidden rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900", children: [_jsx("input", { type: "text", value: searchInput, onChange: (e) => setSearchInput(e.target.value), onKeyDown: (e) => e.key === "Enter" && commitSearch(), placeholder: "Search events...", className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 outline-none" }), searchInput && (_jsx("button", { type: "button", onClick: () => { setSearchInput(""); table.set("q", ""); }, className: "px-2 text-zinc-400 hover:text-zinc-600 transition-colors", "aria-label": "Clear search", children: _jsx(X, { className: "h-3.5 w-3.5" }) })), _jsx("button", { type: "button", onClick: commitSearch, className: "flex shrink-0 items-center justify-center px-3 py-2 text-zinc-400 hover:text-primary dark:hover:text-primary-400 transition-colors", "aria-label": "Search", children: _jsx(Search, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5 text-sm text-zinc-500 dark:text-zinc-400", children: [_jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Sort by" }), _jsx(SortDropdown, { value: table.get("sort") || "startsAt", onChange: (v) => { table.set("sort", v); table.setPage(1); }, options: EVENT_PUBLIC_SORT_OPTIONS })] })] }) }), _jsxs("div", { className: "py-6", children: [isLoading ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-video bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-4 space-y-2", children: [_jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded mt-2" })] })] }, i))) })) : events.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No events found." })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) })), totalPages > 1 && (_jsx("div", { className: "mt-8 flex justify-center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: closeFilters }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsx("button", { type: "button", onClick: closeFilters, "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(EventFilters, { table: table, variant: "public" }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsx("button", { type: "button", onClick: closeFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors", children: "Apply filters" }) })] })] }))] }));
|
|
24
54
|
}
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
type SearchParams = Record<string, string | string[]>;
|
|
2
|
+
export interface EventsListPageViewProps {
|
|
3
|
+
searchParams?: SearchParams;
|
|
4
|
+
}
|
|
5
|
+
export declare function EventsListPageView({ searchParams }: EventsListPageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
6
|
+
export {};
|
|
@@ -3,14 +3,47 @@ import { eventRepository } from "../../../repositories";
|
|
|
3
3
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
4
4
|
import { Container, Heading, Main, Section } from "../../../ui";
|
|
5
5
|
import { EventsIndexListing } from "./EventsIndexListing";
|
|
6
|
-
|
|
6
|
+
function sp(params, key) {
|
|
7
|
+
const v = params[key];
|
|
8
|
+
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
9
|
+
}
|
|
10
|
+
function buildEventFilters(params) {
|
|
11
|
+
// Public page defaults to published status; allow overriding to active/ended
|
|
12
|
+
const statusRaw = sp(params, "status");
|
|
13
|
+
const parts = [];
|
|
14
|
+
if (statusRaw) {
|
|
15
|
+
const values = statusRaw.split("|").filter(Boolean);
|
|
16
|
+
if (values.length === 1)
|
|
17
|
+
parts.push(`status==${values[0]}`);
|
|
18
|
+
else if (values.length > 1)
|
|
19
|
+
parts.push(`status==${values.join("|")}`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
parts.push("status==published");
|
|
23
|
+
}
|
|
24
|
+
const type = sp(params, "type");
|
|
25
|
+
if (type) {
|
|
26
|
+
const values = type.split("|").filter(Boolean);
|
|
27
|
+
if (values.length === 1)
|
|
28
|
+
parts.push(`type==${values[0]}`);
|
|
29
|
+
else if (values.length > 1)
|
|
30
|
+
parts.push(`type==${values.join("|")}`);
|
|
31
|
+
}
|
|
32
|
+
const dateFrom = sp(params, "dateFrom");
|
|
33
|
+
const dateTo = sp(params, "dateTo");
|
|
34
|
+
if (dateFrom)
|
|
35
|
+
parts.push(`startsAt>=${dateFrom}`);
|
|
36
|
+
if (dateTo)
|
|
37
|
+
parts.push(`endsAt<=${dateTo}`);
|
|
38
|
+
return parts.join(",");
|
|
39
|
+
}
|
|
40
|
+
export async function EventsListPageView({ searchParams = {} }) {
|
|
41
|
+
const sort = sp(searchParams, "sort") || "startsAt";
|
|
42
|
+
const page = Number(sp(searchParams, "page")) || 1;
|
|
43
|
+
const pageSize = Number(sp(searchParams, "pageSize")) || 24;
|
|
44
|
+
const filters = buildEventFilters(searchParams);
|
|
7
45
|
const result = await eventRepository
|
|
8
|
-
.list({
|
|
9
|
-
filters: "status==published",
|
|
10
|
-
sorts: "startsAt",
|
|
11
|
-
page: 1,
|
|
12
|
-
pageSize: 24,
|
|
13
|
-
})
|
|
46
|
+
.list({ filters, sorts: sort, page, pageSize })
|
|
14
47
|
.catch(() => null);
|
|
15
48
|
return (_jsx(Main, { children: _jsx(Section, { className: "py-10", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-8 text-3xl font-semibold text-zinc-900 dark:text-zinc-50", children: "Events" }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(EventsIndexListing, { initialData: result }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
16
49
|
}
|
|
@@ -13,6 +13,8 @@ export { EventLeaderboard } from "./EventLeaderboard";
|
|
|
13
13
|
export type { EventLeaderboardProps } from "./EventLeaderboard";
|
|
14
14
|
export { AdminEventsView } from "./AdminEventsView";
|
|
15
15
|
export type { AdminEventsViewProps } from "./AdminEventsView";
|
|
16
|
+
export { AdminEventEditorView } from "./AdminEventEditorView";
|
|
17
|
+
export type { AdminEventEditorViewProps } from "./AdminEventEditorView";
|
|
16
18
|
export { AdminEventEntriesView } from "./AdminEventEntriesView";
|
|
17
19
|
export type { AdminEventEntriesViewProps } from "./AdminEventEntriesView";
|
|
18
20
|
export { EventFormDrawer } from "./EventFormDrawer";
|
|
@@ -7,6 +7,7 @@ export { EventDetailView } from "./EventDetailView";
|
|
|
7
7
|
export { EventParticipateView } from "./EventParticipateView";
|
|
8
8
|
export { EventLeaderboard } from "./EventLeaderboard";
|
|
9
9
|
export { AdminEventsView } from "./AdminEventsView";
|
|
10
|
+
export { AdminEventEditorView } from "./AdminEventEditorView";
|
|
10
11
|
export { AdminEventEntriesView } from "./AdminEventEntriesView";
|
|
11
12
|
export { EventFormDrawer } from "./EventFormDrawer";
|
|
12
13
|
export { EventBanner } from "./EventBanner";
|
|
@@ -3,6 +3,8 @@ import { apiClient } from "../../../http";
|
|
|
3
3
|
import { EVENT_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
4
4
|
export function useEvents(params = {}, opts) {
|
|
5
5
|
const sp = new URLSearchParams();
|
|
6
|
+
if (params.q)
|
|
7
|
+
sp.set("q", params.q);
|
|
6
8
|
if (params.status)
|
|
7
9
|
sp.set("status", params.status);
|
|
8
10
|
if (params.type)
|
|
@@ -8,5 +8,5 @@ import { ROUTES } from "../../../next";
|
|
|
8
8
|
export function BlogArticlesSection({ title = "From Our Blog", description, viewMoreHref, viewMoreLabel = "View all posts →", className = "", }) {
|
|
9
9
|
const { data, isLoading } = useBlogArticles();
|
|
10
10
|
const items = data?.posts ?? [];
|
|
11
|
-
return (_jsx(SectionCarousel, { title: title, description: description, viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: THEME_CONSTANTS.carousel.perView.
|
|
11
|
+
return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "From Our Blog", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: THEME_CONSTANTS.carousel.perView.standard, gap: 16, keyExtractor: (post) => post.id, renderItem: (post) => (_jsx(BlogFeaturedCard, { post: post, href: ROUTES.BLOG.ARTICLE(post.slug) })), className: className }));
|
|
12
12
|
}
|
|
@@ -6,5 +6,5 @@ import { useHomepageEvents } from "../hooks/useHomepageEvents";
|
|
|
6
6
|
import { EventCard } from "../../events/components/EventCard";
|
|
7
7
|
export function EventsSection({ title = "Events & Offers", description, viewMoreHref, viewMoreLabel = "View all events →", limit = 6, className = "", }) {
|
|
8
8
|
const { data: items = [], isLoading } = useHomepageEvents(limit);
|
|
9
|
-
return (_jsx(SectionCarousel, { title: title, description: description, viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 3, perView: THEME_CONSTANTS.carousel.perView.events, gap: 16, keyExtractor: (event) => event.id, renderItem: (event) => (_jsx(EventCard, { event: event })), className: className }));
|
|
9
|
+
return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "Events & Offers", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 3, perView: THEME_CONSTANTS.carousel.perView.events, gap: 16, keyExtractor: (event) => event.id, renderItem: (event) => (_jsx(EventCard, { event: event })), className: className }));
|
|
10
10
|
}
|
|
@@ -6,5 +6,5 @@ import { useFeaturedAuctions } from "../hooks/useFeaturedAuctions";
|
|
|
6
6
|
import { MarketplaceAuctionCard } from "../../auctions/components/MarketplaceAuctionCard";
|
|
7
7
|
export function FeaturedAuctionsSection({ title = "Live Auctions", description, viewMoreHref, viewMoreLabel = "View all auctions →", className = "", }) {
|
|
8
8
|
const { data: items = [], isLoading } = useFeaturedAuctions();
|
|
9
|
-
return (_jsx(SectionCarousel, { title: title, description: description, viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: THEME_CONSTANTS.carousel.perView.
|
|
9
|
+
return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "Live Auctions", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: THEME_CONSTANTS.carousel.perView.standard, gap: 16, keyExtractor: (product) => product.id, renderItem: (product) => (_jsx(MarketplaceAuctionCard, { product: product })), className: className }));
|
|
10
10
|
}
|
|
@@ -6,5 +6,5 @@ import { useFeaturedPreOrders } from "../hooks/useFeaturedPreOrders";
|
|
|
6
6
|
import { MarketplacePreorderCard } from "../../pre-orders/components/MarketplacePreorderCard";
|
|
7
7
|
export function FeaturedPreOrdersSection({ title = "Reserve Before It Ships", description, viewMoreHref, viewMoreLabel = "View all pre-orders →", className = "", }) {
|
|
8
8
|
const { data: items = [], isLoading } = useFeaturedPreOrders();
|
|
9
|
-
return (_jsx(SectionCarousel, { title: title, description: description, viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: THEME_CONSTANTS.carousel.perView.
|
|
9
|
+
return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "Pre-Order Incoming", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: THEME_CONSTANTS.carousel.perView.standard, gap: 16, keyExtractor: (product) => product.id, renderItem: (product) => (_jsx(MarketplacePreorderCard, { product: product })), className: className }));
|
|
10
10
|
}
|
|
@@ -7,5 +7,5 @@ import { ROUTES } from "../../../next";
|
|
|
7
7
|
export function FeaturedProductsSection({ title = "Featured Products", description, viewMoreHref, viewMoreLabel = "View all products →", className = "", }) {
|
|
8
8
|
const { data, isLoading } = useFeaturedProducts();
|
|
9
9
|
const items = data?.items ?? [];
|
|
10
|
-
return (_jsx(SectionCarousel, { title: title, description: description, viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4,
|
|
10
|
+
return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "Featured Products", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, gap: 16, keyExtractor: (product) => product.id, renderItem: (product) => (_jsx(InteractiveProductCard, { product: product, href: ROUTES.PUBLIC.PRODUCT_DETAIL(product.slug ?? product.id) })), className: className }));
|
|
11
11
|
}
|
|
@@ -7,5 +7,5 @@ import { InteractiveStoreCard } from "../../stores/components/InteractiveStoreCa
|
|
|
7
7
|
import { ROUTES } from "../../../next";
|
|
8
8
|
export function FeaturedStoresSection({ title = "Featured Stores", description, viewMoreHref, viewMoreLabel = "View all stores →", limit = 8, className = "", }) {
|
|
9
9
|
const { data: items = [], isLoading } = useFeaturedStores(limit);
|
|
10
|
-
return (_jsx(SectionCarousel, { title: title, description: description, viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: THEME_CONSTANTS.carousel.perView.
|
|
10
|
+
return (_jsx(SectionCarousel, { title: title, description: description, pillLabel: "Top Stores", headingVariant: "editorial", viewMoreHref: viewMoreHref, viewMoreLabel: viewMoreLabel, items: items, isLoading: isLoading, skeletonCount: 4, perView: THEME_CONSTANTS.carousel.perView.standard, gap: 16, keyExtractor: (store) => store.id, renderItem: (store) => (_jsx(InteractiveStoreCard, { store: store, href: ROUTES.PUBLIC.STORE_DETAIL(store.storeSlug) })), className: className }));
|
|
11
11
|
}
|
|
@@ -146,5 +146,5 @@ export function HeroCarousel({ initialSlides, push } = {}) {
|
|
|
146
146
|
}
|
|
147
147
|
}, children: _jsx(Span, { className: "text-lg md:text-2xl", children: card.buttons[0].text }) }))] }, card.id))) }))] }, slide.id))) }) }), slides.length > 1 && (_jsx(Div, { className: "absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-10", children: slides.map((_, index) => (_jsx(Button, { variant: "ghost", className: `relative overflow-hidden rounded-full transition-all duration-500 p-0 !min-h-0 ${index === currentSlide
|
|
148
148
|
? THEME_CONSTANTS.carousel.dotActive
|
|
149
|
-
: `${THEME_CONSTANTS.carousel.dotInactive} hover:bg-white/75`}`, onClick: () => goToSlide(index), "aria-label": `Go to slide ${index + 1}`, children: index === currentSlide && (_jsx(Span, { className: "absolute inset-y-0 left-0 bg-black/20 rounded-full animate-[progress-fill_4s_linear_forwards]", "aria-hidden": "true" })) }, index))) })),
|
|
149
|
+
: `${THEME_CONSTANTS.carousel.dotInactive} hover:bg-white/75`}`, onClick: () => goToSlide(index), "aria-label": `Go to slide ${index + 1}`, children: index === currentSlide && (_jsx(Span, { className: "absolute inset-y-0 left-0 bg-black/20 rounded-full animate-[progress-fill_4s_linear_forwards]", "aria-hidden": "true" })) }, index))) })), slides.length > 1 && (_jsxs(Div, { className: "absolute bottom-4 right-4 z-20 flex gap-2", children: [_jsx(Button, { variant: "ghost", className: `p-0 ${THEME_CONSTANTS.carousel.arrow}`, onClick: goPrev, "aria-label": "Previous slide", children: _jsx("svg", { className: "w-7 h-7", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M15 19l-7-7 7-7" }) }) }), _jsx(Button, { variant: "ghost", className: `p-0 ${THEME_CONSTANTS.carousel.arrow}`, onClick: goNext, "aria-label": "Next slide", children: _jsx("svg", { className: "w-7 h-7", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M9 5l7 7-7 7" }) }) })] }))] }));
|
|
150
150
|
}
|