@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.
Files changed (186) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +42 -0
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +86 -0
  3. package/dist/_internal/client/features/lottery/LotteryAdminSlotView.d.ts +14 -0
  4. package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +46 -0
  5. package/dist/_internal/client/features/lottery/LotteryDetailView.d.ts +28 -0
  6. package/dist/_internal/client/features/lottery/LotteryDetailView.js +17 -0
  7. package/dist/_internal/client/features/lottery/LotteryEntriesView.d.ts +23 -0
  8. package/dist/_internal/client/features/lottery/LotteryEntriesView.js +58 -0
  9. package/dist/_internal/client/features/lottery/LotteryListView.d.ts +21 -0
  10. package/dist/_internal/client/features/lottery/LotteryListView.js +25 -0
  11. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +19 -0
  12. package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -0
  13. package/dist/_internal/client/features/lottery/LotterySlotGrid.d.ts +13 -0
  14. package/dist/_internal/client/features/lottery/LotterySlotGrid.js +21 -0
  15. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.d.ts +28 -0
  16. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +18 -0
  17. package/dist/_internal/server/features/auctions/__tests__/service.test.d.ts +1 -0
  18. package/dist/_internal/server/features/auctions/__tests__/service.test.js +140 -0
  19. package/dist/_internal/server/features/checkout/__tests__/actions.test.d.ts +1 -0
  20. package/dist/_internal/server/features/checkout/__tests__/actions.test.js +600 -0
  21. package/dist/_internal/server/features/lottery/actions.d.ts +43 -0
  22. package/dist/_internal/server/features/lottery/actions.js +243 -0
  23. package/dist/_internal/server/features/lottery/adapters.d.ts +9 -0
  24. package/dist/_internal/server/features/lottery/adapters.js +21 -0
  25. package/dist/_internal/server/features/lottery/data.d.ts +20 -0
  26. package/dist/_internal/server/features/lottery/data.js +41 -0
  27. package/dist/_internal/server/features/lottery/metadata.d.ts +6 -0
  28. package/dist/_internal/server/features/lottery/metadata.js +31 -0
  29. package/dist/_internal/server/features/refunds/__tests__/actions.test.d.ts +1 -0
  30. package/dist/_internal/server/features/refunds/__tests__/actions.test.js +263 -0
  31. package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.d.ts +1 -0
  32. package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.js +249 -0
  33. package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.d.ts +1 -0
  34. package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.js +201 -0
  35. package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.d.ts +1 -0
  36. package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.js +169 -0
  37. package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.d.ts +1 -0
  38. package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.js +148 -0
  39. package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.d.ts +1 -0
  40. package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.js +210 -0
  41. package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.d.ts +1 -0
  42. package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.js +190 -0
  43. package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.d.ts +1 -0
  44. package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.js +161 -0
  45. package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.d.ts +1 -0
  46. package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.js +302 -0
  47. package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.d.ts +1 -0
  48. package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.js +252 -0
  49. package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.d.ts +1 -0
  50. package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.js +129 -0
  51. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  52. package/dist/_internal/shared/actions/action-registry.js +46 -0
  53. package/dist/_internal/shared/features/products/schema.d.ts +8 -8
  54. package/dist/client.d.ts +9 -0
  55. package/dist/client.js +9 -0
  56. package/dist/constants/field-names.d.ts +5 -1
  57. package/dist/constants/field-names.js +5 -1
  58. package/dist/features/addresses/repository/__tests__/addresses.repository.test.d.ts +1 -0
  59. package/dist/features/addresses/repository/__tests__/addresses.repository.test.js +386 -0
  60. package/dist/features/admin/api/bids/route.d.ts +2 -2
  61. package/dist/features/admin/api/coupons/route.d.ts +2 -2
  62. package/dist/features/admin/api/products/route.d.ts +2 -2
  63. package/dist/features/admin/api/reviews/route.d.ts +2 -2
  64. package/dist/features/admin/repository/__tests__/notification.repository.test.d.ts +1 -0
  65. package/dist/features/admin/repository/__tests__/notification.repository.test.js +356 -0
  66. package/dist/features/admin/repository/__tests__/site-settings.repository.test.d.ts +1 -0
  67. package/dist/features/admin/repository/__tests__/site-settings.repository.test.js +298 -0
  68. package/dist/features/auctions/repository/__tests__/bid.repository.test.d.ts +1 -0
  69. package/dist/features/auctions/repository/__tests__/bid.repository.test.js +290 -0
  70. package/dist/features/auth/api/route.d.ts +2 -2
  71. package/dist/features/auth/repository/__tests__/session.repository.test.d.ts +1 -0
  72. package/dist/features/auth/repository/__tests__/session.repository.test.js +310 -0
  73. package/dist/features/auth/repository/__tests__/session.test.d.ts +1 -0
  74. package/dist/features/auth/repository/__tests__/session.test.js +146 -0
  75. package/dist/features/auth/repository/__tests__/sms-counter.repository.test.d.ts +1 -0
  76. package/dist/features/auth/repository/__tests__/sms-counter.repository.test.js +171 -0
  77. package/dist/features/auth/repository/__tests__/sms-counter.test.d.ts +1 -0
  78. package/dist/features/auth/repository/__tests__/sms-counter.test.js +88 -0
  79. package/dist/features/auth/repository/__tests__/token.repository.test.d.ts +1 -0
  80. package/dist/features/auth/repository/__tests__/token.repository.test.js +260 -0
  81. package/dist/features/auth/repository/__tests__/token.test.d.ts +1 -0
  82. package/dist/features/auth/repository/__tests__/token.test.js +140 -0
  83. package/dist/features/auth/repository/__tests__/user.repository.test.d.ts +1 -0
  84. package/dist/features/auth/repository/__tests__/user.repository.test.js +352 -0
  85. package/dist/features/auth/repository/__tests__/user.test.d.ts +1 -0
  86. package/dist/features/auth/repository/__tests__/user.test.js +226 -0
  87. package/dist/features/auth/repository/session.repository.js +10 -1
  88. package/dist/features/cart/repository/__tests__/cart.repository.test.d.ts +1 -0
  89. package/dist/features/cart/repository/__tests__/cart.repository.test.js +371 -0
  90. package/dist/features/cart/repository/cart.repository.js +2 -2
  91. package/dist/features/categories/api/[id]/route.d.ts +4 -4
  92. package/dist/features/categories/api/route.d.ts +2 -2
  93. package/dist/features/categories/repository/__tests__/categories.repository.test.d.ts +1 -0
  94. package/dist/features/categories/repository/__tests__/categories.repository.test.js +813 -0
  95. package/dist/features/events/components/EventCard.js +1 -0
  96. package/dist/features/events/repository/__tests__/event.repository.test.d.ts +1 -0
  97. package/dist/features/events/repository/__tests__/event.repository.test.js +299 -0
  98. package/dist/features/events/schemas/firestore.d.ts +3 -0
  99. package/dist/features/events/schemas/index.d.ts +834 -14
  100. package/dist/features/events/schemas/index.js +80 -1
  101. package/dist/features/events/types/index.d.ts +4 -1
  102. package/dist/features/faq/repository/__tests__/faqs.repository.test.d.ts +1 -0
  103. package/dist/features/faq/repository/__tests__/faqs.repository.test.js +311 -0
  104. package/dist/features/history/repository/__tests__/user-history.repository.test.d.ts +1 -0
  105. package/dist/features/history/repository/__tests__/user-history.repository.test.js +180 -0
  106. package/dist/features/homepage/api/[id]/route.d.ts +4 -4
  107. package/dist/features/homepage/api/carousel/[id]/route.d.ts +4 -4
  108. package/dist/features/homepage/api/carousel/route.d.ts +2 -2
  109. package/dist/features/homepage/api/route.d.ts +2 -2
  110. package/dist/features/homepage/repository/__tests__/carousel.repository.test.d.ts +1 -0
  111. package/dist/features/homepage/repository/__tests__/carousel.repository.test.js +182 -0
  112. package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.d.ts +1 -0
  113. package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.js +185 -0
  114. package/dist/features/lottery/repository/lottery-entry.repository.d.ts +33 -0
  115. package/dist/features/lottery/repository/lottery-entry.repository.js +130 -0
  116. package/dist/features/lottery/schemas/firestore.d.ts +49 -0
  117. package/dist/features/lottery/schemas/firestore.js +25 -0
  118. package/dist/features/lottery/schemas/zod.d.ts +113 -0
  119. package/dist/features/lottery/schemas/zod.js +39 -0
  120. package/dist/features/lottery/types/index.d.ts +67 -0
  121. package/dist/features/lottery/types/index.js +55 -0
  122. package/dist/features/messages/repository/__tests__/conversations.repository.test.d.ts +1 -0
  123. package/dist/features/messages/repository/__tests__/conversations.repository.test.js +257 -0
  124. package/dist/features/orders/repository/__tests__/orders.repository.test.d.ts +1 -0
  125. package/dist/features/orders/repository/__tests__/orders.repository.test.js +211 -0
  126. package/dist/features/payments/repository/__tests__/payout.repository.test.d.ts +1 -0
  127. package/dist/features/payments/repository/__tests__/payout.repository.test.js +292 -0
  128. package/dist/features/products/api/[id]/route.d.ts +4 -4
  129. package/dist/features/products/api/route.d.ts +2 -2
  130. package/dist/features/products/repository/__tests__/product-features.repository.test.d.ts +1 -0
  131. package/dist/features/products/repository/__tests__/product-features.repository.test.js +329 -0
  132. package/dist/features/products/repository/__tests__/products.repository.test.d.ts +1 -0
  133. package/dist/features/products/repository/__tests__/products.repository.test.js +607 -0
  134. package/dist/features/products/schemas/firestore.d.ts +15 -1
  135. package/dist/features/products/schemas/index.d.ts +12 -12
  136. package/dist/features/products/schemas/index.js +4 -2
  137. package/dist/features/products/types/index.d.ts +1 -1
  138. package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.d.ts +1 -0
  139. package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.js +219 -0
  140. package/dist/features/promotions/repository/__tests__/coupons.repository.test.d.ts +1 -0
  141. package/dist/features/promotions/repository/__tests__/coupons.repository.test.js +480 -0
  142. package/dist/features/reviews/api/[id]/route.d.ts +4 -4
  143. package/dist/features/reviews/api/route.d.ts +2 -2
  144. package/dist/features/reviews/repository/__tests__/reviews.repository.test.d.ts +1 -0
  145. package/dist/features/reviews/repository/__tests__/reviews.repository.test.js +327 -0
  146. package/dist/features/scams/repository/__tests__/scammer.repository.test.d.ts +1 -0
  147. package/dist/features/scams/repository/__tests__/scammer.repository.test.js +385 -0
  148. package/dist/features/search/repository/__tests__/search.repository.test.d.ts +1 -0
  149. package/dist/features/search/repository/__tests__/search.repository.test.js +369 -0
  150. package/dist/features/seller/api/coupons/route.d.ts +2 -2
  151. package/dist/features/seller/api/offers/route.d.ts +2 -2
  152. package/dist/features/seller/api/products/route.d.ts +2 -2
  153. package/dist/features/seller/api/store/route.d.ts +2 -2
  154. package/dist/features/seller/repository/__tests__/offer.repository.test.d.ts +1 -0
  155. package/dist/features/seller/repository/__tests__/offer.repository.test.js +458 -0
  156. package/dist/features/stores/repository/__tests__/store.repository.test.d.ts +1 -0
  157. package/dist/features/stores/repository/__tests__/store.repository.test.js +261 -0
  158. package/dist/features/support/repository/__tests__/support.repository.test.d.ts +1 -0
  159. package/dist/features/support/repository/__tests__/support.repository.test.js +312 -0
  160. package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.d.ts +1 -0
  161. package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.js +139 -0
  162. package/dist/index.d.ts +14 -0
  163. package/dist/index.js +17 -0
  164. package/dist/next/api/routeHandler.d.ts +1 -1
  165. package/dist/next/routing/route-map.d.ts +13 -0
  166. package/dist/next/routing/route-map.js +6 -0
  167. package/dist/repositories/index.d.ts +1 -0
  168. package/dist/repositories/index.js +2 -0
  169. package/dist/security/index.d.ts +1 -1
  170. package/dist/security/index.js +1 -1
  171. package/dist/security/pii-schemas.d.ts +2 -0
  172. package/dist/security/pii-schemas.js +6 -0
  173. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  174. package/dist/seed/events-seed-data.js +85 -2
  175. package/dist/seed/grouped-listings-seed-data.d.ts +3 -1
  176. package/dist/seed/index.d.ts +1 -0
  177. package/dist/seed/index.js +1 -0
  178. package/dist/seed/lottery-entries-seed-data.d.ts +2 -0
  179. package/dist/seed/lottery-entries-seed-data.js +108 -0
  180. package/dist/seed/manifest.js +1 -0
  181. package/dist/seed/products-preorders-seed-data.d.ts +3 -1
  182. package/dist/server.d.ts +6 -0
  183. package/dist/server.js +6 -0
  184. package/dist/styles.css +1 -1
  185. package/dist/tailwind-utilities.css +1 -1
  186. 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
+ }