"use client";
import { useState } from "react";
import {
Tag,
Percent,
X,
Check,
Loader2,
AlertCircle,
Sparkles,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
type AppliedCoupon = {
code: string;
discount: string;
type: "percent" | "fixed";
};
const validCoupons: Record<string, { discount: string; type: "percent" | "fixed" }> = {
SAVE20: { discount: "20%", type: "percent" },
WELCOME10: { discount: "$10", type: "fixed" },
FREESHIP: { discount: "Free Shipping", type: "fixed" },
};
export const title = "React Dialog Block Apply Coupon";
export default function DialogApplyCoupon() {
const [open, setOpen] = useState(false);
const [code, setCode] = useState("");
const [isValidating, setIsValidating] = useState(false);
const [error, setError] = useState<string | null>(null);
const [appliedCoupons, setAppliedCoupons] = useState<AppliedCoupon[]>([]);
const [previewDiscount, setPreviewDiscount] = useState<{
discount: string;
type: "percent" | "fixed";
} | null>(null);
const handleCodeChange = (value: string) => {
setCode(value.toUpperCase());
setError(null);
setPreviewDiscount(null);
// Preview discount as user types
const coupon = validCoupons[value.toUpperCase()];
if (coupon) {
setPreviewDiscount(coupon);
}
};
const handleApply = async () => {
if (!code.trim()) {
setError("Please enter a coupon code");
return;
}
// Check if already applied
if (appliedCoupons.some((c) => c.code === code)) {
setError("This code is already applied");
return;
}
setIsValidating(true);
setError(null);
// Simulate API validation
await new Promise((resolve) => setTimeout(resolve, 1000));
const coupon = validCoupons[code];
if (coupon) {
setAppliedCoupons((prev) => [
...prev,
{ code, discount: coupon.discount, type: coupon.type },
]);
setCode("");
setPreviewDiscount(null);
} else {
setError("Invalid or expired coupon code");
}
setIsValidating(false);
};
const handleRemove = (codeToRemove: string) => {
setAppliedCoupons((prev) => prev.filter((c) => c.code !== codeToRemove));
};
const handleClose = () => {
setOpen(false);
setCode("");
setError(null);
setPreviewDiscount(null);
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<div className="flex min-h-[350px] items-center justify-center">
<DialogTrigger asChild>
<Button variant="outline">
<Tag className="mr-2 h-4 w-4" />
Apply Coupon
{appliedCoupons.length > 0 && (
<Badge variant="secondary" className="ml-2">
{appliedCoupons.length}
</Badge>
)}
</Button>
</DialogTrigger>
</div>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Tag className="h-5 w-5" />
Apply Coupon Code
</DialogTitle>
<DialogDescription>
Enter a promo code to get a discount on your order.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Coupon Input */}
<div className="space-y-2">
<Label htmlFor="coupon-code">Coupon Code</Label>
<div className="flex gap-2">
<Input
id="coupon-code"
placeholder="Enter code"
value={code}
onChange={(e) => handleCodeChange(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleApply()}
className="uppercase"
disabled={isValidating}
/>
<Button onClick={handleApply} disabled={isValidating || !code.trim()}>
{isValidating ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
"Apply"
)}
</Button>
</div>
</div>
{/* Preview Discount */}
{previewDiscount && !error && (
<div className="flex items-center gap-2 rounded-lg border border-primary/50 p-3">
<Sparkles className="h-4 w-4 text-primary" />
<span className="text-sm">
You'll save{" "}
<span className="font-semibold text-primary">
{previewDiscount.discount}
</span>{" "}
with this code!
</span>
</div>
)}
{/* Error Message */}
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* Applied Coupons */}
{appliedCoupons.length > 0 && (
<>
<Separator />
<div className="space-y-2">
<Label className="text-muted-foreground">Applied Coupons</Label>
<div className="space-y-2">
{appliedCoupons.map((coupon) => (
<div
key={coupon.code}
className="flex items-center justify-between rounded-lg border p-3"
>
<div className="flex items-center gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full border">
{coupon.type === "percent" ? (
<Percent className="h-4 w-4" />
) : (
<Tag className="h-4 w-4" />
)}
</div>
<div>
<p className="text-sm font-medium">{coupon.code}</p>
<p className="text-xs text-primary">{coupon.discount} off</p>
</div>
</div>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => handleRemove(coupon.code)}
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
</div>
</>
)}
{/* Sample Codes Hint */}
<p className="text-xs text-muted-foreground text-center">
Try: SAVE20, WELCOME10, or FREESHIP
</p>
</div>
<DialogFooter>
<Button variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button onClick={handleClose}>
<Check className="mr-2 h-4 w-4" />
Done
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}