"use client"; import { useState, useEffect, useCallback } from "react"; import Link from "next/link"; import ThemedLayout from "@/components/layouts/ThemedLayout"; import { getMerchantBtcAddress, isMerchantBtcConfigured } from "@/lib/merchantBtc"; import { useAccount } from "@/contexts/AccountContext"; const PRIZES = [ { place: "1st", prize: "500 LUX Credits", sub: "Applied to your CyberLux handle." }, { place: "2nd", prize: "250 LUX Credits", sub: "Applied to your CyberLux handle." }, { place: "3rd", prize: "100 LUX Credits", sub: "Applied to your CyberLux handle." }, { place: "Runner-up ×5", prize: "Handler Pass", sub: "Digital access pass upgrade." }, ]; const DRAW_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days in ms const DRAW_EPOCH = new Date("2026-04-14T00:00:00Z").getTime(); function getNextDraw() { const now = Date.now(); const elapsed = now - DRAW_EPOCH; const cycles = Math.floor(elapsed / DRAW_INTERVAL_MS); return DRAW_EPOCH + (cycles + 1) * DRAW_INTERVAL_MS; } function formatRemaining(ms: number) { if (ms <= 0) return "00:00:00"; const totalSec = Math.floor(ms / 1000); const d = Math.floor(totalSec / 86400); const h = Math.floor((totalSec % 86400) / 3600); const m = Math.floor((totalSec % 3600) / 60); const s = totalSec % 60; if (d > 0) return `${d}d ${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; } const TICKET_PRICE_BTC = "0.0001"; const TICKET_ENTRY_KEY = "cyberlux-raffle-entries-v1"; type Entry = { handle: string; txid: string; ts: string }; function loadEntries(): Entry[] { if (typeof window === "undefined") return []; try { return JSON.parse(localStorage.getItem(TICKET_ENTRY_KEY) ?? "[]") as Entry[]; } catch { return []; } } function saveEntries(e: Entry[]) { if (typeof window === "undefined") return; localStorage.setItem(TICKET_ENTRY_KEY, JSON.stringify(e)); } export default function RafflePage() { const { user } = useAccount(); const [remaining, setRemaining] = useState(0); const [entries, setEntries] = useState([]); const [txid, setTxid] = useState(""); const [status, setStatus] = useState<"idle" | "success" | "error">("idle"); const [errMsg, setErrMsg] = useState(""); const btcAddr = getMerchantBtcAddress(); const btcReady = isMerchantBtcConfigured(); useEffect(() => { setEntries(loadEntries()); const tick = () => setRemaining(getNextDraw() - Date.now()); tick(); const id = setInterval(tick, 1000); return () => clearInterval(id); }, []); const handleEnter = useCallback( (e: React.FormEvent) => { e.preventDefault(); const t = txid.trim(); if (!t || t.length < 20) { setErrMsg("Enter a valid Bitcoin transaction ID."); setStatus("error"); return; } if (entries.some((x) => x.txid === t)) { setErrMsg("This txid has already been submitted."); setStatus("error"); return; } const entry: Entry = { handle: user?.username ?? "anon", txid: t, ts: new Date().toISOString(), }; const updated = [...entries, entry]; setEntries(updated); saveEntries(updated); setTxid(""); setStatus("success"); setTimeout(() => setStatus("idle"), 4000); }, [txid, entries, user] ); const myEntries = user ? entries.filter((e) => e.handle === user.username) : []; return (
⚠ Prize pool is in LUX credits — not BTC. Entry fee covers onchain raffle infrastructure.

Weekly Shadow Raffle

Next Draw
{formatRemaining(remaining)}
Ticket Price
{TICKET_PRICE_BTC} BTC
Entries This Round
{entries.length}

Prize Breakdown

{PRIZES.map((p) => (
{p.place}: {p.prize}

{p.sub}

))}

Enter Your Ticket

Send exactly {TICKET_PRICE_BTC} BTC to the address below. After at least 1 confirmation, paste your transaction ID here. Your handle is registered as an entrant. Draw happens at the countdown above.

{btcReady ? (
{btcAddr}
) : (
Merchant address not configured. Set NEXT_PUBLIC_MERCHANT_BTC_ADDRESS in .env.local.
)}
setTxid(e.target.value)} placeholder="Bitcoin transaction ID (64 hex chars)…" className="flex-1 border-2 border-white bg-black px-4 py-3 text-sm text-white focus:border-[#00ff41] focus:outline-none" maxLength={64} />
{status === "success" && (

✓ Entry registered for @{user?.username ?? "anon"}. Good luck.

)} {status === "error" && (

{errMsg}

)} {!user && (

Sign in{" "} to attach entries to your handle.

)} {myEntries.length > 0 && (
Your entries this round
{myEntries.map((e) => (
{e.txid.slice(0, 12)}…{e.txid.slice(-8)} · {new Date(e.ts).toLocaleDateString()}
))}
)}
Draw is via deterministic hash of the winning block header at the draw timestamp. Results posted in{" "} /forum . Prizes are LUX credits — no fiat or BTC payout. Entry fees are non-refundable.
); }