"use client"; import { FormEvent, useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import Navbar from "@/components/Navbar"; import { useAccount } from "@/contexts/AccountContext"; import { useWallet } from "@/contexts/WalletContext"; import { getMerchantBtcAddress, isMerchantBtcConfigured } from "@/lib/merchantBtc"; type Coin = "BTC" | "ETH" | "XMR"; type PendingDeposit = { id: string; coin: Coin; txid: string; ts: number; status: "pending" | "credited"; ref?: string; }; const PENDING_KEY = "cyberlux-pending-deposits-v1"; function loadPending(): PendingDeposit[] { if (typeof window === "undefined") return []; try { return JSON.parse(localStorage.getItem(PENDING_KEY) ?? "[]") as PendingDeposit[]; } catch { return []; } } function savePending(d: PendingDeposit[]) { if (typeof window !== "undefined") localStorage.setItem(PENDING_KEY, JSON.stringify(d)); } function shortId() { return Math.random().toString(36).slice(2, 10).toUpperCase(); } const COIN_COLORS: Record = { BTC: "text-amber-400", ETH: "text-blue-400", XMR: "text-orange-400", }; const COIN_BORDER: Record = { BTC: "border-amber-500/30", ETH: "border-blue-500/30", XMR: "border-orange-500/30", }; const COIN_GLOW: Record = { BTC: "bg-amber-500/10", ETH: "bg-blue-500/10", XMR: "bg-orange-500/10", }; export default function AddFundsPage() { const router = useRouter(); const { user, hydrated } = useAccount(); const { usdStoreCredit, luxCredits, verifyBtcDeposit } = useWallet(); const [activeCoin, setActiveCoin] = useState("BTC"); const [txid, setTxid] = useState(""); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState<{ kind: "ok" | "err" | "pending"; text: string; ref?: string } | null>(null); const [pendingDeposits, setPendingDeposits] = useState([]); const [copied, setCopied] = useState(null); const processorUrl = (process.env.NEXT_PUBLIC_BITCOIN_CHECKOUT_URL || "").trim(); const btcAddr = getMerchantBtcAddress(); const btcReady = isMerchantBtcConfigured(); const ethAddr = (process.env.NEXT_PUBLIC_ETH_ADDRESS || "").trim(); const xmrAddr = (process.env.NEXT_PUBLIC_XMR_ADDRESS || "").trim(); const addresses: Record = { BTC: btcAddr, ETH: ethAddr, XMR: xmrAddr, }; const addrReady: Record = { BTC: btcReady, ETH: Boolean(ethAddr), XMR: Boolean(xmrAddr), }; useEffect(() => { if (hydrated && !user) router.replace("/sign-in?next=/account/add-funds"); }, [hydrated, user, router]); useEffect(() => { setPendingDeposits(loadPending()); }, []); const copyAddr = async (coin: Coin) => { const addr = addresses[coin]; if (!addr) return; try { await navigator.clipboard.writeText(addr); setCopied(coin); setTimeout(() => setCopied(null), 1800); } catch { /* ignore */ } }; const onVerifyBtc = async (e: FormEvent) => { e.preventDefault(); if (!txid.trim()) return; setMsg(null); setBusy(true); try { const res = await verifyBtcDeposit(txid.trim()); if (res.ok) { setMsg({ kind: "ok", text: `✓ $${(res.creditedUsd ?? 0).toFixed(2)} USD credited to @${user!.username}. Balance updated.`, }); setTxid(""); } else { setMsg({ kind: "err", text: res.error || "Verification failed. Check the txid and try again." }); } } finally { setBusy(false); } }; const onSubmitManual = async (e: FormEvent) => { e.preventDefault(); if (!txid.trim() || !user) return; setMsg(null); setBusy(true); try { const res = await fetch("/api/pending-deposit", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ coin: activeCoin, txid: txid.trim(), handle: user.username }), }); const json = (await res.json()) as { ok: boolean; message?: string; ref?: string; error?: string }; if (json.ok) { const entry: PendingDeposit = { id: shortId(), coin: activeCoin, txid: txid.trim(), ts: Date.now(), status: "pending", ref: json.ref, }; const updated = [entry, ...pendingDeposits]; setPendingDeposits(updated); savePending(updated); setMsg({ kind: "pending", text: json.message ?? "Deposit request submitted for manual review.", ref: json.ref }); setTxid(""); } else { setMsg({ kind: "err", text: json.error ?? "Submission failed." }); } } catch { setMsg({ kind: "err", text: "Network error. Try again." }); } finally { setBusy(false); } }; if (!hydrated || !user) { return (
Loading…
); } return (
{/* Header */}

@{user.username} · deposit desk

Fund Your Account

Send crypto to the address below. We verify the transaction on-chain, convert to USD at current market rate, and credit your account instantly. You no longer hold the crypto — we do. When you make a purchase we pay the vendor with it on your behalf.

{/* How it works */}
{[ { step: "1", icon: "₿", title: "You send crypto", body: "Send BTC, ETH, or XMR to our receiving address." }, { step: "2", icon: "📊", title: "We verify & convert", body: "On-chain confirmation. Credited at live market USD rate." }, { step: "3", icon: "🛒", title: "You shop", body: "Spend USD credits across every CyberLux surface. We pay vendors in crypto." }, ].map((s) => (
{s.step}
{s.icon}
{s.title}

{s.body}

))}
{/* Balance card */}
Current balance — @{user.username}
${usdStoreCredit.toFixed(2)} USD
{luxCredits.toLocaleString()} LUX
Spend → Passes
{/* Coin selector */}
{(["BTC", "ETH", "XMR"] as Coin[]).map((coin) => ( ))}
{/* Deposit address */}

{activeCoin} Deposit Address

{activeCoin === "BTC" && ( Auto-verify )} {(activeCoin === "ETH" || activeCoin === "XMR") && ( Manual review )}
{!addrReady[activeCoin] ? (

Address not configured

Set{" "} {activeCoin === "BTC" ? "NEXT_PUBLIC_MERCHANT_BTC_ADDRESS" : activeCoin === "ETH" ? "NEXT_PUBLIC_ETH_ADDRESS" : "NEXT_PUBLIC_XMR_ADDRESS"} {" "} in .env.local and restart Next.

) : ( <>
{addresses[activeCoin]}
{activeCoin === "BTC" && (
Send any amount · 1 confirmation required · credited at CoinGecko spot rate
)} {activeCoin === "ETH" && (
Send ERC-20 or native ETH · submit txhash below for manual review · credited within 2–24h
)} {activeCoin === "XMR" && (
Monero transactions take ~20 min · submit txid below for manual review · credited within 2–24h
)} )}
{/* Optional hosted processor (BTCPay etc) */} {activeCoin === "BTC" && processorUrl && (
Payment Processor

Pay through the hosted checkout page (BTCPay or custom processor).

Open →
)} {/* Verify / submit form */} {addrReady[activeCoin] && (

{activeCoin === "BTC" ? "Verify BTC Transaction" : `Submit ${activeCoin} Deposit for Review`}

{activeCoin === "BTC" ? "After at least 1 confirmation, paste the 64-character transaction ID. We check mempool.space and credit immediately." : `After sending, paste the transaction hash. Our team verifies on-chain and credits your @${user.username} account. You'll see it in your pending history below.`}

activeCoin === "BTC" ? void onVerifyBtc(e) : void onSubmitManual(e)} className="space-y-4" > setTxid(e.target.value.trim())} placeholder={ activeCoin === "BTC" ? "Bitcoin txid — 64 hex characters" : activeCoin === "ETH" ? "Ethereum txhash — 0x…" : "Monero txid" } className="w-full rounded-xl border border-white/10 bg-transparent px-4 py-3 font-mono text-sm placeholder-zinc-600 focus:border-neon-cyan/40 focus:outline-none" autoComplete="off" maxLength={100} />
{msg && (

{msg.text}

{msg.ref && (

Reference: {msg.ref}

)}
)}
)} {/* Pending history */} {pendingDeposits.length > 0 && (

Pending Manual Reviews

{pendingDeposits.map((d) => (
{d.coin} {d.txid.slice(0, 12)}…{d.txid.slice(-8)} {d.ref && ( ref: {d.ref} )}
{d.status} {new Date(d.ts).toLocaleDateString()}
))}
)} {/* Important notes */}
Important — Read Before Depositing
  • Always double-check the address against the one shown here before sending. Verify on each new session.
  • BTC deposits are credited automatically after 1 confirmation (~10 min). ETH and XMR require manual review (2–24h).
  • Your USD credit balance is stored per-handle in your browser. Export your account bundle from {" "}/dashboard{" "} before clearing site data.
  • Once credited, funds cannot be withdrawn — they are USD store credit for purchases only.
  • Minimum deposit: no minimum enforced, but small amounts may be consumed by exchange rate rounding.
Dashboard Checkout Digital Passes Cross-onion identity Hub
); }