first commit
This commit is contained in:
268
src/components/sections/SpendSection.tsx
Normal file
268
src/components/sections/SpendSection.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useUser } from "@/lib/context";
|
||||
import { Coffee, ShoppingBag, Music, Film, Dumbbell, Ticket, Check, Coins, Copy, ArrowRight, ShieldCheck } from "lucide-react";
|
||||
|
||||
interface BrandVoucher {
|
||||
id: string;
|
||||
brand: string;
|
||||
discount: string;
|
||||
cost: number;
|
||||
icon: React.ReactNode;
|
||||
color: string;
|
||||
bgGlow: string;
|
||||
}
|
||||
|
||||
export default function SpendSection() {
|
||||
const { profile, claimVoucher, vouchers } = useUser();
|
||||
const [redeemedCode, setRedeemedCode] = useState<{ brand: string; code: string } | null>(null);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [copiedId, setCopiedId] = useState<string | null>(null);
|
||||
|
||||
const availableVouchers: BrandVoucher[] = [
|
||||
{
|
||||
id: "v-sbux",
|
||||
brand: "Starbucks",
|
||||
discount: "$10 Gift Card",
|
||||
cost: 150,
|
||||
icon: <Coffee className="w-8 h-8 text-emerald-400" />,
|
||||
color: "border-emerald-500/20 text-emerald-400 hover:border-emerald-500/50",
|
||||
bgGlow: "rgba(16, 185, 129, 0.15)",
|
||||
},
|
||||
{
|
||||
id: "v-nike",
|
||||
brand: "Nike",
|
||||
discount: "20% Discount Code",
|
||||
cost: 300,
|
||||
icon: <ShoppingBag className="w-8 h-8 text-white" />,
|
||||
color: "border-white/10 text-white hover:border-white/30",
|
||||
bgGlow: "rgba(255, 255, 255, 0.15)",
|
||||
},
|
||||
{
|
||||
id: "v-spotify",
|
||||
brand: "Spotify",
|
||||
discount: "1 Month Premium",
|
||||
cost: 200,
|
||||
icon: <Music className="w-8 h-8 text-green-400" />,
|
||||
color: "border-green-500/20 text-green-400 hover:border-green-500/50",
|
||||
bgGlow: "rgba(34, 197, 94, 0.15)",
|
||||
},
|
||||
{
|
||||
id: "v-netflix",
|
||||
brand: "Netflix",
|
||||
discount: "1 Month Free UltraHD",
|
||||
cost: 450,
|
||||
icon: <Film className="w-8 h-8 text-red-500" />,
|
||||
color: "border-red-500/20 text-red-500 hover:border-red-500/50",
|
||||
bgGlow: "rgba(239, 68, 68, 0.15)",
|
||||
},
|
||||
{
|
||||
id: "v-gymshark",
|
||||
brand: "Gymshark",
|
||||
discount: "30% Storewide Voucher",
|
||||
cost: 250,
|
||||
icon: <Dumbbell className="w-8 h-8 text-amber-500" />,
|
||||
color: "border-amber-500/20 text-amber-500 hover:border-amber-500/50",
|
||||
bgGlow: "rgba(245, 158, 11, 0.15)",
|
||||
},
|
||||
{
|
||||
id: "v-tickets",
|
||||
brand: "Ticketmaster",
|
||||
discount: "$25 Concert Coupon",
|
||||
cost: 400,
|
||||
icon: <Ticket className="w-8 h-8 text-sky-400" />,
|
||||
color: "border-sky-500/20 text-sky-400 hover:border-sky-500/50",
|
||||
bgGlow: "rgba(56, 189, 248, 0.15)",
|
||||
},
|
||||
];
|
||||
|
||||
const handleRedeem = (voucher: BrandVoucher) => {
|
||||
if (profile.lytBalance < voucher.cost) {
|
||||
setErrorMessage(`Insufficient LYT balance. You need ${voucher.cost - profile.lytBalance} more LYTs for this voucher.`);
|
||||
setTimeout(() => setErrorMessage(null), 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
const res = claimVoucher(voucher.brand, voucher.discount, voucher.cost);
|
||||
if (res.success && res.code) {
|
||||
setRedeemedCode({ brand: voucher.brand, code: res.code });
|
||||
// Clear popup after a while
|
||||
setTimeout(() => setRedeemedCode(null), 10000);
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = (id: string, text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopiedId(id);
|
||||
setTimeout(() => setCopiedId(null), 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="spend" className="py-24 bg-dark relative overflow-hidden">
|
||||
{/* Visual background glows */}
|
||||
<div className="absolute top-1/4 right-0 w-[500px] h-[500px] rounded-full bg-primary/5 blur-[150px] pointer-events-none" />
|
||||
<div className="absolute bottom-1/4 left-0 w-[500px] h-[500px] rounded-full bg-accent-pink/5 blur-[150px] pointer-events-none" />
|
||||
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||
|
||||
{/* Section Header */}
|
||||
<div className="text-center max-w-3xl mx-auto mb-16 space-y-4">
|
||||
<span className="text-accent-purple font-extrabold text-xs tracking-widest uppercase">
|
||||
Spend Your LYTs
|
||||
</span>
|
||||
<h2 className="text-3xl sm:text-5xl font-extrabold font-sora text-white leading-tight">
|
||||
Claim Vouchers at <span className="text-transparent bg-clip-text bg-gradient-to-r from-accent-pink via-accent-purple to-accent-lime">Your Favorite Brands</span>
|
||||
</h2>
|
||||
<p className="text-white/60 text-base sm:text-lg">
|
||||
Redeem your accumulated LYT coins for gift cards, subscriptions, and discounts. Click redeem to generate a checkout code.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Dynamic Warning Alert */}
|
||||
{errorMessage && (
|
||||
<div className="max-w-xl mx-auto mb-8 bg-red-950/40 border border-red-500/20 text-red-200 px-5 py-4 rounded-2xl flex items-center justify-between text-sm animate-bounce">
|
||||
<span>{errorMessage}</span>
|
||||
<a href="#play" className="text-primary font-bold underline hover:no-underline">Earn LYTs fast</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Success Modal Popup */}
|
||||
{redeemedCode && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-dark/80 backdrop-blur-sm animate-in fade-in duration-200">
|
||||
<div className="glassmorphism max-w-md w-full p-8 rounded-3xl border border-accent-lime/30 text-center space-y-6 shadow-[0_0_50px_rgba(200,255,55,0.15)]">
|
||||
<div className="w-16 h-16 rounded-full bg-accent-lime/10 border border-accent-lime/30 mx-auto flex items-center justify-center text-accent-lime">
|
||||
<ShieldCheck className="w-8 h-8 animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-2xl font-bold font-sora text-white">Redemption Successful!</h3>
|
||||
<p className="text-white/60 text-sm">
|
||||
You successfully claimed your <span className="text-white font-bold">{redeemedCode.brand}</span> voucher code.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Code Shelf */}
|
||||
<div className="bg-white/5 border border-white/10 rounded-2xl p-4 flex items-center justify-between">
|
||||
<code className="text-xl font-mono font-bold tracking-wider text-accent-lime">{redeemedCode.code}</code>
|
||||
<button
|
||||
onClick={() => copyToClipboard("modal", redeemedCode.code)}
|
||||
className="p-2.5 rounded-xl bg-white/5 hover:bg-white/10 text-white/80 hover:text-white transition-colors cursor-pointer"
|
||||
>
|
||||
{copiedId === "modal" ? <Check className="w-5 h-5 text-accent-lime" /> : <Copy className="w-5 h-5" />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
onClick={() => setRedeemedCode(null)}
|
||||
className="w-full py-3 rounded-xl bg-primary text-dark font-extrabold text-sm tracking-wider cursor-pointer"
|
||||
>
|
||||
Awesome, Thanks!
|
||||
</button>
|
||||
<span className="text-[10px] text-white/40">Code is also saved to your Active Vouchers panel below.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Voucher Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-20">
|
||||
{availableVouchers.map((voucher) => {
|
||||
const isAffordable = profile.lytBalance >= voucher.cost;
|
||||
return (
|
||||
<div
|
||||
key={voucher.id}
|
||||
style={{
|
||||
boxShadow: `0 10px 30px -15px ${voucher.bgGlow}`,
|
||||
}}
|
||||
className={`glassmorphism rounded-3xl p-6 flex flex-col justify-between border transition-all duration-300 transform hover:-translate-y-1.5 ${voucher.color}`}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Brand Header */}
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="p-3.5 bg-white/5 rounded-2xl border border-white/5 flex items-center justify-center">
|
||||
{voucher.icon}
|
||||
</div>
|
||||
<div className="flex items-center space-x-1.5 px-3 py-1 rounded-full bg-white/5 border border-white/10 font-sora font-extrabold text-xs text-white">
|
||||
<Coins className="w-3.5 h-3.5 text-primary" />
|
||||
<span>{voucher.cost} LYTs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div className="space-y-1.5">
|
||||
<h3 className="text-xl font-bold font-sora text-white">{voucher.brand}</h3>
|
||||
<p className="text-white/60 text-sm font-semibold">{voucher.discount}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom action */}
|
||||
<div className="mt-8">
|
||||
<button
|
||||
onClick={() => handleRedeem(voucher)}
|
||||
className={`w-full py-3.5 rounded-xl font-extrabold text-xs tracking-wider uppercase transition-all flex items-center justify-center gap-1.5 cursor-pointer ${
|
||||
isAffordable
|
||||
? "bg-white/5 hover:bg-primary hover:text-dark text-white border border-white/10 hover:border-primary shadow-sm"
|
||||
: "bg-white/2 text-white/20 border border-white/5 cursor-not-allowed"
|
||||
}`}
|
||||
>
|
||||
<span>Redeem Voucher</span>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Claimed Vouchers Display */}
|
||||
{vouchers.length > 0 && (
|
||||
<div className="border-t border-white/5 pt-16">
|
||||
<h3 className="text-2xl font-bold font-sora text-white mb-6 flex items-center gap-2">
|
||||
<span>Your Claimed Vouchers</span>
|
||||
<span className="text-xs px-2.5 py-1 rounded-full bg-accent-lime/10 border border-accent-lime/20 text-accent-lime">
|
||||
{vouchers.length} Total
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{vouchers.map((v) => (
|
||||
<div
|
||||
key={v.id}
|
||||
className="bg-[#0b0b0b] border border-white/5 rounded-2xl p-5 flex items-center justify-between gap-4 animate-in slide-in-from-bottom-2 duration-300"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<span className="text-white font-extrabold text-sm">{v.brand}</span>
|
||||
<p className="text-white/60 text-xs">{v.discount}</p>
|
||||
<span className="text-[10px] text-white/40 block">Claimed on {v.claimedAt}</span>
|
||||
</div>
|
||||
|
||||
<div className="text-right space-y-1.5">
|
||||
<span className="inline-block px-2.5 py-1 bg-white/5 border border-white/10 rounded-lg text-xs font-mono font-extrabold text-accent-lime">
|
||||
{v.code}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => copyToClipboard(v.id, v.code)}
|
||||
className="text-[10px] font-bold text-primary hover:underline flex items-center justify-end gap-1 ml-auto cursor-pointer"
|
||||
>
|
||||
{copiedId === v.id ? (
|
||||
<span className="text-accent-lime">Copied!</span>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-3 h-3" />
|
||||
<span>Copy Code</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user