Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | "use client";
import { useState } from "react";
import { Button, Input } from "@/components/ui";
import { Icon } from "@/components/ui/icons";
import { cn } from "@/lib/core";
interface RedeemPointsProps {
availablePoints: number;
redemptionRate: number; // Dollar value per point (e.g., 0.01 = 100 points = $1)
maxRedemption?: number; // Maximum points that can be redeemed
minRedemption?: number; // Minimum points required to redeem
onRedeem: (points: number, discountValue: number) => void;
onCancel?: () => void;
className?: string;
}
export function RedeemPoints({
availablePoints,
redemptionRate,
maxRedemption,
minRedemption = 100,
onRedeem,
onCancel,
className}: RedeemPointsProps) {
const [pointsToRedeem, setPointsToRedeem] = useState<string>("");
const [error, setError] = useState<string | null>(null);
const maxAllowed = maxRedemption
? Math.min(availablePoints, maxRedemption)
: availablePoints;
const pointsValue = parseInt(pointsToRedeem) || 0;
const discountValue = pointsValue * redemptionRate;
const formatPoints = (points: number) => {
return new Intl.NumberFormat("en-US").format(points);
};
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"}).format(amount);
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/[^0-9]/g, "");
setPointsToRedeem(value);
setError(null);
};
const handleQuickSelect = (percentage: number) => {
const points = Math.floor(maxAllowed * percentage);
setPointsToRedeem(points.toString());
setError(null);
};
const handleRedeem = () => {
const points = parseInt(pointsToRedeem);
if (!points || points <= 0) {
setError("Please enter a valid number of points");
return;
}
if (points < minRedemption) {
setError(`Minimum redemption is ${formatPoints(minRedemption)} points`);
return;
}
if (points > maxAllowed) {
setError(`Maximum redemption is ${formatPoints(maxAllowed)} points`);
return;
}
onRedeem(points, points * redemptionRate);
};
if (availablePoints < minRedemption) {
return (
<div className={cn("p-4 bg-gray-50 border border-gray-200 rounded-lg", className)}>
<div className="flex items-center gap-2 text-gray-600">
<Icon name="info-circle" size={18} />
<span className="text-sm">
You need at least {formatPoints(minRedemption)} points to redeem.
You have {formatPoints(availablePoints)} points.
</span>
</div>
</div>
);
}
return (
<div className={cn("p-4 bg-yellow-50 border border-yellow-200 rounded-lg", className)}>
<h4 className="text-sm font-semibold text-gray-800 mb-3 flex items-center gap-2">
<Icon name="star" size={16} className="text-yellow-600" />
Redeem Loyalty Points
</h4>
<div className="mb-3">
<p className="text-sm text-gray-600 mb-1">
Available: <span className="font-semibold">{formatPoints(availablePoints)}</span> points
</p>
<p className="text-xs text-gray-500">
{formatPoints(Math.round(1 / redemptionRate))} points = $1.00
</p>
</div>
<div className="space-y-3">
<div>
<label className="text-sm text-gray-600 block mb-1">
Points to redeem
</label>
<Input
type="text"
value={pointsToRedeem}
onChange={handleInputChange}
placeholder="Enter points"
className={error ? "border-red-300" : ""}
/>
</div>
{/* Quick select buttons */}
<div className="flex gap-2">
<button
onClick={() => handleQuickSelect(0.25)}
className="flex-1 py-1 px-2 text-xs bg-yellow-100 hover:bg-yellow-200 text-yellow-800 rounded transition-colors"
>
25%
</button>
<button
onClick={() => handleQuickSelect(0.5)}
className="flex-1 py-1 px-2 text-xs bg-yellow-100 hover:bg-yellow-200 text-yellow-800 rounded transition-colors"
>
50%
</button>
<button
onClick={() => handleQuickSelect(0.75)}
className="flex-1 py-1 px-2 text-xs bg-yellow-100 hover:bg-yellow-200 text-yellow-800 rounded transition-colors"
>
75%
</button>
<button
onClick={() => handleQuickSelect(1)}
className="flex-1 py-1 px-2 text-xs bg-yellow-100 hover:bg-yellow-200 text-yellow-800 rounded transition-colors"
>
All
</button>
</div>
{/* Discount preview */}
{pointsValue > 0 && (
<div className="p-2 bg-white rounded border border-yellow-200">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600">Discount value:</span>
<span className="text-lg font-bold text-green-600">
-{formatCurrency(discountValue)}
</span>
</div>
</div>
)}
{error && (
<p className="text-sm text-red-600 flex items-center gap-1">
<Icon name="alert-circle" size={14} />
{error}
</p>
)}
<div className="flex gap-2">
{onCancel && (
<Button variant="ghost" onClick={onCancel} className="flex-1">
Cancel
</Button>
)}
<Button
onClick={handleRedeem}
disabled={!pointsValue || pointsValue < minRedemption}
className="flex-1"
>
Redeem {pointsValue > 0 ? formatPoints(pointsValue) : ""} Points
</Button>
</div>
</div>
</div>
);
}
export default RedeemPoints;
|