PageSourceSearch

https://alpha.govafy.ai/assets/AdminCoupons--D-GddOJ.js

js govafy.ai collected 2026-10-05 15:35:14 UTC 41,592 bytes, 930 lines download raw bytes

1import { n as Coins } from "./Header-DQPPT6P1.js";
2import { t as AdminLayout } from "./AdminLayout-HxnHZjbV.js";
3import { t as Ticket } from "./ticket-C3w8wBvv.js";
4import { A as SelectValue, Ar as require_jsx_runtime, Bn as Eye, C as DropdownMenuTrigger, Cr as useAuth, Ct as useToast, D as SelectContent, Dr as useMutation, Dt as CardHeader, E as Select, Hn as EllipsisVertical, Hr as __toESM, O as SelectItem, Or as useQuery, Ot as CardTitle, S as DropdownMenuSeparator, St as TabsTrigger, Tn as LoaderCircle, Tt as CardContent, _n as Plus, at as Textarea, b as DropdownMenuContent, br as createLucideIcon, bt as TabsContent, dt as DialogDescription, ft as DialogFooter, gt as DialogTitle, j as Label, k as SelectTrigger, kr as useQueryClient, kt as Button, lt as Dialog, ot as useAuthReady, pn as Search, pt as DialogHeader, rn as Trash2, st as useBackend, tt as Input, ut as DialogContent, v as DropdownMenu, vt as Badge, wt as Card, x as DropdownMenuItem, xt as TabsList, yt as Tabs, zr as require_react } from "./index-Cqycx31B.js";
5import { i as DrawerTitle, n as DrawerContent, r as DrawerHeader, t as Drawer } from "./drawer-gCXhKnFK.js";
6import { a as TableHeader, i as TableHead, n as TableBody, o as TableRow, r as TableCell, t as Table } from "./table-CnLCOrpC.js";
7/**
8* @license lucide-react v0.484.0 - ISC
9*
10* This source code is licensed under the ISC license.
11* See the LICENSE file in the root directory of this source tree.
12*/
13var ToggleLeft = createLucideIcon("toggle-left", [["rect", {
14	width: "20",
15	height: "12",
16	x: "2",
17	y: "6",
18	rx: "6",
19	ry: "6",
20	key: "f2vt7d"
21}], ["circle", {
22	cx: "8",
23	cy: "12",
24	r: "2",
25	key: "1nvbw3"
26}]]);
27/**
28* @license lucide-react v0.484.0 - ISC
29*
30* This source code is licensed under the ISC license.
31* See the LICENSE file in the root directory of this source tree.
32*/
33var ToggleRight = createLucideIcon("toggle-right", [["rect", {
34	width: "20",
35	height: "12",
36	x: "2",
37	y: "6",
38	rx: "6",
39	ry: "6",
40	key: "f2vt7d"
41}], ["circle", {
42	cx: "16",
43	cy: "12",
44	r: "2",
45	key: "4ma0v8"
46}]]);
47//#endregion
48//#region pages/AdminCoupons.tsx
49var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
50var import_jsx_runtime = require_jsx_runtime();
51function AdminCoupons() {
52	const backend = useBackend();
53	const queryClient = useQueryClient();
54	const { isLoaded, isSignedIn } = useAuth();
55	const isAuthReady = useAuthReady();
56	const { toast } = useToast();
57	const [search, setSearch] = (0, import_react.useState)("");
58	const [page, setPage] = (0, import_react.useState)(0);
59	const pageSize = 50;
60	const [selectedCouponId, setSelectedCouponId] = (0, import_react.useState)(null);
61	const [createCouponDialog, setCreateCouponDialog] = (0, import_react.useState)({
62		open: false,
63		code: "",
64		name: "",
65		description: "",
66		couponType: "credit_grant",
67		creditAmount: "",
68		tierId: "",
69		discountPercentage: "",
70		discountFixedCents: "",
71		maxRedemptions: "",
72		maxPerUser: "1",
73		validUntil: "",
74		notes: ""
75	});
76	const { data: adminAccessData } = useQuery({
77		queryKey: ["admin", "health-check"],
78		queryFn: async () => backend.admin.checkAdminAccess(),
79		enabled: isLoaded && isSignedIn && isAuthReady
80	});
81	const { data: couponsData, isLoading } = useQuery({
82		queryKey: [
83			"admin",
84			"coupons",
85			search,
86			page
87		],
88		queryFn: async () => backend.admin.listCoupons({
89			search,
90			limit: pageSize,
91			offset: page * pageSize
92		}),
93		enabled: isLoaded && isSignedIn && isAuthReady && !!adminAccessData
94	});
95	const { data: selectedCouponDetails } = useQuery({
96		queryKey: [
97			"admin",
98			"coupon-details",
99			selectedCouponId
100		],
101		queryFn: async () => backend.admin.getCouponDetails({ couponId: selectedCouponId }),
102		enabled: !!selectedCouponId
103	});
104	const { data: redemptionAttempts } = useQuery({
105		queryKey: [
106			"admin",
107			"redemption-attempts",
108			selectedCouponId
109		],
110		queryFn: async () => backend.admin.queryRedemptionAttempts({
111			couponId: selectedCouponId,
112			limit: 50
113		}),
114		enabled: !!selectedCouponId
115	});
116	const createCouponMutation = useMutation({
117		mutationFn: async (params) => backend.admin.createCoupon(params),
118		onSuccess: (data) => {
119			toast({
120				title: "Coupon Created",
121				description: `Successfully created coupon: ${data.code}`
122			});
123			queryClient.invalidateQueries({ queryKey: ["admin", "coupons"] });
124			setCreateCouponDialog({
125				open: false,
126				code: "",
127				name: "",
128				description: "",
129				couponType: "credit_grant",
130				creditAmount: "",
131				tierId: "",
132				discountPercentage: "",
133				discountFixedCents: "",
134				maxRedemptions: "",
135				maxPerUser: "1",
136				validUntil: "",
137				notes: ""
138			});
139		},
140		onError: (error) => {
141			toast({
142				title: "Failed to Create Coupon",
143				description: error.message,
144				variant: "destructive"
145			});
146		}
147	});
148	const toggleCouponMutation = useMutation({
149		mutationFn: async (params) => backend.admin.updateCoupon({
150			couponId: params.couponId,
151			isActive: params.isActive
152		}),
153		onSuccess: (_, variables) => {
154			toast({
155				title: variables.isActive ? "Coupon Activated" : "Coupon Deactivated",
156				description: "Coupon status updated successfully"
157			});
158			queryClient.invalidateQueries({ queryKey: ["admin", "coupons"] });
159			queryClient.invalidateQueries({ queryKey: [
160				"admin",
161				"coupon-details",
162				variables.couponId
163			] });
164		},
165		onError: (error) => {
166			toast({
167				title: "Failed to Update Coupon",
168				description: error.message,
169				variant: "destructive"
170			});
171		}
172	});
173	const deleteCouponMutation = useMutation({
174		mutationFn: async (couponId) => backend.admin.deleteCoupon({ couponId }),
175		onSuccess: (data) => {
176			toast({
177				title: "Coupon Deleted",
178				description: data.message
179			});
180			queryClient.invalidateQueries({ queryKey: ["admin", "coupons"] });
181			setSelectedCouponId(null);
182		},
183		onError: (error) => {
184			toast({
185				title: "Failed to Delete Coupon",
186				description: error.message,
187				variant: "destructive"
188			});
189		}
190	});
191	const coupons = couponsData?.coupons || [];
192	const total = couponsData?.total || 0;
193	const totalPages = Math.ceil(total / pageSize);
194	if (!isLoaded || !isSignedIn || !isAuthReady || !adminAccessData) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AdminLayout, {
195		adminRole: adminAccessData?.role ?? void 0,
196		adminEmail: adminAccessData?.email ?? void 0,
197		children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
198			className: "flex items-center justify-center py-32",
199			children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, { className: "w-12 h-12 animate-spin text-primary" })
200		})
201	});
202	return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AdminLayout, {
203		adminRole: adminAccessData.role ?? void 0,
204		adminEmail: adminAccessData.email ?? void 0,
205		children: [
206			/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
207				className: "space-y-6",
208				children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
209					className: "flex items-center justify-between",
210					children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
211						className: "flex items-center gap-3",
212						children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
213							className: "w-12 h-12 rounded-2xl bg-amber-500/10 flex items-center justify-center",
214							children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ticket, { className: "w-6 h-6 text-amber-500" })
215						}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
216							className: "text-3xl font-bold",
217							children: "Coupons"
218						}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
219							className: "text-muted-foreground",
220							children: "Manage promotional codes and discounts"
221						})] })]
222					}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
223						onClick: () => setCreateCouponDialog({
224							...createCouponDialog,
225							open: true
226						}),
227						children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "w-4 h-4 mr-2" }), "Create Coupon"]
228					})]
229				}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardTitle, {
230					className: "text-sm flex items-center justify-between",
231					children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
232						"All Coupons (",
233						total.toLocaleString(),
234						")"
235					] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
236						className: "relative",
237						children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Search, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
238							placeholder: "Search coupons...",
239							value: search,
240							onChange: (e) => {
241								setSearch(e.target.value);
242								setPage(0);
243							},
244							className: "pl-9 w-80"
245						})]
246					})]
247				}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, { children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
248					className: "flex items-center justify-center py-12",
249					children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, { className: "w-8 h-8 animate-spin text-muted-foreground" })
250				}) : coupons.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
251					className: "flex flex-col items-center justify-center py-12 text-center",
252					children: [
253						/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
254							className: "w-16 h-16 rounded-full bg-muted flex items-center justify-center mb-4",
255							children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ticket, { className: "w-8 h-8 text-muted-foreground" })
256						}),
257						/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
258							className: "text-lg font-semibold mb-1",
259							children: "No coupons found"
260						}),
261						/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
262							className: "text-sm text-muted-foreground",
263							children: search ? "Try adjusting your search criteria" : "Create your first coupon to get started"
264						})
265					]
266				}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
267					className: "overflow-x-auto rounded-md border",
268					children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [
269						/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Code" }),
270						/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Name" }),
271						/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Type" }),
272						/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Status" }),
273						/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
274							className: "text-right",
275							children: "Value"
276						}),
277						/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
278							className: "text-right",
279							children: "Redemptions"
280						}),
281						/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Expires" }),
282						/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { className: "w-12" })
283					] }) }
283), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: coupons.map((coupon) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
284						className: "group",
285						children: [
286							/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
287								className: "font-mono font-bold",
288								children: coupon.code
289							}) }),
290							/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
291								className: "font-medium",
292								children: coupon.name
293							}),
294							/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
295								variant: "outline",
296								children: coupon.couponType.replaceAll("_", " ")
297							}) }),
298							/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: coupon.isActive ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
299								className: "bg-green-500",
300								children: "Active"
301							}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
302								variant: "outline",
303								children: "Inactive"
304							}) }),
305							/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableCell, {
306								className: "text-right",
307								children: [
308									coupon.creditAmount && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
309										className: "flex items-center justify-end gap-1",
310										children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Coins, { className: "w-3 h-3 text-amber-500" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: coupon.creditAmount.toLocaleString() })]
311									}),
312									coupon.discountPercentage && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [coupon.discountPercentage, "% off"] }),
313									coupon.discountFixedCents && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
314										"$",
315										(coupon.discountFixedCents / 100).toFixed(2),
316										" off"
317									] })
318								]
319							}),
320							/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
321								className: "text-right",
322								children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
323									coupon.redemptionsCount,
324									" / ",
325									coupon.maxRedemptions || "∞"
326								] })
327							}),
328							/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
329								className: "text-sm text-muted-foreground",
330								children: coupon.validUntil ? new Date(coupon.validUntil).toLocaleDateString() : "Never"
331							}),
332							/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenu, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuTrigger, {
333								asChild: true,
334								children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
335									variant: "ghost",
336									size: "sm",
337									className: "h-8 w-8 p-0",
338									children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EllipsisVertical, { className: "w-4 h-4" })
339								})
340							}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuContent, {
341								align: "end",
342								children: [
343									/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
344										onClick: () => setSelectedCouponId(coupon.id),
345										children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Eye, { className: "w-4 h-4 mr-2" }), "View Details"]
346									}),
347									/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuSeparator, {}),
348									/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuItem, {
349										onClick: () => toggleCouponMutation.mutate({
350											couponId: coupon.id,
351											isActive: !coupon.isActive
352										}),
353										children: coupon.isActive ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToggleLeft, { className: "w-4 h-4 mr-2" }), "Deactivate"] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToggleRight, { className: "w-4 h-4 mr-2 text-green-500" }), "Activate"] })
354									}),
355									/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
356										onClick: () => {
357											if (confirm(`Delete coupon "${coupon.code}"?`)) deleteCouponMutation.mutate(coupon.id);
358										},
359										className: "text-red-500",
360										children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, { className: "w-4 h-4 mr-2" }), "Delete"]
361									})
362								]
363							})] }) })
364						]
365					}, coupon.id)) })] })
366				}), totalPages > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
367					className: "flex items-center justify-between mt-4",
368					children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
369						className: "text-sm text-muted-foreground",
370						children: [
371							"Showing ",
372							page * pageSize + 1,
373							"-",
374							Math.min((page + 1) * pageSize, total),
375							" of ",
376							total
377						]
378					}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
379						className: "flex items-center gap-2",
380						children: [
381							/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
382								variant: "outline",
383								size: "sm",
384								onClick: () => setPage(Math.max(0, page - 1)),
385								disabled: page === 0,
386								children: "Previous"
387							}),
388							/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
389								className: "text-sm text-muted-foreground",
390								children: [
391									"Page ",
392									page + 1,
393									" of ",
394									totalPages
395								]
396							}),
397							/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
398								variant: "outline",
399								size: "sm",
400								onClick: () => setPage(Math.min(totalPages - 1, page + 1)),
401								disabled: page >= totalPages - 1,
402								children: "Next"
403							})
404						]
405					})]
406				})] }) })] })]
407			}),
408			/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Drawer, {
409				open: !!selectedCouponId,
410				onOpenChange: (open) => !open && setSelectedCouponId(null),
411				children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DrawerContent, {
412					className: "max-h-[90vh]",
413					children: selectedCouponDetails && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DrawerHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DrawerTitle, {
414						className: "flex items-center justify-between",
415						children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["Coupon: ", selectedCouponDetails.coupon.code] }
415), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
416							variant: "destructive",
417							size: "sm",
418							onClick: () => {
419								if (confirm(`Delete coupon "${selectedCouponDetails.coupon.code}"?`)) deleteCouponMutation.mutate(selectedCouponDetails.coupon.id);
420							},
421							disabled: deleteCouponMutation.isPending,
422							children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, { className: "w-4 h-4 mr-1" }), "Delete"]
423						})]
424					}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
425						className: "p-6 overflow-y-auto",
426						children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
427							className: "space-y-4",
428							children: [
429								/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
430									className: "grid grid-cols-2 gap-4",
431									children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
432										className: "text-sm text-muted-foreground",
433										children: "Information"
434									}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
435										className: "mt-2 space-y-2 text-sm",
436										children: [
437											/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
438												/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
439													className: "font-medium",
440													children: "Code:"
441												}),
442												" ",
443												/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
444													className: "font-mono",
445													children: selectedCouponDetails.coupon.code
446												})
447											] }),
448											/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
449												/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
450													className: "font-medium",
451													children: "Name:"
452												}),
453												" ",
454												selectedCouponDetails.coupon.name
455											] }),
456											/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
457												/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
458													className: "font-medium",
459													children: "Type:"
460												}),
461												" ",
462												selectedCouponDetails.coupon.couponType.replace("_", " ")
463											] }),
464											/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
465												/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
466													className: "font-medium",
467													children: "Status:"
468												}),
469												" ",
470												selectedCouponDetails.coupon.isActive ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
471													className: "bg-green-500",
472													children: "Active"
473												}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
474													variant: "outline",
475													children: "Inactive"
476												})
477											] })
478										]
479									})] }
479), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
480										className: "text-sm text-muted-foreground",
481										children: "Stats"
482									}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
483										className: "mt-2 space-y-2 text-sm",
484										children: [
485											/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
486												/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
487													className: "font-medium",
488													children: "Total Redemptions:"
489												}),
490												" ",
491												selectedCouponDetails.stats.totalRedemptions
492											] }),
493											/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
494												/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
495													className: "font-medium",
496													children: "Unique Users:"
497												}),
498												" ",
499												selectedCouponDetails.stats.uniqueUsers
500											] }),
501											/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
502												/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
503													className: "font-medium",
504													children: "Remaining:"
505												}),
506												" ",
507												selectedCouponDetails.stats.remainingRedemptions ?? "∞"
508											] }),
509											/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
510												/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
511													className: "font-medium",
512													children: "Valid Until:"
513												}),
514												" ",
515												selectedCouponDetails.coupon.validUntil ? new Date(selectedCouponDetails.coupon.validUntil).toLocaleDateString() : "Never"
516											] })
517										]
518									})] })]
519								}),
520								selectedCouponDetails.coupon.description && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
521									className: "text-sm text-muted-foreground mb-1",
522									children: "Description"
523								}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
524									className: "text-sm",
525									children: selectedCouponDetails.coupon.description
526								})] }),
527								selectedCouponDetails.coupon.notes && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
528									className: "text-sm text-muted-foreground mb-1",
529									children: "Admin Notes"
530								}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
531									className: "text-sm bg-muted/50 p-3 rounded",
532									children: selectedCouponDetails.coupon.notes
533								})] }),
534								/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tabs, {
535									defaultValue: "redemptions",
536									className: "mt-4",
537									children: [
538										/* @__P
538URE__ */ (0, import_jsx_runtime.jsxs)(TabsList, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TabsTrigger, {
539											value: "redemptions",
540											children: [
541												"Successful Redemptions (",
542												selectedCouponDetails.redemptions.length,
543												")"
544											]
545										}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TabsTrigger, {
546											value: "attempts",
547											children: [
548												"All Attempts (",
549												redemptionAttempts?.total || 0,
550												")"
551											]
552										})] }),
553										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsContent, {
554											value: "redemptions",
555											className: "space-y-2 mt-4",
556											children: selectedCouponDetails.redemptions.length > 0 ? selectedCouponDetails.redemptions.map((redemption) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
557												className: "p-3 bg-muted/50 rounded text-sm",
558												children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
559													className: "flex items-center justify-between",
560													children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
561														className: "font-medium",
562														children: redemption.userId
563													}), redemption.organizationId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
564														className: "text-xs text-muted-foreground",
565														children: ["Org: ", redemption.organizationId]
566													})] }
566), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
567														className: "text-right",
568														children: [redemption.creditsGranted && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
569															className: "text-xs",
570															children: [redemption.creditsGranted.toLocaleString(), " credits"]
571														}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
572															className: "text-xs text-muted-foreground",
573															children: new Date(redemption.redeemedAt).toLocaleDateString()
574														})]
575													})]
576												})
577											}, redemption.id)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
578												className: "text-sm text-muted-foreground text-center py-8",
579												children: "No successful redemptions yet"
580											})
581										}),
582										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsContent, {
583											value: "attempts",
584											className: "space-y-2 mt-4",
585											children: redemptionAttempts && redemptionAttempts.attempts.length > 0 ? redemptionAttempts.attempts.map((attempt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
586												className: `p-3 rounded text-sm ${attempt.success ? "bg-green-50 border border-green-200" : "bg-red-50 border border-red-200"}`,
587												children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
588													className: "flex items-center justify-between",
589													children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
590														/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
591															className: "flex items-center gap-2",
592															children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
593																className: "font-medium font-mono text-xs",
594																children: attempt.userId
595															}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
596																variant: attempt.success ? "default" : "destructive",
597																children: attempt.success ? "Success" : "Failed"
598															})]
599														}),
600														attempt.organizationId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
601															className: "text-xs text-muted-foreground mt-1",
602															children: ["Org: ", attempt.organizationId]
603														}),
604														!attempt.success && attempt.failureReason && /* @__P
604URE__ */ (0, import_jsx_runtime.jsxs)("p", {
605															className: "text-xs text-red-600 mt-1",
606															children: [
607																/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Reason:" }),
608																" ",
609																attempt.failureReason
610															]
611														})
612													] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
613														className: "text-right",
614														children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
615															className: "text-xs text-muted-foreground",
616															children: new Date(attempt.attemptedAt).toLocaleDateString()
617														}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
618															className: "text-xs text-muted-foreground",
619															children: new Date(attempt.attemptedAt).toLocaleTimeString()
620														})]
621													})]
622												})
623											}, attempt.id)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
624												className: "text-sm text-muted-foreground text-center py-8",
625												children: "No redemption attempts yet"
626											})
627										})
628									]
629								})
630							]
631						})
632					})] })
633				})
634			}),
635			/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog, {
636				open: createCouponDialog.open,
637				onOpenChange: (open) => setCreateCouponDialog({
638					...createCouponDialog,
639					open
640				}),
641				children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
642					className: "max-w-2xl max-h-[90vh] overflow-y-auto",
643					children: [
644						/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Create New Coupon" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, { children: "Create a new coupon code for credits, discounts, or tier upgrades" })] }),
645						/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
646							className: "space-y-4 py-4",
647							children: [
648								/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
649									className: "grid grid-cols-2 gap-4",
650									children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
651										htmlFor: "coupon-code",
652										children: "Code (required)"
653									}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
654										id: "coupon-code",
655										value: createCouponDialog.code,
656										onChange: (e) => setCreateCouponDialog({
657											...createCouponDialog,
658											code: e.target.value.toUpperCase()
659										}),
660										placeholder: "e.g. WELCOME2024",
661										className: "font-mono"
662									})] }
662), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
663										htmlFor: "coupon-name",
664										children: "Name (required)"
665									}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
666										id: "coupon-name",
667										value: createCouponDialog.name,
668										onChange: (e) => setCreateCouponDialog({
669											...createCouponDialog,
670											name: e.target.value
671										}),
672										placeholder: "e.g. Welcome Offer"
673									})] })]
674								}),
675								/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
676									htmlFor: "coupon-type",
677									children: "Coupon Type"
678								}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select, {
679									value: createCouponDialog.couponType,
680									onValueChange: (value) => setCreateCouponDialog({
681										...createCouponDialog,
682										couponType: value
683									}),
684									children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, {}) }
684), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [
685										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
686											value: "credit_grant",
687											children: "Credit Grant"
688										}),
689										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
690											value: "tier_upgrade",
691											children: "Tier Upgrade (Trial)"
692										}),
693										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
694											value: "percentage_discount",
695											children: "Percentage Discount"
696										}),
697										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
698											value: "fixed_discount",
699											children: "Fixed Discount"
700										})
701									] })]
702								})] }),
703								createCouponDialog.couponType === "credit_grant" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
704									htmlFor: "credit-amount",
705									children: "Credit Amount (required)"
706								}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
707									id: "credit-amount",
708									type: "number",
709									min: "1",
710									max: "1000000",
711									value: createCouponDialog.creditAmount,
712									onChange: (e) => setCreateCouponDialog({
713										...createCouponDialog,
714										creditAmount: e.target.value
715									}),
716									placeholder: "e.g. 10000"
717								})] }),
718								createCouponDialog.couponType === "tier_upgrade" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
719									htmlFor: "tier-id",
720									children: "Tier ID (required)"
721								}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select, {
722									value: createCouponDialog.tierId,
723									onValueChange: (value) => setCreateCouponDialog({
724										...createCouponDialog,
725										tierId: value
726									}),
727									children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "Select tier" }) }
727), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [
728										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
729											value: "starter",
730											children: "Starter"
731										}),
732										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
733											value: "professional",
734											children: "Professional"
735										}),
736										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
737											value: "business",
738											children: "Business"
739										}),
740										/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
741											value: "enterprise",
742											children: "Enterprise"
743										})
744									] })]
745								})] }),
746								createCouponDialog.couponType === "percentage_discount" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
747									htmlFor: "discount-percentage",
748									children: "Discount Percentage (required)"
749								}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
750									id: "discount-percentage",
751									type: "number",
752									min: "1",
753									max: "100",
754									value: createCouponDialog.discountPercentage,
755									onChange: (e) => setCreateCouponDialog({
756										...createCouponDialog,
757										discountPercentage: e.target.value
758									}),
759									placeholder: "e.g. 20"
760								})] }),
761								createCouponDialog.couponType === "fixed_discount" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
762									htmlFor: "discount-fixed",
763									children: "Fixed Discount (cents, required)"
764								}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
765									id: "discount-fixed",
766									type: "number",
767									min: "1",
768									value: createCouponDialog.discountFixedCents,
769									onChange: (e) => setCreateCouponDialog({
770										...createCouponDialog,
771										discountFixedCents: e.target.value
772									}),
773									placeholder: "e.g. 1000 (= $10.00)"
774								})] }),
775								/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
776									htmlFor: "description",
777									children: "Description"
778								}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
779									id: "description",
780									value: createCouponDialog.description,
781									onChange: (e) => setCreateCouponDialog({
782										...createCouponDialog,
783										description: e.target.value
784									}),
785									placeholder: "User-facing description of the coupon",
786									rows: 2
787								})] }),
788								/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
789									className: "grid grid-cols-2 gap-4",
790									children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
791										htmlFor: "max-redemptions",
792										children: "Max Redemptions (optional)"
793									}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
794										id: "max-redemptions",
795										type: "number",
796										min: "1",
797										value: createCouponDialog.maxRedemptions,
798										onChange: (e) => setCreateCouponDialog({
799											...createCouponDialog,
800											maxRedemptions: e.target.value
801										}),
802										placeholder: "Leave empty for unlimited"
803									})] }
803), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
804										htmlFor: "max-per-user",
805										children: "Max Per User"
806									}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
807										id: "max-per-user",
808										type: "number",
809										min: "1",
810										value: createCouponDialog.maxPerUser,
811										onChange: (e) => setCreateCouponDialog({
812											...createCouponDialog,
813											maxPerUser: e.target.value
814										}),
815										placeholder: "Default: 1"
816									})] })]
817								}),
818								/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
819									htmlFor: "valid-until",
820									children: "Valid Until (optional)"
821								}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
822									id: "valid-until",
823									type: "date",
824									value: createCouponDialog.validUntil,
825									onChange: (e) => setCreateCouponDialog({
826										...createCouponDialog,
827										validUntil: e.target.value
828									})
829								})] }),
830								/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, {
831									htmlFor: "notes",
832									children: "Admin Notes (optional)"
833								}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
834									id: "notes",
835									value: createCouponDialog.notes,
836									onChange: (e) => setCreateCouponDialog({
837										...createCouponDialog,
838										notes: e.target.value
839									}),
840									placeholder: "Internal notes (not shown to users)",
841									rows: 2
842								})] })
843							]
844						}),
845						/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
846							variant: "outline",
847							onClick: () => setCreateCouponDialog({
848								...createCouponDialog,
849								open: false
850							}),
851							children: "Cancel"
852						}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
853							onClick: () => {
854								if (!createCouponDialog.code || createCouponDialog.code.length < 3) {
855									toast({
856										title: "Code must be at least 3 characters",
857										variant: "destructive"
858									});
859									return;
860								}
861								if (!createCouponDialog.name || createCouponDialog.name.length < 3) {
862									toast({
863										title: "Name must be at least 3 characters",
864										variant: "destructive"
865									});
866									return;
867								}
868								const payload = {
869									code: createCouponDialog.code,
870									name: createCouponDialog.name,
871									description: createCouponDialog.description || void 0,
872									couponType: createCouponDialog.couponType,
873									notes: createCouponDialog.notes || void 0,
874									maxRedemptions: createCouponDialog.maxRedemptions ? parseInt(createCouponDialog.maxRedemptions) : void 0,
875									maxPerUser: createCouponDialog.maxPerUser ? parseInt(createCouponDialog.maxPerUser) : 1,
876									validUntil: createCouponDialog.validUntil ? new Date(createCouponDialog.validUntil) : void 0
877								};
878								if (createCouponDialog.couponType === "credit_grant") {
879									const amount = parseInt(createCouponDialog.creditAmount);
880									if (isNaN(amount) || amount <= 0) {
881										toast({
882											title: "Credit amount must be positive",
883											variant: "destructive"
884										});
885										return;
886									}
887									payload.creditAmount = amount;
888								} else if (createCouponDialog.couponType === "tier_upgrade") {
889									if (!createCouponDialog.tierId) {
890										toast({
891											title: "Please select a tier",
892											variant: "destructive"
893										});
894										return;
895									}
896									payload.tierId = createCouponDialog.tierId;
897								} else if (createCouponDialog.couponType === "percentage_discount") {
898									const pct = parseInt(createCouponDialog.discountPercentage);
899									if (isNaN(pct) || pct < 1 || pct > 100) {
900										toast({
901											title: "Discount percentage must be 1-100",
902											variant: "destructive"
903										});
904										return;
905									}
906									payload.discountPercentage = pct;
907								} else if (createCouponDialog.couponType === "fixed_discount") {
908									const cents = parseInt(createCouponDialog.discountFixedCents);
909									if (isNaN(cents) || cents <= 0) {
910										toast({
911											title: "Fixed discount must be positive",
912											variant: "destructive"
913										});
914										return;
915									}
916									payload.discountFixedCents = cents;
917								}
918								createCouponMutation.mutate(payload);
919							},
920							disabled: createCouponMutation.isPending,
921							children: createCouponMutation.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, { className: "w-4 h-4 mr-2 animate-spin" }), "Creating..."] }) : "Create Coupon"
922						})] })
923					]
924				})
925			})
926		]
927	});
928}
929//#endregion
930export { AdminCoupons as default };

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.