@mohasinac/appkit 3.0.6 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +42 -0
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +86 -0
- package/dist/_internal/client/features/lottery/LotteryAdminSlotView.d.ts +14 -0
- package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +46 -0
- package/dist/_internal/client/features/lottery/LotteryDetailView.d.ts +28 -0
- package/dist/_internal/client/features/lottery/LotteryDetailView.js +17 -0
- package/dist/_internal/client/features/lottery/LotteryEntriesView.d.ts +23 -0
- package/dist/_internal/client/features/lottery/LotteryEntriesView.js +58 -0
- package/dist/_internal/client/features/lottery/LotteryListView.d.ts +21 -0
- package/dist/_internal/client/features/lottery/LotteryListView.js +25 -0
- package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +19 -0
- package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -0
- package/dist/_internal/client/features/lottery/LotterySlotGrid.d.ts +13 -0
- package/dist/_internal/client/features/lottery/LotterySlotGrid.js +21 -0
- package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.d.ts +28 -0
- package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +18 -0
- package/dist/_internal/server/features/auctions/__tests__/service.test.d.ts +1 -0
- package/dist/_internal/server/features/auctions/__tests__/service.test.js +140 -0
- package/dist/_internal/server/features/checkout/__tests__/actions.test.d.ts +1 -0
- package/dist/_internal/server/features/checkout/__tests__/actions.test.js +600 -0
- package/dist/_internal/server/features/lottery/actions.d.ts +43 -0
- package/dist/_internal/server/features/lottery/actions.js +243 -0
- package/dist/_internal/server/features/lottery/adapters.d.ts +9 -0
- package/dist/_internal/server/features/lottery/adapters.js +21 -0
- package/dist/_internal/server/features/lottery/data.d.ts +20 -0
- package/dist/_internal/server/features/lottery/data.js +41 -0
- package/dist/_internal/server/features/lottery/metadata.d.ts +6 -0
- package/dist/_internal/server/features/lottery/metadata.js +31 -0
- package/dist/_internal/server/features/refunds/__tests__/actions.test.d.ts +1 -0
- package/dist/_internal/server/features/refunds/__tests__/actions.test.js +263 -0
- package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.js +249 -0
- package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.js +201 -0
- package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.js +169 -0
- package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.js +148 -0
- package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.js +210 -0
- package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.js +190 -0
- package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.js +161 -0
- package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.js +302 -0
- package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.js +252 -0
- package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.js +129 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +46 -0
- package/dist/_internal/shared/features/products/schema.d.ts +8 -8
- package/dist/client.d.ts +9 -0
- package/dist/client.js +9 -0
- package/dist/constants/field-names.d.ts +5 -1
- package/dist/constants/field-names.js +5 -1
- package/dist/features/addresses/repository/__tests__/addresses.repository.test.d.ts +1 -0
- package/dist/features/addresses/repository/__tests__/addresses.repository.test.js +386 -0
- package/dist/features/admin/api/bids/route.d.ts +2 -2
- package/dist/features/admin/api/coupons/route.d.ts +2 -2
- package/dist/features/admin/api/products/route.d.ts +2 -2
- package/dist/features/admin/api/reviews/route.d.ts +2 -2
- package/dist/features/admin/repository/__tests__/notification.repository.test.d.ts +1 -0
- package/dist/features/admin/repository/__tests__/notification.repository.test.js +356 -0
- package/dist/features/admin/repository/__tests__/site-settings.repository.test.d.ts +1 -0
- package/dist/features/admin/repository/__tests__/site-settings.repository.test.js +298 -0
- package/dist/features/auctions/repository/__tests__/bid.repository.test.d.ts +1 -0
- package/dist/features/auctions/repository/__tests__/bid.repository.test.js +290 -0
- package/dist/features/auth/api/route.d.ts +2 -2
- package/dist/features/auth/repository/__tests__/session.repository.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/session.repository.test.js +310 -0
- package/dist/features/auth/repository/__tests__/session.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/session.test.js +146 -0
- package/dist/features/auth/repository/__tests__/sms-counter.repository.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/sms-counter.repository.test.js +171 -0
- package/dist/features/auth/repository/__tests__/sms-counter.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/sms-counter.test.js +88 -0
- package/dist/features/auth/repository/__tests__/token.repository.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/token.repository.test.js +260 -0
- package/dist/features/auth/repository/__tests__/token.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/token.test.js +140 -0
- package/dist/features/auth/repository/__tests__/user.repository.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/user.repository.test.js +352 -0
- package/dist/features/auth/repository/__tests__/user.test.d.ts +1 -0
- package/dist/features/auth/repository/__tests__/user.test.js +226 -0
- package/dist/features/auth/repository/session.repository.js +10 -1
- package/dist/features/cart/repository/__tests__/cart.repository.test.d.ts +1 -0
- package/dist/features/cart/repository/__tests__/cart.repository.test.js +371 -0
- package/dist/features/cart/repository/cart.repository.js +2 -2
- package/dist/features/categories/api/[id]/route.d.ts +4 -4
- package/dist/features/categories/api/route.d.ts +2 -2
- package/dist/features/categories/repository/__tests__/categories.repository.test.d.ts +1 -0
- package/dist/features/categories/repository/__tests__/categories.repository.test.js +813 -0
- package/dist/features/events/components/EventCard.js +1 -0
- package/dist/features/events/repository/__tests__/event.repository.test.d.ts +1 -0
- package/dist/features/events/repository/__tests__/event.repository.test.js +299 -0
- package/dist/features/events/schemas/firestore.d.ts +3 -0
- package/dist/features/events/schemas/index.d.ts +834 -14
- package/dist/features/events/schemas/index.js +80 -1
- package/dist/features/events/types/index.d.ts +4 -1
- package/dist/features/faq/repository/__tests__/faqs.repository.test.d.ts +1 -0
- package/dist/features/faq/repository/__tests__/faqs.repository.test.js +311 -0
- package/dist/features/history/repository/__tests__/user-history.repository.test.d.ts +1 -0
- package/dist/features/history/repository/__tests__/user-history.repository.test.js +180 -0
- package/dist/features/homepage/api/[id]/route.d.ts +4 -4
- package/dist/features/homepage/api/carousel/[id]/route.d.ts +4 -4
- package/dist/features/homepage/api/carousel/route.d.ts +2 -2
- package/dist/features/homepage/api/route.d.ts +2 -2
- package/dist/features/homepage/repository/__tests__/carousel.repository.test.d.ts +1 -0
- package/dist/features/homepage/repository/__tests__/carousel.repository.test.js +182 -0
- package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.d.ts +1 -0
- package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.js +185 -0
- package/dist/features/lottery/repository/lottery-entry.repository.d.ts +33 -0
- package/dist/features/lottery/repository/lottery-entry.repository.js +130 -0
- package/dist/features/lottery/schemas/firestore.d.ts +49 -0
- package/dist/features/lottery/schemas/firestore.js +25 -0
- package/dist/features/lottery/schemas/zod.d.ts +113 -0
- package/dist/features/lottery/schemas/zod.js +39 -0
- package/dist/features/lottery/types/index.d.ts +67 -0
- package/dist/features/lottery/types/index.js +55 -0
- package/dist/features/messages/repository/__tests__/conversations.repository.test.d.ts +1 -0
- package/dist/features/messages/repository/__tests__/conversations.repository.test.js +257 -0
- package/dist/features/orders/repository/__tests__/orders.repository.test.d.ts +1 -0
- package/dist/features/orders/repository/__tests__/orders.repository.test.js +211 -0
- package/dist/features/payments/repository/__tests__/payout.repository.test.d.ts +1 -0
- package/dist/features/payments/repository/__tests__/payout.repository.test.js +292 -0
- package/dist/features/products/api/[id]/route.d.ts +4 -4
- package/dist/features/products/api/route.d.ts +2 -2
- package/dist/features/products/repository/__tests__/product-features.repository.test.d.ts +1 -0
- package/dist/features/products/repository/__tests__/product-features.repository.test.js +329 -0
- package/dist/features/products/repository/__tests__/products.repository.test.d.ts +1 -0
- package/dist/features/products/repository/__tests__/products.repository.test.js +607 -0
- package/dist/features/products/schemas/firestore.d.ts +15 -1
- package/dist/features/products/schemas/index.d.ts +12 -12
- package/dist/features/products/schemas/index.js +4 -2
- package/dist/features/products/types/index.d.ts +1 -1
- package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.d.ts +1 -0
- package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.js +219 -0
- package/dist/features/promotions/repository/__tests__/coupons.repository.test.d.ts +1 -0
- package/dist/features/promotions/repository/__tests__/coupons.repository.test.js +480 -0
- package/dist/features/reviews/api/[id]/route.d.ts +4 -4
- package/dist/features/reviews/api/route.d.ts +2 -2
- package/dist/features/reviews/repository/__tests__/reviews.repository.test.d.ts +1 -0
- package/dist/features/reviews/repository/__tests__/reviews.repository.test.js +327 -0
- package/dist/features/scams/repository/__tests__/scammer.repository.test.d.ts +1 -0
- package/dist/features/scams/repository/__tests__/scammer.repository.test.js +385 -0
- package/dist/features/search/repository/__tests__/search.repository.test.d.ts +1 -0
- package/dist/features/search/repository/__tests__/search.repository.test.js +369 -0
- package/dist/features/seller/api/coupons/route.d.ts +2 -2
- package/dist/features/seller/api/offers/route.d.ts +2 -2
- package/dist/features/seller/api/products/route.d.ts +2 -2
- package/dist/features/seller/api/store/route.d.ts +2 -2
- package/dist/features/seller/repository/__tests__/offer.repository.test.d.ts +1 -0
- package/dist/features/seller/repository/__tests__/offer.repository.test.js +458 -0
- package/dist/features/stores/repository/__tests__/store.repository.test.d.ts +1 -0
- package/dist/features/stores/repository/__tests__/store.repository.test.js +261 -0
- package/dist/features/support/repository/__tests__/support.repository.test.d.ts +1 -0
- package/dist/features/support/repository/__tests__/support.repository.test.js +312 -0
- package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.d.ts +1 -0
- package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.js +139 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +17 -0
- package/dist/next/api/routeHandler.d.ts +1 -1
- package/dist/next/routing/route-map.d.ts +13 -0
- package/dist/next/routing/route-map.js +6 -0
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +2 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/pii-schemas.d.ts +2 -0
- package/dist/security/pii-schemas.js +6 -0
- package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
- package/dist/seed/events-seed-data.js +85 -2
- package/dist/seed/grouped-listings-seed-data.d.ts +3 -1
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/lottery-entries-seed-data.d.ts +2 -0
- package/dist/seed/lottery-entries-seed-data.js +108 -0
- package/dist/seed/manifest.js +1 -0
- package/dist/seed/products-preorders-seed-data.d.ts +3 -1
- package/dist/server.d.ts +6 -0
- package/dist/server.js +6 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
interface LotterySlotRow {
|
|
2
|
+
slotNumber: number;
|
|
3
|
+
name: string;
|
|
4
|
+
priceInPaise: number;
|
|
5
|
+
}
|
|
6
|
+
interface LotteryEventFormData {
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
type: "lottery";
|
|
10
|
+
lotteryConfig: {
|
|
11
|
+
slots: Array<{
|
|
12
|
+
slotNumber: number;
|
|
13
|
+
name: string;
|
|
14
|
+
priceInPaise: number;
|
|
15
|
+
weight: number;
|
|
16
|
+
isBooked: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
totalSlots: number;
|
|
19
|
+
pricingMode: "uniform" | "variable";
|
|
20
|
+
uniformPriceInPaise?: number;
|
|
21
|
+
drawWindowDurationMinutes: number;
|
|
22
|
+
maxPullsPerTransaction: number;
|
|
23
|
+
maxPullsPerUser: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
interface LotteryAdminEditViewProps {
|
|
27
|
+
eventId?: string;
|
|
28
|
+
initialData?: {
|
|
29
|
+
title: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
totalSlots: number;
|
|
32
|
+
pricingMode: "uniform" | "variable";
|
|
33
|
+
uniformPriceInPaise?: number;
|
|
34
|
+
drawWindowDurationMinutes: number;
|
|
35
|
+
maxPullsPerTransaction: number;
|
|
36
|
+
maxPullsPerUser: number;
|
|
37
|
+
slots: LotterySlotRow[];
|
|
38
|
+
};
|
|
39
|
+
onSubmit: (data: LotteryEventFormData) => Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export declare function LotteryAdminEditView({ eventId, initialData, onSubmit, }: LotteryAdminEditViewProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useTransition } from "react";
|
|
4
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
5
|
+
import { Stack, Row, Text, Heading, Section, Container, Button, DataTable, } from "../../../../ui";
|
|
6
|
+
import { Form, FieldInput, FieldSelect } from "../../../../ui";
|
|
7
|
+
export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
|
|
8
|
+
const [isPending, startTransition] = useTransition();
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
const [title, setTitle] = useState(initialData?.title ?? "");
|
|
11
|
+
const [description, setDescription] = useState(initialData?.description ?? "");
|
|
12
|
+
const [pricingMode, setPricingMode] = useState(initialData?.pricingMode ?? "uniform");
|
|
13
|
+
const [uniformPrice, setUniformPrice] = useState(String((initialData?.uniformPriceInPaise ?? 0) / 100));
|
|
14
|
+
const [drawWindowMinutes, setDrawWindowMinutes] = useState(String(initialData?.drawWindowDurationMinutes ?? 5));
|
|
15
|
+
const [maxPullsPerTx, setMaxPullsPerTx] = useState(String(initialData?.maxPullsPerTransaction ?? 1));
|
|
16
|
+
const [maxPullsPerUser, setMaxPullsPerUser] = useState(String(initialData?.maxPullsPerUser ?? 1));
|
|
17
|
+
const [slots, setSlots] = useState(initialData?.slots ?? [{ slotNumber: 1, name: "", priceInPaise: 0 }]);
|
|
18
|
+
const addSlot = () => {
|
|
19
|
+
setSlots((prev) => [
|
|
20
|
+
...prev,
|
|
21
|
+
{ slotNumber: prev.length + 1, name: "", priceInPaise: 0 },
|
|
22
|
+
]);
|
|
23
|
+
};
|
|
24
|
+
const removeSlot = (idx) => {
|
|
25
|
+
setSlots((prev) => prev.filter((_, i) => i !== idx).map((s, i) => ({ ...s, slotNumber: i + 1 })));
|
|
26
|
+
};
|
|
27
|
+
const updateSlot = (idx, field, value) => {
|
|
28
|
+
setSlots((prev) => prev.map((s, i) => i === idx
|
|
29
|
+
? { ...s, [field]: field === "priceInPaise" ? Math.round(parseFloat(value) * 100) || 0 : value }
|
|
30
|
+
: s));
|
|
31
|
+
};
|
|
32
|
+
const handleSubmit = () => {
|
|
33
|
+
setError(null);
|
|
34
|
+
startTransition(async () => {
|
|
35
|
+
try {
|
|
36
|
+
const lotteryConfig = {
|
|
37
|
+
slots: slots.map((s) => ({
|
|
38
|
+
slotNumber: s.slotNumber,
|
|
39
|
+
name: s.name,
|
|
40
|
+
priceInPaise: s.priceInPaise,
|
|
41
|
+
weight: 0,
|
|
42
|
+
isBooked: false,
|
|
43
|
+
})),
|
|
44
|
+
totalSlots: slots.length,
|
|
45
|
+
pricingMode,
|
|
46
|
+
uniformPriceInPaise: pricingMode === "uniform" ? Math.round(parseFloat(uniformPrice) * 100) || 0 : undefined,
|
|
47
|
+
drawWindowDurationMinutes: parseInt(drawWindowMinutes, 10) || 5,
|
|
48
|
+
maxPullsPerTransaction: parseInt(maxPullsPerTx, 10) || 1,
|
|
49
|
+
maxPullsPerUser: parseInt(maxPullsPerUser, 10) || 1,
|
|
50
|
+
};
|
|
51
|
+
await onSubmit({ title, description, type: "lottery", lotteryConfig });
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
void normalizeError(err);
|
|
55
|
+
setError(err instanceof Error ? err.message : "Failed to save lottery");
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
// Slot table columns
|
|
60
|
+
const slotColumns = [
|
|
61
|
+
{
|
|
62
|
+
key: "slotNumber",
|
|
63
|
+
header: "#",
|
|
64
|
+
render: (s) => _jsx(Text, { size: "xs", color: "muted", family: "mono", children: s.slotNumber }),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: "name",
|
|
68
|
+
header: "Prize Name",
|
|
69
|
+
render: (s) => (_jsx("input", { type: "text", className: "w-full rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-input)] px-2 py-1 text-sm", placeholder: `Prize for slot ${s.slotNumber}`, value: s.name, onChange: (e) => updateSlot(s._idx, "name", e.target.value), "aria-label": `Slot ${s.slotNumber} prize name` })),
|
|
70
|
+
},
|
|
71
|
+
...(pricingMode === "variable"
|
|
72
|
+
? [
|
|
73
|
+
{
|
|
74
|
+
key: "priceInPaise",
|
|
75
|
+
header: "Price (₹)",
|
|
76
|
+
render: (s) => (_jsx("input", { type: "number", className: "w-24 rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-input)] px-2 py-1 text-sm", placeholder: "0", value: s.priceInPaise / 100, onChange: (e) => updateSlot(s._idx, "priceInPaise", e.target.value), "aria-label": `Slot ${s.slotNumber} price` })),
|
|
77
|
+
},
|
|
78
|
+
]
|
|
79
|
+
: []),
|
|
80
|
+
];
|
|
81
|
+
const slotsWithIdx = slots.map((s, i) => ({ ...s, _idx: i }));
|
|
82
|
+
return (_jsx(Container, { children: _jsx(Section, { children: _jsx(Form, { onSubmit: (e) => e.preventDefault(), className: "space-y-6", children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "xl", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "bold", children: eventId ? "Edit Lottery" : "Create Lottery" }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: "Basic Info" }), _jsx(FieldInput, { name: "title", label: "Title", required: true, value: title, onChange: (v) => { setTitle(v); clearErrors(); } }), _jsx(FieldInput, { name: "description", label: "Description", value: description, onChange: (v) => { setDescription(v); clearErrors(); } })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: "Configuration" }), _jsx(FieldSelect, { name: "pricingMode", label: "Pricing Mode", value: pricingMode, options: [
|
|
83
|
+
{ label: "Uniform (all slots same price)", value: "uniform" },
|
|
84
|
+
{ label: "Variable (per-slot pricing)", value: "variable" },
|
|
85
|
+
], onChange: (v) => setPricingMode(v) }), pricingMode === "uniform" && (_jsx(FieldInput, { name: "uniformPrice", label: "Price per Slot (\u20B9)", type: "number", value: uniformPrice, onChange: (v) => setUniformPrice(v) })), _jsx(FieldInput, { name: "drawWindowMinutes", label: "Draw Window (minutes)", type: "number", value: drawWindowMinutes, onChange: (v) => setDrawWindowMinutes(v) }), _jsx(FieldInput, { name: "maxPullsPerTx", label: "Max Pulls per Transaction", type: "number", value: maxPullsPerTx, onChange: (v) => setMaxPullsPerTx(v) }), _jsx(FieldInput, { name: "maxPullsPerUser", label: "Max Pulls per User", type: "number", value: maxPullsPerUser, onChange: (v) => setMaxPullsPerUser(v) })] }), _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Heading, { level: 2, size: "lg", weight: "semibold", children: ["Slots (", slots.length, ")"] }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: addSlot, disabled: slots.length >= 200, children: "+ Add Slot" })] }), _jsx(DataTable, { data: slotsWithIdx, columns: slotColumns, keyExtractor: (s) => String(s.slotNumber), emptyMessage: "No slots yet.", actions: (s) => slots.length > 1 ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeSlot(s._idx), "aria-label": `Remove slot ${s.slotNumber}`, children: "\u00D7" })) : null })] }), error && _jsx(Text, { className: "text-error", size: "sm", role: "alert", children: error }), _jsx(Button, { type: "submit", variant: "primary", isLoading: isPending, onClick: handleSubmit, className: "w-full sm:w-auto", children: isPending ? "Saving…" : eventId ? "Update Lottery" : "Create Lottery" })] })) }) }) }));
|
|
86
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ClientLotterySlot } from "../../../../features/lottery/types";
|
|
2
|
+
interface AdminLotterySlot extends ClientLotterySlot {
|
|
3
|
+
priceInPaise: number;
|
|
4
|
+
weight: number;
|
|
5
|
+
}
|
|
6
|
+
interface LotteryAdminSlotViewProps {
|
|
7
|
+
slots: AdminLotterySlot[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Admin-only view showing slots with price + weight + % chance.
|
|
11
|
+
* Separate from LotterySlotGrid (public) which never shows price/weight.
|
|
12
|
+
*/
|
|
13
|
+
export declare function LotteryAdminSlotView({ slots }: LotteryAdminSlotViewProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Stack, Text, Heading, Badge, Section, Container, DataTable, } from "../../../../ui";
|
|
4
|
+
/**
|
|
5
|
+
* Admin-only view showing slots with price + weight + % chance.
|
|
6
|
+
* Separate from LotterySlotGrid (public) which never shows price/weight.
|
|
7
|
+
*/
|
|
8
|
+
export function LotteryAdminSlotView({ slots }) {
|
|
9
|
+
const totalWeight = slots.reduce((s, sl) => s + sl.weight, 0);
|
|
10
|
+
const columns = [
|
|
11
|
+
{
|
|
12
|
+
key: "slotNumber",
|
|
13
|
+
header: "Slot #",
|
|
14
|
+
render: (s) => _jsxs(Text, { size: "sm", weight: "semibold", family: "mono", children: ["#", s.slotNumber] }),
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: "name",
|
|
18
|
+
header: "Name",
|
|
19
|
+
render: (s) => _jsx(Text, { size: "sm", children: s.name }),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: "priceInPaise",
|
|
23
|
+
header: "Price (₹)",
|
|
24
|
+
render: (s) => (_jsxs(Text, { size: "sm", family: "mono", children: ["\u20B9", (s.priceInPaise / 100).toLocaleString("en-IN")] })),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "weight",
|
|
28
|
+
header: "Weight",
|
|
29
|
+
render: (s) => _jsx(Text, { size: "sm", family: "mono", children: s.weight }),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: "status",
|
|
33
|
+
header: "% Chance",
|
|
34
|
+
render: (s) => {
|
|
35
|
+
const chance = totalWeight > 0 ? ((s.weight / totalWeight) * 100).toFixed(1) : "0.0";
|
|
36
|
+
return _jsxs(Text, { size: "sm", family: "mono", children: [chance, "%"] });
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
key: "isBooked",
|
|
41
|
+
header: "Status",
|
|
42
|
+
render: (s) => (_jsx(Badge, { variant: s.isBooked ? "success" : "secondary", size: "sm", children: s.isBooked ? "Claimed" : "Available" })),
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "xl", weight: "bold", children: "Slot Analysis (Admin Only)" }), _jsx(Text, { size: "sm", color: "muted", children: "Price and weight data is never exposed to store owners or users." }), _jsx(DataTable, { data: slots, columns: columns, keyExtractor: (s) => String(s.slotNumber), emptyMessage: "No slots defined." })] }) }) }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ClientLotteryConfig } from "../../../../features/lottery/types";
|
|
2
|
+
interface ClientLotteryEvent {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
status: string;
|
|
7
|
+
endsAt?: string | Date | null;
|
|
8
|
+
coverImageUrl?: string;
|
|
9
|
+
lotteryConfig?: ClientLotteryConfig;
|
|
10
|
+
}
|
|
11
|
+
interface LotteryDetailViewProps {
|
|
12
|
+
event: ClientLotteryEvent;
|
|
13
|
+
user?: {
|
|
14
|
+
uid: string;
|
|
15
|
+
displayName?: string | null;
|
|
16
|
+
} | null;
|
|
17
|
+
currentEntry?: {
|
|
18
|
+
userLotteryNumber: number;
|
|
19
|
+
assignedPrizeSlotNumber?: number;
|
|
20
|
+
slotName?: string;
|
|
21
|
+
} | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Full lottery detail page view.
|
|
25
|
+
* Shows cover + description + slot grid + pull form (if active + logged in).
|
|
26
|
+
*/
|
|
27
|
+
export declare function LotteryDetailView({ event, user, currentEntry }: LotteryDetailViewProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Stack, Row, Text, Heading, Badge, Section, Container, Div, Anchor, } from "../../../../ui";
|
|
5
|
+
import { LotterySlotGrid } from "./LotterySlotGrid";
|
|
6
|
+
import { LotteryPullForm } from "./LotteryPullForm";
|
|
7
|
+
/**
|
|
8
|
+
* Full lottery detail page view.
|
|
9
|
+
* Shows cover + description + slot grid + pull form (if active + logged in).
|
|
10
|
+
*/
|
|
11
|
+
export function LotteryDetailView({ event, user, currentEntry }) {
|
|
12
|
+
const [pulledEntry, setPulledEntry] = useState(currentEntry?.assignedPrizeSlotNumber != null ? currentEntry : null);
|
|
13
|
+
const config = event.lotteryConfig;
|
|
14
|
+
const isActive = event.status === "active";
|
|
15
|
+
const isEnded = event.status === "ended" || event.status === "cancelled";
|
|
16
|
+
return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { gap: "xl", children: [event.coverImageUrl ? (_jsx(Div, { rounded: "2xl", className: "overflow-hidden", style: { aspectRatio: "16/9", maxHeight: "16rem" }, children: _jsx("img", { src: event.coverImageUrl, alt: event.title, className: "w-full h-full object-cover" }) })) : (_jsx(Div, { rounded: "2xl", surface: "subtle", padding: "xl", align: "center", justify: "center", children: _jsx(Text, { size: "5xl", children: "\uD83C\uDFB0" }) })), _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: isActive ? "success" : isEnded ? "danger" : "warning", children: event.status }), config && (_jsxs(Badge, { variant: "info", children: [config.slots.filter((s) => s.isBooked).length, "/", config.totalSlots, " slots claimed"] }))] }), _jsx(Heading, { level: 1, size: "3xl", weight: "bold", children: event.title }), event.description ? (_jsx(Text, { color: "muted", size: "sm", children: event.description.replace(/<[^>]+>/g, "").slice(0, 200) })) : null, event.endsAt ? (_jsxs(Text, { size: "sm", color: "muted", children: ["Ends: ", new Date(event.endsAt).toLocaleDateString("en-IN", { dateStyle: "long" })] })) : null] }), config && config.slots.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Slots" }), _jsx(LotterySlotGrid, { slots: config.slots, totalSlots: config.totalSlots })] })) : null, isActive && config && (_jsx(Div, { padding: "md", rounded: "2xl", surface: "card", className: "border border-[var(--appkit-color-border)]", children: !user ? (_jsxs(Stack, { gap: "md", className: "text-center", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Want to Participate?" }), _jsx(Text, { color: "muted", children: "Log in to submit your lottery entry." }), _jsx(Anchor, { href: "/login", tone: "brand", children: "Log In to Enter" })] })) : pulledEntry ? (_jsxs(Stack, { gap: "sm", surface: "success-surface", padding: "md", rounded: "xl", className: "border border-success/20", children: [_jsxs(Heading, { level: 2, size: "lg", weight: "bold", className: "text-success", children: ["You're Lottery #", pulledEntry.userLotteryNumber, "!"] }), _jsxs(Text, { className: "text-success", size: "sm", children: ["Assigned to Slot #", pulledEntry.assignedPrizeSlotNumber, pulledEntry.slotName ? ` — ${pulledEntry.slotName}` : "", "."] })] })) : (_jsx(LotteryPullForm, { sourceType: "event", eventId: event.id, totalSlots: config.totalSlots, maxPullsPerUser: config.maxPullsPerUser, onSuccess: (res) => setPulledEntry(res) })) })), isEnded && !pulledEntry && (_jsx(Stack, { gap: "sm", surface: "muted", padding: "md", rounded: "xl", children: _jsx(Text, { color: "muted", weight: "semibold", children: "This lottery has ended." }) }))] }) }) }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface LotteryEntryRow {
|
|
2
|
+
id: string;
|
|
3
|
+
userLotteryNumber: number;
|
|
4
|
+
userDisplayName?: string;
|
|
5
|
+
userId: string;
|
|
6
|
+
transactionId?: string;
|
|
7
|
+
userPhone?: string;
|
|
8
|
+
assignedPrizeSlotNumber?: number;
|
|
9
|
+
status: string;
|
|
10
|
+
isFlagged: boolean;
|
|
11
|
+
submittedAt?: string | Date;
|
|
12
|
+
}
|
|
13
|
+
interface LotteryEntriesViewProps {
|
|
14
|
+
sourceType: "event" | "product";
|
|
15
|
+
sourceId: string;
|
|
16
|
+
entries: LotteryEntryRow[];
|
|
17
|
+
isAdmin: boolean;
|
|
18
|
+
isStoreOwner?: boolean;
|
|
19
|
+
onFlagEntry?: (entryId: string, flagNote: string) => Promise<void>;
|
|
20
|
+
onReopenSlot?: (slotNumber: number) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export declare function LotteryEntriesView({ entries, isAdmin, isStoreOwner, onFlagEntry, onReopenSlot, }: LotteryEntriesViewProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Stack, Row, Text, Heading, Badge, Button, Section, Container, DataTable, } from "../../../../ui";
|
|
5
|
+
import { Dialog } from "../../../../ui/components/Dialog";
|
|
6
|
+
import { FieldTextarea } from "../../../../ui/forms/FieldTextarea";
|
|
7
|
+
export function LotteryEntriesView({ entries, isAdmin, isStoreOwner = false, onFlagEntry, onReopenSlot, }) {
|
|
8
|
+
const [flaggingId, setFlaggingId] = useState(null);
|
|
9
|
+
const [flagNote, setFlagNote] = useState("");
|
|
10
|
+
const canViewSensitive = isAdmin || isStoreOwner;
|
|
11
|
+
const columns = [
|
|
12
|
+
{
|
|
13
|
+
key: "userLotteryNumber",
|
|
14
|
+
header: "#",
|
|
15
|
+
render: (e) => _jsxs(Text, { size: "sm", weight: "semibold", family: "mono", children: ["#", e.userLotteryNumber] }),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: "userDisplayName",
|
|
19
|
+
header: "Participant",
|
|
20
|
+
render: (e) => _jsx(Text, { size: "sm", children: e.userDisplayName ?? e.userId }),
|
|
21
|
+
},
|
|
22
|
+
...(canViewSensitive
|
|
23
|
+
? [
|
|
24
|
+
{
|
|
25
|
+
key: "transactionId",
|
|
26
|
+
header: "TX ID",
|
|
27
|
+
render: (e) => _jsx(Text, { size: "xs", color: "muted", family: "mono", children: e.transactionId ?? "—" }),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: "userPhone",
|
|
31
|
+
header: "Phone",
|
|
32
|
+
render: (e) => _jsx(Text, { size: "xs", color: "muted", family: "mono", children: e.userPhone ?? "—" }),
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
: []),
|
|
36
|
+
{
|
|
37
|
+
key: "assignedPrizeSlotNumber",
|
|
38
|
+
header: "Slot",
|
|
39
|
+
render: (e) => e.assignedPrizeSlotNumber != null ? (_jsxs(Badge, { variant: "info", size: "sm", children: ["Slot #", e.assignedPrizeSlotNumber] })) : (_jsx(Text, { size: "sm", children: "\u2014" })),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: "status",
|
|
43
|
+
header: "Status",
|
|
44
|
+
render: (e) => (_jsx(Badge, { variant: e.status === "drawn" || e.status === "won" ? "success"
|
|
45
|
+
: e.status === "flagged" ? "danger"
|
|
46
|
+
: e.status === "cancelled" ? "secondary"
|
|
47
|
+
: "warning", size: "sm", children: e.status })),
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { gap: "lg", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "bold", children: "Lottery Entries" }), _jsxs(Text, { color: "muted", children: [entries.length, " ", entries.length === 1 ? "entry" : "entries", " total"] }), _jsx(DataTable, { data: entries, columns: columns, keyExtractor: (e) => e.id, emptyMessage: "No entries yet.", actions: isAdmin
|
|
51
|
+
? (entry) => (_jsxs(Row, { gap: "xs", children: [!entry.isFlagged && onFlagEntry && (_jsx(Button, { variant: "danger", size: "sm", onClick: () => { setFlaggingId(entry.id); setFlagNote(""); }, children: "Flag" })), entry.isFlagged && entry.assignedPrizeSlotNumber != null && onReopenSlot && (_jsx(Button, { variant: "secondary", size: "sm", onClick: () => onReopenSlot(entry.assignedPrizeSlotNumber), children: "Reopen Slot" }))] }))
|
|
52
|
+
: undefined }), _jsx(Dialog, { open: !!flaggingId, onClose: () => setFlaggingId(null), padding: "lg", "aria-label": "Flag entry", children: _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "xl", weight: "bold", children: "Flag This Entry?" }), _jsx(Text, { color: "muted", size: "sm", children: "Entry will be marked as fraudulent. Their claimed slot stays booked until you manually reopen it." }), _jsx(FieldTextarea, { name: "flagNote", label: "Reason for flagging", required: true, value: flagNote, onChange: (v) => setFlagNote(v), rows: 3 }), _jsxs(Row, { gap: "sm", justify: "end", children: [_jsx(Button, { variant: "ghost", onClick: () => setFlaggingId(null), children: "Cancel" }), _jsx(Button, { variant: "danger", disabled: !flagNote.trim(), onClick: async () => {
|
|
53
|
+
if (onFlagEntry && flaggingId && flagNote.trim()) {
|
|
54
|
+
await onFlagEntry(flaggingId, flagNote.trim());
|
|
55
|
+
setFlaggingId(null);
|
|
56
|
+
}
|
|
57
|
+
}, children: "Flag Entry" })] })] }) })] }) }) }));
|
|
58
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ClientLotteryConfig } from "../../../../features/lottery/types";
|
|
2
|
+
interface ClientLotteryEvent {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
status: string;
|
|
7
|
+
startsAt?: string | Date;
|
|
8
|
+
endsAt?: string | Date;
|
|
9
|
+
coverImageUrl?: string;
|
|
10
|
+
lotteryConfig?: ClientLotteryConfig;
|
|
11
|
+
}
|
|
12
|
+
interface LotteryListViewProps {
|
|
13
|
+
items: ClientLotteryEvent[];
|
|
14
|
+
adminMode?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Card grid of lotteries.
|
|
18
|
+
* Admin sees all statuses; public sees active only.
|
|
19
|
+
*/
|
|
20
|
+
export declare function LotteryListView({ items, adminMode }: LotteryListViewProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Stack, Row, Text, Heading, Badge, Card, Section, Container, Anchor, Div, Grid, } from "../../../../ui";
|
|
4
|
+
/**
|
|
5
|
+
* Card grid of lotteries.
|
|
6
|
+
* Admin sees all statuses; public sees active only.
|
|
7
|
+
*/
|
|
8
|
+
export function LotteryListView({ items, adminMode = false }) {
|
|
9
|
+
const visible = adminMode
|
|
10
|
+
? items
|
|
11
|
+
: items.filter((e) => e.status === "active");
|
|
12
|
+
if (visible.length === 0) {
|
|
13
|
+
return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { className: "text-center", gap: "md", padding: "xl", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "bold", children: "No Active Lotteries" }), _jsx(Text, { color: "muted", children: "Check back soon for upcoming lottery events!" })] }) }) }));
|
|
14
|
+
}
|
|
15
|
+
return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { gap: "lg", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "bold", children: adminMode ? "All Lotteries" : "Active Lotteries" }), _jsx(Grid, { cols: 3, gap: "md", children: visible.map((event) => {
|
|
16
|
+
const claimed = event.lotteryConfig?.slots.filter((s) => s.isBooked).length ?? 0;
|
|
17
|
+
const total = event.lotteryConfig?.totalSlots ?? 0;
|
|
18
|
+
const slotsLeft = total - claimed;
|
|
19
|
+
return (_jsx(Anchor, { href: `/lottery/${event.id}`, tone: "brand", rounded: "2xl", className: "block focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", "aria-label": `${event.title} lottery`, children: _jsxs(Card, { variant: "default", padding: "none", animate: "hoverLift", className: "overflow-hidden", children: [event.coverImageUrl ? (_jsx(Div, { className: "aspect-video overflow-hidden", children: _jsx("img", { src: event.coverImageUrl, alt: event.title, className: "w-full h-full object-cover" }) })) : (_jsx(Div, { className: "aspect-video", surface: "muted", align: "center", justify: "center", children: _jsx(Text, { size: "3xl", children: "\uD83C\uDFB0" }) })), _jsxs(Stack, { gap: "xs", padding: "md", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: event.status === "active"
|
|
20
|
+
? "success"
|
|
21
|
+
: event.status === "ended"
|
|
22
|
+
? "danger"
|
|
23
|
+
: "warning", size: "sm", children: event.status }), slotsLeft > 0 && event.status === "active" && (_jsxs(Badge, { variant: "info", size: "sm", children: [slotsLeft, " slot", slotsLeft !== 1 ? "s" : "", " left"] })), slotsLeft === 0 && total > 0 && (_jsx(Badge, { variant: "secondary", size: "sm", children: "Full" }))] }), _jsx(Heading, { level: 3, size: "lg", weight: "semibold", className: "truncate", children: event.title }), event.description ? (_jsx(Text, { size: "sm", color: "muted", className: "truncate", children: event.description.replace(/<[^>]+>/g, "").slice(0, 100) })) : null, total > 0 && (_jsxs(Text, { size: "xs", color: "muted", children: [claimed, "/", total, " slots claimed"] }))] })] }) }, event.id));
|
|
24
|
+
}) })] }) }) }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface LotteryPullFormProps {
|
|
2
|
+
sourceType: "event" | "product";
|
|
3
|
+
eventId?: string;
|
|
4
|
+
productId?: string;
|
|
5
|
+
totalSlots: number;
|
|
6
|
+
maxPullsPerUser: number;
|
|
7
|
+
onSuccess?: (result: {
|
|
8
|
+
userLotteryNumber: number;
|
|
9
|
+
assignedPrizeSlotNumber: number;
|
|
10
|
+
slotName: string;
|
|
11
|
+
}) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* User-facing lottery pull form.
|
|
15
|
+
* Submits TX ID + phone + payment time + item number.
|
|
16
|
+
* On success, shows the user their lottery number and assigned prize slot.
|
|
17
|
+
*/
|
|
18
|
+
export declare function LotteryPullForm({ sourceType, eventId, productId, totalSlots, maxPullsPerUser: _maxPullsPerUser, onSuccess, }: LotteryPullFormProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Stack, Row, Text, Heading, Span, } from "../../../../ui";
|
|
5
|
+
import { Form, FieldInput, Button } from "../../../../ui";
|
|
6
|
+
import { useApiMutation } from "../../../../client/api/useApiMutation";
|
|
7
|
+
import { apiClient } from "../../../../http";
|
|
8
|
+
/**
|
|
9
|
+
* User-facing lottery pull form.
|
|
10
|
+
* Submits TX ID + phone + payment time + item number.
|
|
11
|
+
* On success, shows the user their lottery number and assigned prize slot.
|
|
12
|
+
*/
|
|
13
|
+
export function LotteryPullForm({ sourceType, eventId, productId, totalSlots, maxPullsPerUser: _maxPullsPerUser, onSuccess, }) {
|
|
14
|
+
const [result, setResult] = useState(null);
|
|
15
|
+
const [transactionId, setTransactionId] = useState("");
|
|
16
|
+
const [paymentTime, setPaymentTime] = useState("");
|
|
17
|
+
const [itemNumber, setItemNumber] = useState("");
|
|
18
|
+
const [userPhone, setUserPhone] = useState("");
|
|
19
|
+
const apiRoute = sourceType === "event"
|
|
20
|
+
? `/api/events/${eventId ?? ""}/lottery-pull`
|
|
21
|
+
: `/api/products/${productId ?? ""}/lottery-pull`;
|
|
22
|
+
const mutation = useApiMutation({
|
|
23
|
+
mutationFn: () => apiClient.post(apiRoute, {
|
|
24
|
+
sourceType,
|
|
25
|
+
...(sourceType === "event" ? { eventId } : { productId }),
|
|
26
|
+
transactionId,
|
|
27
|
+
paymentTime: new Date(paymentTime).toISOString(),
|
|
28
|
+
purchasedItemNumber: parseInt(itemNumber, 10),
|
|
29
|
+
userPhone,
|
|
30
|
+
}),
|
|
31
|
+
successMessage: "Your lottery entry has been submitted!",
|
|
32
|
+
onSuccess: (data) => {
|
|
33
|
+
if (data.data) {
|
|
34
|
+
setResult(data.data);
|
|
35
|
+
onSuccess?.(data.data);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
if (result) {
|
|
40
|
+
return (_jsxs(Stack, { gap: "sm", padding: "md", rounded: "2xl", surface: "success-surface", className: "border border-success/20", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { layout: "inline-flex", color: "inverse", weight: "semibold", className: "bg-success text-[11px] tracking-wide", padding: "pill-sm", rounded: "full", transform: "uppercase", children: "Entry Confirmed" }), _jsxs(Heading, { level: 2, className: "text-success", size: "lg", weight: "bold", children: ["You're #", result.userLotteryNumber, "!"] })] }), _jsxs(Text, { className: "text-success", size: "sm", children: ["You've been assigned", " ", _jsxs(Span, { weight: "bold", children: ["Slot #", result.assignedPrizeSlotNumber] }), result.slotName ? ` — ${result.slotName}` : "", ". Good luck!"] })] }));
|
|
41
|
+
}
|
|
42
|
+
return (_jsx(Form, { onSubmit: (e) => e.preventDefault(), className: "space-y-4", children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, size: "lg", weight: "semibold", children: "Submit Your Entry" }), _jsx(Text, { size: "sm", color: "muted", children: "Enter your UPI Transaction ID and payment details to claim your slot." }), _jsx(FieldInput, { name: "transactionId", label: "Transaction ID (UPI Ref / Payment Ref)", placeholder: "e.g. 123456789012", required: true, value: transactionId, onChange: (v) => { setTransactionId(v); clearErrors(); } }), _jsx(FieldInput, { name: "paymentTime", label: "Payment Date & Time", type: "datetime-local", required: true, value: paymentTime, onChange: (v) => { setPaymentTime(v); clearErrors(); } }), _jsx(FieldInput, { name: "itemNumber", label: `Slot Number (1 – ${totalSlots})`, type: "number", placeholder: `Enter a number between 1 and ${totalSlots}`, required: true, value: itemNumber, onChange: (v) => { setItemNumber(v); clearErrors(); } }), _jsx(FieldInput, { name: "userPhone", label: "Your Phone Number", type: "tel", placeholder: "e.g. 9876543210", required: true, value: userPhone, onChange: (v) => { setUserPhone(v); clearErrors(); } }), _jsx(Button, { type: "submit", variant: "primary", isLoading: mutation.isPending, onClick: () => {
|
|
43
|
+
clearErrors();
|
|
44
|
+
const num = parseInt(itemNumber, 10);
|
|
45
|
+
let valid = true;
|
|
46
|
+
if (!transactionId || transactionId.length < 4) {
|
|
47
|
+
setFieldError("transactionId", "Transaction ID must be at least 4 characters");
|
|
48
|
+
valid = false;
|
|
49
|
+
}
|
|
50
|
+
if (!paymentTime) {
|
|
51
|
+
setFieldError("paymentTime", "Payment date & time is required");
|
|
52
|
+
valid = false;
|
|
53
|
+
}
|
|
54
|
+
if (isNaN(num) || num < 1 || num > totalSlots) {
|
|
55
|
+
setFieldError("itemNumber", `Slot number must be between 1 and ${totalSlots}`);
|
|
56
|
+
valid = false;
|
|
57
|
+
}
|
|
58
|
+
if (!userPhone || userPhone.replace(/\D/g, "").length < 10) {
|
|
59
|
+
setFieldError("userPhone", "Enter a valid phone number (minimum 10 digits)");
|
|
60
|
+
valid = false;
|
|
61
|
+
}
|
|
62
|
+
if (valid)
|
|
63
|
+
mutation.mutate();
|
|
64
|
+
}, className: "w-full", children: mutation.isPending ? "Submitting…" : "Submit Entry" })] })) }));
|
|
65
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ClientLotterySlot } from "../../../../features/lottery/types";
|
|
2
|
+
interface LotterySlotGridProps {
|
|
3
|
+
slots: ClientLotterySlot[];
|
|
4
|
+
totalSlots: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Visual grid of lottery slots.
|
|
8
|
+
* Booked slots show buyer display name + user lottery number in green.
|
|
9
|
+
* Available slots show slot number + name in white.
|
|
10
|
+
* Never shows price or weight.
|
|
11
|
+
*/
|
|
12
|
+
export declare function LotterySlotGrid({ slots, totalSlots }: LotterySlotGridProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Stack, Row, Text, Span, Div } from "../../../../ui";
|
|
4
|
+
/**
|
|
5
|
+
* Visual grid of lottery slots.
|
|
6
|
+
* Booked slots show buyer display name + user lottery number in green.
|
|
7
|
+
* Available slots show slot number + name in white.
|
|
8
|
+
* Never shows price or weight.
|
|
9
|
+
*/
|
|
10
|
+
export function LotterySlotGrid({ slots, totalSlots }) {
|
|
11
|
+
const cols = totalSlots <= 10 ? 5 : totalSlots <= 25 ? 5 : totalSlots <= 50 ? 10 : totalSlots <= 100 ? 10 : 20;
|
|
12
|
+
return (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "sm", align: "center", children: [_jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Span, { layout: "inline-block", className: "w-3 h-3", rounded: "sm", surface: "success-surface" }), _jsx(Text, { size: "xs", color: "muted", children: "Claimed" })] }), _jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Span, { layout: "inline-block", className: "w-3 h-3 border border-[var(--appkit-color-border)]", rounded: "sm", surface: "muted" }), _jsx(Text, { size: "xs", color: "muted", children: "Available" })] })] }), _jsxs(Div, { style: { display: "grid", gap: "4px", gridTemplateColumns: `repeat(${Math.min(cols, totalSlots)}, minmax(0, 1fr))` }, "aria-label": "Lottery slot grid", children: [slots.map((slot) => (_jsx(Div, { title: slot.isBooked
|
|
13
|
+
? `Slot #${slot.slotNumber}: ${slot.name} — claimed by ${slot.bookedByDisplayName ?? "a participant"} (#${slot.bookedByUserLotteryNumber ?? "?"})`
|
|
14
|
+
: `Slot #${slot.slotNumber}: ${slot.name} — available`, "aria-label": `Slot ${slot.slotNumber}: ${slot.isBooked ? "claimed" : "available"}`, className: [
|
|
15
|
+
"aspect-square rounded-sm flex items-center justify-center text-[10px] font-mono font-semibold transition-colors",
|
|
16
|
+
slot.isBooked
|
|
17
|
+
? "text-success border border-success/30"
|
|
18
|
+
: "text-zinc-400 border border-[var(--appkit-color-border-subtle)] hover:border-primary/40",
|
|
19
|
+
].join(" "), surface: slot.isBooked ? "success-surface" : "subtle", children: slot.slotNumber }, slot.slotNumber))), slots.length < totalSlots &&
|
|
20
|
+
Array.from({ length: totalSlots - slots.length }, (_, i) => (_jsx(Div, { rounded: "sm", surface: "subtle", className: "aspect-square border border-[var(--appkit-color-border-subtle)]", "aria-hidden": "true" }, `empty-${i}`)))] }), _jsxs(Text, { size: "xs", color: "muted", children: [slots.filter((s) => s.isBooked).length, " / ", totalSlots, " slots claimed"] })] }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ClientLotteryConfig } from "../../../../features/lottery/types";
|
|
2
|
+
interface ClientProduct {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
status: string;
|
|
7
|
+
mainImage?: string;
|
|
8
|
+
prizeDrawMode?: "reveal" | "lottery";
|
|
9
|
+
lotteryConfig?: ClientLotteryConfig;
|
|
10
|
+
}
|
|
11
|
+
interface PrizeDrawLotteryDetailViewProps {
|
|
12
|
+
product: ClientProduct;
|
|
13
|
+
user?: {
|
|
14
|
+
uid: string;
|
|
15
|
+
displayName?: string | null;
|
|
16
|
+
} | null;
|
|
17
|
+
currentEntry?: {
|
|
18
|
+
userLotteryNumber: number;
|
|
19
|
+
assignedPrizeSlotNumber?: number;
|
|
20
|
+
slotName?: string;
|
|
21
|
+
} | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Prize-draw product detail view for lottery mode.
|
|
25
|
+
* Same flow as LotteryDetailView but sourced from a product.
|
|
26
|
+
*/
|
|
27
|
+
export declare function PrizeDrawLotteryDetailView({ product, user, currentEntry, }: PrizeDrawLotteryDetailViewProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Stack, Row, Text, Heading, Badge, Section, Container, Div, Anchor, } from "../../../../ui";
|
|
5
|
+
import { LotterySlotGrid } from "./LotterySlotGrid";
|
|
6
|
+
import { LotteryPullForm } from "./LotteryPullForm";
|
|
7
|
+
/**
|
|
8
|
+
* Prize-draw product detail view for lottery mode.
|
|
9
|
+
* Same flow as LotteryDetailView but sourced from a product.
|
|
10
|
+
*/
|
|
11
|
+
export function PrizeDrawLotteryDetailView({ product, user, currentEntry, }) {
|
|
12
|
+
const [pulledEntry, setPulledEntry] = useState(currentEntry?.assignedPrizeSlotNumber != null
|
|
13
|
+
? currentEntry
|
|
14
|
+
: null);
|
|
15
|
+
const config = product.lotteryConfig;
|
|
16
|
+
const isActive = product.status === "published";
|
|
17
|
+
return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { gap: "xl", children: [product.mainImage ? (_jsx(Div, { rounded: "2xl", className: "overflow-hidden", style: { aspectRatio: "16/9", maxHeight: "16rem" }, children: _jsx("img", { src: product.mainImage, alt: product.title, className: "w-full h-full object-cover" }) })) : (_jsx(Div, { rounded: "2xl", surface: "muted", padding: "xl", align: "center", justify: "center", children: _jsx(Text, { size: "5xl", children: "\uD83C\uDF81" }) })), _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: "info", children: "Lottery Prize Draw" }), config && (_jsxs(Badge, { variant: isActive ? "success" : "secondary", children: [config.slots.filter((s) => s.isBooked).length, "/", config.totalSlots, " claimed"] }))] }), _jsx(Heading, { level: 1, size: "3xl", weight: "bold", children: product.title }), product.description ? (_jsx(Text, { color: "muted", children: product.description })) : null] }), config && config.slots.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Slots" }), _jsx(LotterySlotGrid, { slots: config.slots, totalSlots: config.totalSlots })] })) : null, isActive && config && (_jsx(Div, { padding: "md", rounded: "2xl", surface: "card", className: "border border-[var(--appkit-color-border)]", children: !user ? (_jsxs(Stack, { gap: "md", className: "text-center", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Want to Enter?" }), _jsx(Text, { color: "muted", children: "Log in to submit your entry." }), _jsx(Anchor, { href: "/login", tone: "brand", children: "Log In to Enter" })] })) : pulledEntry ? (_jsxs(Stack, { gap: "sm", surface: "success-surface", padding: "md", rounded: "xl", className: "border border-success/20", children: [_jsxs(Heading, { level: 2, size: "lg", weight: "bold", className: "text-success", children: ["You're Entry #", pulledEntry.userLotteryNumber, "!"] }), _jsxs(Text, { className: "text-success", size: "sm", children: ["Assigned to Slot #", pulledEntry.assignedPrizeSlotNumber, pulledEntry.slotName ? ` — ${pulledEntry.slotName}` : "", "."] })] })) : (_jsx(LotteryPullForm, { sourceType: "product", productId: product.id, totalSlots: config.totalSlots, maxPullsPerUser: config.maxPullsPerUser, onSuccess: (res) => setPulledEntry(res) })) }))] }) }) }));
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|