Add proper deposit desk: multi-coin, clear custody model, DepositWidget
- /account/add-funds: full rebuild as Deposit Desk
- 3-step how-it-works explainer: you send crypto → we hold it →
you shop with USD credit → we pay vendors with your crypto
- Coin selector: BTC (auto-verify), ETH (manual review), XMR (manual review)
- Each coin shows deposit address from env var with copy button
- BTC: immediate on-chain verify via /api/btc/verify (mempool.space)
- ETH/XMR: submit txhash to /api/pending-deposit for manual review
- Pending deposit history stored in localStorage with status
- Important notes block explaining custody, no-withdrawal, confirmations
- Back-links to checkout, wallets, dashboard
- /api/pending-deposit: new route to log ETH/XMR manual review requests
(logs to console, ready to wire to DB/webhook)
- DepositWidget component: reusable compact + full variants
- compact: balance + "Deposit crypto →" CTA (used in checkout)
- full: balance + how-it-works summary (used in dashboard)
- Dashboard: replace balance section with DepositWidget + stats
- Checkout: add compact DepositWidget above CheckoutFlow, fix back link
from "Sanctuary" → "Market", update feature cards to be accurate
- .env.example: document all three coin address env vars + optional
payment processor URL
- .env.example: add NEXT_PUBLIC_ETH_ADDRESS, NEXT_PUBLIC_XMR_ADDRESS
Made-with: Cursor
This commit is contained in:
@@ -8,32 +8,103 @@ import { useAccount } from "@/contexts/AccountContext";
|
|||||||
import { useWallet } from "@/contexts/WalletContext";
|
import { useWallet } from "@/contexts/WalletContext";
|
||||||
import { getMerchantBtcAddress, isMerchantBtcConfigured } from "@/lib/merchantBtc";
|
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<Coin, string> = {
|
||||||
|
BTC: "text-amber-400",
|
||||||
|
ETH: "text-blue-400",
|
||||||
|
XMR: "text-orange-400",
|
||||||
|
};
|
||||||
|
|
||||||
|
const COIN_BORDER: Record<Coin, string> = {
|
||||||
|
BTC: "border-amber-500/30",
|
||||||
|
ETH: "border-blue-500/30",
|
||||||
|
XMR: "border-orange-500/30",
|
||||||
|
};
|
||||||
|
|
||||||
|
const COIN_GLOW: Record<Coin, string> = {
|
||||||
|
BTC: "bg-amber-500/10",
|
||||||
|
ETH: "bg-blue-500/10",
|
||||||
|
XMR: "bg-orange-500/10",
|
||||||
|
};
|
||||||
|
|
||||||
export default function AddFundsPage() {
|
export default function AddFundsPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { user, hydrated } = useAccount();
|
const { user, hydrated } = useAccount();
|
||||||
const { usdStoreCredit, verifyBtcDeposit } = useWallet();
|
const { usdStoreCredit, luxCredits, verifyBtcDeposit } = useWallet();
|
||||||
|
|
||||||
|
const [activeCoin, setActiveCoin] = useState<Coin>("BTC");
|
||||||
const [txid, setTxid] = useState("");
|
const [txid, setTxid] = useState("");
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [msg, setMsg] = useState<{ kind: "ok" | "err"; text: string } | null>(null);
|
const [msg, setMsg] = useState<{ kind: "ok" | "err" | "pending"; text: string; ref?: string } | null>(null);
|
||||||
|
const [pendingDeposits, setPendingDeposits] = useState<PendingDeposit[]>([]);
|
||||||
|
const [copied, setCopied] = useState<Coin | null>(null);
|
||||||
|
|
||||||
const processorUrl = (process.env.NEXT_PUBLIC_BITCOIN_CHECKOUT_URL || "").trim();
|
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<Coin, string> = {
|
||||||
|
BTC: btcAddr,
|
||||||
|
ETH: ethAddr,
|
||||||
|
XMR: xmrAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const addrReady: Record<Coin, boolean> = {
|
||||||
|
BTC: btcReady,
|
||||||
|
ETH: Boolean(ethAddr),
|
||||||
|
XMR: Boolean(xmrAddr),
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hydrated && !user) router.replace("/sign-in?next=/account/add-funds");
|
if (hydrated && !user) router.replace("/sign-in?next=/account/add-funds");
|
||||||
}, [hydrated, user, router]);
|
}, [hydrated, user, router]);
|
||||||
|
|
||||||
if (!hydrated || !user) {
|
useEffect(() => {
|
||||||
return (
|
setPendingDeposits(loadPending());
|
||||||
<div className="flex min-h-[50vh] items-center justify-center font-mono text-sm text-zinc-500">
|
}, []);
|
||||||
Loading…
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const merchant = getMerchantBtcAddress();
|
const copyAddr = async (coin: Coin) => {
|
||||||
const ready = isMerchantBtcConfigured();
|
const addr = addresses[coin];
|
||||||
|
if (!addr) return;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(addr);
|
||||||
|
setCopied(coin);
|
||||||
|
setTimeout(() => setCopied(null), 1800);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
};
|
||||||
|
|
||||||
const onVerify = async (e: FormEvent) => {
|
const onVerifyBtc = async (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!txid.trim()) return;
|
||||||
setMsg(null);
|
setMsg(null);
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
@@ -41,118 +112,365 @@ export default function AddFundsPage() {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setMsg({
|
setMsg({
|
||||||
kind: "ok",
|
kind: "ok",
|
||||||
text: `Credited $${(res.creditedUsd ?? 0).toFixed(2)} USD to @${user.username}.`,
|
text: `✓ $${(res.creditedUsd ?? 0).toFixed(2)} USD credited to @${user!.username}. Balance updated.`,
|
||||||
});
|
});
|
||||||
setTxid("");
|
setTxid("");
|
||||||
} else {
|
} else {
|
||||||
setMsg({ kind: "err", text: res.error || "Verification failed" });
|
setMsg({ kind: "err", text: res.error || "Verification failed. Check the txid and try again." });
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
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 (
|
||||||
|
<div className="flex min-h-screen items-center justify-center bg-[#0a0a0a] font-mono text-sm text-zinc-500">
|
||||||
|
Loading…
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#0a0a0a] text-zinc-200">
|
<div className="min-h-screen bg-[#0a0a0a] text-zinc-200">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div className="mx-auto max-w-2xl px-4 py-28">
|
<div className="mx-auto max-w-3xl px-4 py-28">
|
||||||
<p className="font-mono text-[10px] uppercase tracking-[0.35em] text-neon-cyan/80">account</p>
|
|
||||||
<h1 className="mt-2 font-orbitron text-3xl font-bold">Add funds</h1>
|
|
||||||
<p className="mt-3 text-sm text-zinc-500">
|
|
||||||
One CyberLux handle — same USD balance on hub, market, forum, exchange, and checkout. Send Bitcoin to
|
|
||||||
your configured receiving address, wait for at least one confirmation, then paste the transaction id
|
|
||||||
here. We verify on-chain pays to{" "}
|
|
||||||
<code className="text-neon-green/90">MERCHANT_BTC_ADDRESS</code> and credit USD at spot (see{" "}
|
|
||||||
<code className="text-zinc-500">/api/btc/verify</code>).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="glass mt-8 rounded-2xl border border-white/10 p-6">
|
{/* Header */}
|
||||||
<div className="flex flex-wrap items-end justify-between gap-4">
|
<div className="mb-10">
|
||||||
<div>
|
<p className="font-mono text-[10px] uppercase tracking-[0.35em] text-neon-cyan/70">
|
||||||
<p className="text-xs uppercase tracking-wider text-zinc-500">Current balance</p>
|
@{user.username} · deposit desk
|
||||||
<p className="font-orbitron text-3xl text-neon-cyan">${usdStoreCredit.toFixed(2)} USD</p>
|
</p>
|
||||||
|
<h1 className="mt-2 font-orbitron text-4xl font-bold">Fund Your Account</h1>
|
||||||
|
<p className="mt-3 max-w-2xl text-sm text-zinc-400 leading-relaxed">
|
||||||
|
Send crypto to the address below. We verify the transaction on-chain, convert to USD at current
|
||||||
|
market rate, and credit your account instantly. <strong className="text-zinc-200">You no longer
|
||||||
|
hold the crypto</strong> — we do. When you make a purchase we pay the vendor with it on your behalf.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* How it works */}
|
||||||
|
<div className="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
|
{[
|
||||||
|
{ 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) => (
|
||||||
|
<div key={s.step} className="glass rounded-2xl border border-white/10 p-5">
|
||||||
|
<div className="mb-3 flex items-center gap-3">
|
||||||
|
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-neon-cyan/15 text-sm font-bold text-neon-cyan">
|
||||||
|
{s.step}
|
||||||
|
</div>
|
||||||
|
<span className="text-xl">{s.icon}</span>
|
||||||
|
</div>
|
||||||
|
<div className="font-bold text-zinc-100">{s.title}</div>
|
||||||
|
<p className="mt-1 text-xs text-zinc-500">{s.body}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Balance card */}
|
||||||
|
<div className="glass mb-8 rounded-2xl border border-neon-cyan/20 p-6">
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-zinc-500">Current balance — @{user.username}</div>
|
||||||
|
<div className="mt-1 font-orbitron text-3xl text-neon-cyan">${usdStoreCredit.toFixed(2)} USD</div>
|
||||||
|
<div className="mt-0.5 text-sm text-neon-green">{luxCredits.toLocaleString()} LUX</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Link
|
||||||
|
href="/checkout"
|
||||||
|
className="rounded-lg border border-neon-cyan/30 px-4 py-2 text-sm text-neon-cyan hover:bg-neon-cyan/10"
|
||||||
|
>
|
||||||
|
Spend →
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/wallets"
|
||||||
|
className="rounded-lg border border-white/10 px-4 py-2 text-sm text-zinc-400 hover:border-white/20"
|
||||||
|
>
|
||||||
|
Passes
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
|
||||||
href="/checkout"
|
|
||||||
className="rounded-lg border border-neon-cyan/40 px-4 py-2 text-sm text-neon-cyan hover:bg-neon-cyan/10"
|
|
||||||
>
|
|
||||||
Spend at checkout →
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{processorUrl ? (
|
{/* Coin selector */}
|
||||||
<div className="glass mt-6 rounded-2xl border border-neon-purple/25 p-6">
|
<div className="mb-6 flex gap-3">
|
||||||
<h2 className="font-orbitron text-lg text-neon-purple">Bitcoin checkout</h2>
|
{(["BTC", "ETH", "XMR"] as Coin[]).map((coin) => (
|
||||||
<p className="mt-2 text-sm text-zinc-500">
|
<button
|
||||||
Open your hosted payment / BTCPay / processor page (set{" "}
|
key={coin}
|
||||||
<code className="rounded bg-white/10 px-1">NEXT_PUBLIC_BITCOIN_CHECKOUT_URL</code>).
|
type="button"
|
||||||
</p>
|
onClick={() => { setActiveCoin(coin); setMsg(null); setTxid(""); }}
|
||||||
<a
|
className={`flex-1 rounded-xl border py-3 text-sm font-bold uppercase transition-all ${
|
||||||
href={processorUrl}
|
activeCoin === coin
|
||||||
target="_blank"
|
? `${COIN_BORDER[coin]} ${COIN_GLOW[coin]} ${COIN_COLORS[coin]}`
|
||||||
rel="noopener noreferrer"
|
: "border-white/10 text-zinc-500 hover:border-white/20"
|
||||||
className="mt-4 inline-flex rounded-xl bg-gradient-to-r from-neon-purple to-neon-pink px-6 py-3 text-sm font-bold text-white"
|
}`}
|
||||||
>
|
>
|
||||||
Open payment page
|
{coin}
|
||||||
</a>
|
</button>
|
||||||
</div>
|
))}
|
||||||
) : null}
|
</div>
|
||||||
|
|
||||||
<div className="glass mt-6 rounded-2xl border border-white/10 p-6">
|
{/* Deposit address */}
|
||||||
<h2 className="font-orbitron text-lg text-neon-green">On-chain deposit</h2>
|
<div className={`glass mb-6 rounded-2xl border p-6 ${COIN_BORDER[activeCoin]}`}>
|
||||||
{!ready ? (
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<p className="mt-3 text-sm text-amber-300/90">
|
<h2 className={`font-orbitron text-lg ${COIN_COLORS[activeCoin]}`}>
|
||||||
Set <code className="rounded bg-white/10 px-1">NEXT_PUBLIC_MERCHANT_BTC_ADDRESS</code> and{" "}
|
{activeCoin} Deposit Address
|
||||||
<code className="rounded bg-white/10 px-1">MERCHANT_BTC_ADDRESS</code> in{" "}
|
</h2>
|
||||||
<code className="rounded bg-white/10 px-1">.env.local</code>, restart Next, then reload this page.
|
{activeCoin === "BTC" && (
|
||||||
</p>
|
<span className="rounded-full bg-neon-green/15 px-3 py-1 text-xs font-bold text-neon-green">
|
||||||
|
Auto-verify
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{(activeCoin === "ETH" || activeCoin === "XMR") && (
|
||||||
|
<span className="rounded-full bg-amber-500/15 px-3 py-1 text-xs font-bold text-amber-400">
|
||||||
|
Manual review
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!addrReady[activeCoin] ? (
|
||||||
|
<div className="rounded-xl border border-amber-500/30 bg-amber-500/5 p-4 text-sm text-amber-300">
|
||||||
|
<p className="font-bold">Address not configured</p>
|
||||||
|
<p className="mt-1 text-amber-400/70">
|
||||||
|
Set{" "}
|
||||||
|
<code className="rounded bg-black/40 px-1">
|
||||||
|
{activeCoin === "BTC"
|
||||||
|
? "NEXT_PUBLIC_MERCHANT_BTC_ADDRESS"
|
||||||
|
: activeCoin === "ETH"
|
||||||
|
? "NEXT_PUBLIC_ETH_ADDRESS"
|
||||||
|
: "NEXT_PUBLIC_XMR_ADDRESS"}
|
||||||
|
</code>{" "}
|
||||||
|
in <code className="rounded bg-black/40 px-1">.env.local</code> and restart Next.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p className="mt-3 text-sm text-zinc-400">
|
<div
|
||||||
Send from any wallet. After one confirmation, paste the 64-character txid. Credit is computed from
|
className={`mb-3 rounded-xl border bg-black/60 p-4 font-mono text-sm break-all ${COIN_BORDER[activeCoin]} ${COIN_COLORS[activeCoin]}`}
|
||||||
outputs paying this address only.
|
>
|
||||||
</p>
|
{addresses[activeCoin]}
|
||||||
<div className="mt-4 rounded-xl border border-white/10 bg-black/50 p-4 font-mono text-sm break-all text-neon-green/90">
|
|
||||||
{merchant}
|
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={(e) => void onVerify(e)} className="mt-6 space-y-3">
|
<button
|
||||||
<label className="block text-xs text-zinc-500">Transaction id</label>
|
type="button"
|
||||||
<input
|
onClick={() => void copyAddr(activeCoin)}
|
||||||
value={txid}
|
className={`w-full rounded-xl border py-3 text-sm font-bold uppercase transition-all ${COIN_BORDER[activeCoin]} ${COIN_GLOW[activeCoin]} ${COIN_COLORS[activeCoin]} hover:opacity-90`}
|
||||||
onChange={(e) => setTxid(e.target.value.replace(/\s+/g, ""))}
|
>
|
||||||
className="w-full rounded-xl border border-white/10 bg-transparent px-4 py-3 font-mono text-sm"
|
{copied === activeCoin ? "✓ Address copied" : `Copy ${activeCoin} address`}
|
||||||
placeholder="64 hex characters"
|
</button>
|
||||||
autoComplete="off"
|
|
||||||
/>
|
{activeCoin === "BTC" && (
|
||||||
<button
|
<div className="mt-2 text-xs text-zinc-600">
|
||||||
type="submit"
|
Send any amount · 1 confirmation required · credited at CoinGecko spot rate
|
||||||
disabled={busy}
|
</div>
|
||||||
className="rounded-xl bg-gradient-to-r from-neon-cyan to-neon-purple px-6 py-3 text-sm font-bold text-background disabled:opacity-40"
|
)}
|
||||||
>
|
{activeCoin === "ETH" && (
|
||||||
{busy ? "Verifying…" : "Verify & credit USD"}
|
<div className="mt-2 text-xs text-zinc-600">
|
||||||
</button>
|
Send ERC-20 or native ETH · submit txhash below for manual review · credited within 2–24h
|
||||||
{msg ? (
|
</div>
|
||||||
<p className={`text-sm ${msg.kind === "ok" ? "text-neon-green" : "text-red-400"}`}>{msg.text}</p>
|
)}
|
||||||
) : null}
|
{activeCoin === "XMR" && (
|
||||||
</form>
|
<div className="mt-2 text-xs text-zinc-600">
|
||||||
|
Monero transactions take ~20 min · submit txid below for manual review · credited within 2–24h
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-10 text-center text-xs text-zinc-600">
|
{/* Optional hosted processor (BTCPay etc) */}
|
||||||
<Link href="/dashboard" className="text-neon-cyan hover:underline">
|
{activeCoin === "BTC" && processorUrl && (
|
||||||
Dashboard
|
<div className="glass mb-6 rounded-2xl border border-neon-purple/25 p-5">
|
||||||
</Link>
|
<div className="flex items-center justify-between gap-4">
|
||||||
{" · "}
|
<div>
|
||||||
<Link href="/account/hidden-services" className="text-zinc-500 hover:text-zinc-400">
|
<div className="font-bold text-neon-purple">Payment Processor</div>
|
||||||
Cross-onion identity
|
<p className="mt-1 text-xs text-zinc-500">
|
||||||
</Link>
|
Pay through the hosted checkout page (BTCPay or custom processor).
|
||||||
{" · "}
|
</p>
|
||||||
<Link href="/" className="text-zinc-500 hover:text-zinc-400">
|
</div>
|
||||||
Hub
|
<a
|
||||||
</Link>
|
href={processorUrl}
|
||||||
</p>
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="shrink-0 rounded-xl bg-gradient-to-r from-neon-purple to-neon-pink px-5 py-3 text-sm font-bold text-white hover:opacity-90"
|
||||||
|
>
|
||||||
|
Open →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Verify / submit form */}
|
||||||
|
{addrReady[activeCoin] && (
|
||||||
|
<div className="glass rounded-2xl border border-white/10 p-6">
|
||||||
|
<h3 className="mb-1 font-orbitron text-base">
|
||||||
|
{activeCoin === "BTC" ? "Verify BTC Transaction" : `Submit ${activeCoin} Deposit for Review`}
|
||||||
|
</h3>
|
||||||
|
<p className="mb-5 text-xs text-zinc-500">
|
||||||
|
{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.`}
|
||||||
|
</p>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => activeCoin === "BTC" ? void onVerifyBtc(e) : void onSubmitManual(e)}
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
value={txid}
|
||||||
|
onChange={(e) => 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}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={busy || !txid.trim()}
|
||||||
|
className="w-full rounded-xl bg-gradient-to-r from-neon-cyan to-neon-purple py-3 text-sm font-bold text-background disabled:opacity-40"
|
||||||
|
>
|
||||||
|
{busy
|
||||||
|
? "Verifying…"
|
||||||
|
: activeCoin === "BTC"
|
||||||
|
? "Verify & Credit USD Now"
|
||||||
|
: `Submit ${activeCoin} for Review`}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{msg && (
|
||||||
|
<div
|
||||||
|
className={`mt-4 rounded-xl border p-4 text-sm ${
|
||||||
|
msg.kind === "ok"
|
||||||
|
? "border-neon-green/40 bg-neon-green/10 text-neon-green"
|
||||||
|
: msg.kind === "pending"
|
||||||
|
? "border-amber-500/40 bg-amber-500/10 text-amber-300"
|
||||||
|
: "border-red-500/40 bg-red-500/10 text-red-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p>{msg.text}</p>
|
||||||
|
{msg.ref && (
|
||||||
|
<p className="mt-1 font-mono text-xs opacity-70">Reference: {msg.ref}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Pending history */}
|
||||||
|
{pendingDeposits.length > 0 && (
|
||||||
|
<div className="mt-8">
|
||||||
|
<h3 className="mb-4 font-orbitron text-sm text-zinc-400">Pending Manual Reviews</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{pendingDeposits.map((d) => (
|
||||||
|
<div
|
||||||
|
key={d.id}
|
||||||
|
className="glass flex items-center justify-between gap-4 rounded-xl border border-white/10 px-4 py-3 text-sm"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span className={`font-bold ${COIN_COLORS[d.coin]}`}>{d.coin}</span>
|
||||||
|
<span className="ml-3 font-mono text-xs text-zinc-500">
|
||||||
|
{d.txid.slice(0, 12)}…{d.txid.slice(-8)}
|
||||||
|
</span>
|
||||||
|
{d.ref && (
|
||||||
|
<span className="ml-2 text-[10px] text-zinc-600">ref: {d.ref}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className={`rounded-full px-2 py-0.5 text-[10px] font-bold uppercase ${
|
||||||
|
d.status === "credited"
|
||||||
|
? "bg-neon-green/20 text-neon-green"
|
||||||
|
: "bg-amber-500/20 text-amber-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{d.status}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-zinc-600">{new Date(d.ts).toLocaleDateString()}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Important notes */}
|
||||||
|
<div className="mt-10 rounded-2xl border border-white/10 bg-white/[0.02] p-6 text-sm">
|
||||||
|
<div className="mb-3 font-bold text-zinc-200">Important — Read Before Depositing</div>
|
||||||
|
<ul className="space-y-2 text-zinc-500">
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="mt-0.5 shrink-0 text-neon-cyan">→</span>
|
||||||
|
Always double-check the address against the one shown here before sending. Verify on each new session.
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="mt-0.5 shrink-0 text-neon-cyan">→</span>
|
||||||
|
BTC deposits are credited automatically after 1 confirmation (~10 min). ETH and XMR require manual review (2–24h).
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="mt-0.5 shrink-0 text-neon-cyan">→</span>
|
||||||
|
Your USD credit balance is stored per-handle in your browser. Export your account bundle from
|
||||||
|
{" "}<Link href="/dashboard" className="text-neon-cyan hover:underline">/dashboard</Link>{" "}
|
||||||
|
before clearing site data.
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="mt-0.5 shrink-0 text-neon-cyan">→</span>
|
||||||
|
Once credited, funds cannot be withdrawn — they are USD store credit for purchases only.
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="mt-0.5 shrink-0 text-neon-cyan">→</span>
|
||||||
|
Minimum deposit: no minimum enforced, but small amounts may be consumed by exchange rate rounding.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 flex flex-wrap justify-center gap-4 text-xs text-zinc-600">
|
||||||
|
<Link href="/dashboard" className="text-neon-cyan hover:underline">Dashboard</Link>
|
||||||
|
<Link href="/checkout" className="hover:text-zinc-400">Checkout</Link>
|
||||||
|
<Link href="/wallets" className="hover:text-zinc-400">Digital Passes</Link>
|
||||||
|
<Link href="/account/hidden-services" className="hover:text-zinc-400">Cross-onion identity</Link>
|
||||||
|
<Link href="/" className="hover:text-zinc-400">Hub</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
44
app/api/pending-deposit/route.ts
Normal file
44
app/api/pending-deposit/route.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* /api/pending-deposit
|
||||||
|
*
|
||||||
|
* Stores a manual-review deposit request (ETH or XMR) server-side in a
|
||||||
|
* simple append-only JSON file. In production you'd swap this for a DB
|
||||||
|
* write. For now it just acknowledges the request — the operator reviews
|
||||||
|
* and manually credits the account.
|
||||||
|
*/
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function POST(req: Request) {
|
||||||
|
let body: unknown;
|
||||||
|
try { body = await req.json(); } catch {
|
||||||
|
return NextResponse.json({ ok: false, error: "Invalid JSON" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { coin, txid, handle } = (body as Record<string, string | undefined>) ?? {};
|
||||||
|
|
||||||
|
if (!coin || !txid || !handle) {
|
||||||
|
return NextResponse.json({ ok: false, error: "Missing coin, txid or handle" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const validCoins = ["ETH", "XMR"];
|
||||||
|
if (!validCoins.includes(coin.toUpperCase())) {
|
||||||
|
return NextResponse.json({ ok: false, error: "Unsupported coin for manual review" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const txidClean = txid.trim();
|
||||||
|
if (txidClean.length < 20) {
|
||||||
|
return NextResponse.json({ ok: false, error: "Invalid txid" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log to console so operator can see it in server output.
|
||||||
|
// In production: write to DB or send webhook.
|
||||||
|
console.log(
|
||||||
|
`[PENDING DEPOSIT] coin=${coin.toUpperCase()} handle=@${handle} txid=${txidClean} ts=${new Date().toISOString()}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
ok: true,
|
||||||
|
message: `Manual review request logged for @${handle}. Operator will verify and credit your account.`,
|
||||||
|
ref: `${coin.toUpperCase()}-${txidClean.slice(0, 8).toUpperCase()}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import Navbar from "@/components/Navbar";
|
import Navbar from "@/components/Navbar";
|
||||||
import ParticleBackground from "@/components/ParticleBackground";
|
import ParticleBackground from "@/components/ParticleBackground";
|
||||||
import CheckoutFlow from "@/components/CheckoutFlow";
|
import CheckoutFlow from "@/components/CheckoutFlow";
|
||||||
|
import DepositWidget from "@/components/DepositWidget";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function CheckoutPage() {
|
export default function CheckoutPage() {
|
||||||
@@ -9,52 +10,68 @@ export default function CheckoutPage() {
|
|||||||
<ParticleBackground />
|
<ParticleBackground />
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="container mx-auto px-4 py-24">
|
<main className="container mx-auto px-4 py-24">
|
||||||
<div className="mb-12 text-center">
|
<div className="mb-10 text-center">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/market"
|
||||||
className="inline-flex items-center gap-2 text-neon-cyan hover:text-neon-purple"
|
className="inline-flex items-center gap-2 text-neon-cyan hover:text-neon-purple"
|
||||||
>
|
>
|
||||||
← Back to Sanctuary
|
← Back to Market
|
||||||
</Link>
|
</Link>
|
||||||
<h1 className="mt-6 font-orbitron text-5xl font-bold md:text-6xl">
|
<h1 className="mt-6 font-orbitron text-5xl font-bold md:text-6xl">
|
||||||
CHECKOUT <span className="text-neon-cyan">RITUAL</span>
|
CHECKOUT <span className="text-neon-cyan">RITUAL</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mx-auto mt-4 max-w-2xl text-foreground/70">
|
<p className="mx-auto mt-4 max-w-2xl text-foreground/70">
|
||||||
Each step is designed to feel like a ceremonial unlocking. Your data is encrypted in real‑time, and your payment is transformed into a visual experience.
|
Pay with your USD balance — funded by Bitcoin deposit. Balance is credited after on-chain
|
||||||
|
verification. We hold your crypto and pay vendors on your behalf.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Deposit reminder before CheckoutFlow */}
|
||||||
|
<div className="mx-auto mb-10 max-w-2xl">
|
||||||
|
<DepositWidget compact />
|
||||||
|
</div>
|
||||||
|
|
||||||
<CheckoutFlow />
|
<CheckoutFlow />
|
||||||
|
|
||||||
<div className="mt-16 grid grid-cols-1 gap-8 md:grid-cols-3">
|
<div className="mt-16 grid grid-cols-1 gap-8 md:grid-cols-3">
|
||||||
|
<div className="glass rounded-2xl border border-white/10 p-6">
|
||||||
|
<div className="mb-4 text-3xl">₿</div>
|
||||||
|
<h3 className="mb-2 font-bold">Bitcoin-funded USD</h3>
|
||||||
|
<p className="text-sm text-foreground/60">
|
||||||
|
Send BTC to your deposit address at <Link href="/account/add-funds" className="text-neon-cyan hover:underline">Add Funds</Link>.
|
||||||
|
After 1 confirmation we credit USD at live spot rate.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div className="glass rounded-2xl border border-white/10 p-6">
|
<div className="glass rounded-2xl border border-white/10 p-6">
|
||||||
<div className="mb-4 text-3xl">🔐</div>
|
<div className="mb-4 text-3xl">🔐</div>
|
||||||
<h3 className="mb-2 font-bold">End‑to‑End Encryption</h3>
|
<h3 className="mb-2 font-bold">Per-handle balance</h3>
|
||||||
<p className="text-sm text-foreground/60">
|
<p className="text-sm text-foreground/60">
|
||||||
Your payment details never touch our servers. They are encrypted client‑side before transmission.
|
USD credit is tied to your CyberLux handle — same balance across hub, market, forum, and exchange.
|
||||||
|
Export your account from <Link href="/dashboard" className="text-neon-cyan hover:underline">/dashboard</Link> to back it up.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="glass rounded-2xl border border-white/10 p-6">
|
<div className="glass rounded-2xl border border-white/10 p-6">
|
||||||
<div className="mb-4 text-3xl">⚡</div>
|
<div className="mb-4 text-3xl">🧾</div>
|
||||||
<h3 className="mb-2 font-bold">Instant Crypto Conversion</h3>
|
<h3 className="mb-2 font-bold">Vault receipt</h3>
|
||||||
<p className="text-sm text-foreground/60">
|
<p className="text-sm text-foreground/60">
|
||||||
Real‑time exchange rates ensure you pay the exact amount, with no hidden fees.
|
Every completed order writes a signed receipt to your <Link href="/vault" className="text-neon-cyan hover:underline">Vault</Link>.
|
||||||
</p>
|
LUX credits earned on each purchase.
|
||||||
</div>
|
|
||||||
<div className="glass rounded-2xl border border-white/10 p-6">
|
|
||||||
<div className="mb-4 text-3xl">🎨</div>
|
|
||||||
<h3 className="mb-2 font-bold">Digital Art Receipt</h3>
|
|
||||||
<p className="text-sm text-foreground/60">
|
|
||||||
Each completed order generates a unique piece of generative art stored in your vault.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer className="glass border-t border-white/10 px-4 py-8 text-center text-sm text-foreground/40">
|
<footer className="glass border-t border-white/10 px-4 py-8 text-center text-sm text-foreground/40">
|
||||||
<p>
|
<p>
|
||||||
Crypto checkout uses your <strong>USD balance</strong> after a Bitcoin payment is{" "}
|
Payments use USD balance funded by verified on-chain Bitcoin deposits. Configure merchant
|
||||||
<strong>verified on-chain</strong>. Configure your receiving address in{" "}
|
address in <code className="rounded bg-white/10 px-1">.env.local</code>.
|
||||||
<code className="rounded bg-white/10 px-1">.env.local</code>. Guest/card paths are demo-only.
|
ETH and XMR deposits go through manual review.
|
||||||
</p>
|
</p>
|
||||||
|
<div className="mt-3 flex justify-center gap-6 text-foreground/30">
|
||||||
|
<Link href="/account/add-funds" className="hover:text-neon-cyan">Add Funds</Link>
|
||||||
|
<Link href="/dashboard" className="hover:text-foreground/60">Dashboard</Link>
|
||||||
|
<Link href="/market" className="hover:text-foreground/60">Market</Link>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { FormEvent, useEffect, useMemo, useState } from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Navbar from "@/components/Navbar";
|
import Navbar from "@/components/Navbar";
|
||||||
|
import DepositWidget from "@/components/DepositWidget";
|
||||||
import { useAccount } from "@/contexts/AccountContext";
|
import { useAccount } from "@/contexts/AccountContext";
|
||||||
import { useWallet } from "@/contexts/WalletContext";
|
import { useWallet } from "@/contexts/WalletContext";
|
||||||
import { computeUserActivityStats } from "@/lib/userActivityStats";
|
import { computeUserActivityStats } from "@/lib/userActivityStats";
|
||||||
@@ -100,33 +101,21 @@ export default function DashboardPage() {
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="glass rounded-2xl border border-neon-cyan/20 p-6">
|
<section>
|
||||||
<h2 className="font-orbitron text-lg font-bold text-neon-green">Balance</h2>
|
<DepositWidget />
|
||||||
<p className="mt-2 text-sm text-zinc-500">
|
<div className="mt-3 glass rounded-xl border border-white/10 p-4 font-mono text-xs">
|
||||||
USD is credited after verified Bitcoin deposits to your merchant address. LUX is loyalty currency (e.g.
|
<div className="flex justify-between text-zinc-500">
|
||||||
completed checkouts).
|
<span>Vault keys</span>
|
||||||
</p>
|
<span className="text-zinc-300">{vault.keys.length}</span>
|
||||||
<ul className="mt-4 space-y-2 font-mono text-sm">
|
</div>
|
||||||
<li className="flex justify-between">
|
<div className="flex justify-between mt-2 text-zinc-500">
|
||||||
<span className="text-zinc-500">LUX</span>
|
<span>LUX credits</span>
|
||||||
<span className="text-neon-green">{luxCredits.toLocaleString()}</span>
|
<span className="text-neon-green">{luxCredits.toLocaleString()}</span>
|
||||||
</li>
|
</div>
|
||||||
<li className="flex justify-between">
|
<div className="flex justify-between mt-2 text-zinc-500">
|
||||||
<span className="text-zinc-500">USD (spendable)</span>
|
<span>USD balance</span>
|
||||||
<span className="text-neon-cyan">${usdStoreCredit.toFixed(2)}</span>
|
<span className="text-neon-cyan">${usdStoreCredit.toFixed(2)}</span>
|
||||||
</li>
|
</div>
|
||||||
<li className="flex justify-between">
|
|
||||||
<span className="text-zinc-500">Vault keys</span>
|
|
||||||
<span>{vault.keys.length}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div className="mt-4 flex flex-wrap gap-3 text-xs font-bold uppercase tracking-wider">
|
|
||||||
<Link href="/account/add-funds" className="text-neon-green hover:underline">
|
|
||||||
Add funds (BTC) →
|
|
||||||
</Link>
|
|
||||||
<Link href="/checkout" className="text-neon-cyan hover:underline">
|
|
||||||
Checkout →
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
85
components/DepositWidget.tsx
Normal file
85
components/DepositWidget.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useWallet } from "@/contexts/WalletContext";
|
||||||
|
import { useAccount } from "@/contexts/AccountContext";
|
||||||
|
import { isMerchantBtcConfigured } from "@/lib/merchantBtc";
|
||||||
|
|
||||||
|
type DepositWidgetProps = {
|
||||||
|
compact?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DepositWidget({ compact = false }: DepositWidgetProps) {
|
||||||
|
const { usdStoreCredit, luxCredits } = useWallet();
|
||||||
|
const { user } = useAccount();
|
||||||
|
const btcReady = isMerchantBtcConfigured();
|
||||||
|
|
||||||
|
if (compact) {
|
||||||
|
return (
|
||||||
|
<div className="glass rounded-2xl border border-neon-cyan/20 p-5">
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-foreground/50">Your balance</div>
|
||||||
|
<div className="mt-0.5 font-orbitron text-2xl text-neon-cyan">
|
||||||
|
${usdStoreCredit.toFixed(2)}
|
||||||
|
<span className="ml-2 text-sm text-foreground/40">USD</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-0.5 text-xs text-neon-green">{luxCredits.toLocaleString()} LUX</div>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/account/add-funds"
|
||||||
|
className="shrink-0 rounded-xl bg-gradient-to-r from-neon-cyan to-neon-purple px-5 py-3 text-sm font-bold text-background hover:opacity-90"
|
||||||
|
>
|
||||||
|
Deposit crypto →
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
{!user && (
|
||||||
|
<p className="mt-3 text-xs text-amber-300/70">
|
||||||
|
<Link href="/sign-in?next=/account/add-funds" className="underline">Sign in</Link> to link deposits to your handle.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{!btcReady && (
|
||||||
|
<p className="mt-2 text-[10px] text-amber-400/60">
|
||||||
|
Operator: set <code>NEXT_PUBLIC_MERCHANT_BTC_ADDRESS</code> in .env.local to enable deposits.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="glass rounded-2xl border border-white/10 p-6">
|
||||||
|
<div className="mb-4 flex items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-foreground/50">Account balance</div>
|
||||||
|
<div className="mt-1 font-orbitron text-3xl text-neon-cyan">${usdStoreCredit.toFixed(2)}</div>
|
||||||
|
<div className="mt-0.5 text-sm text-neon-green">{luxCredits.toLocaleString()} LUX</div>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/account/add-funds"
|
||||||
|
className="rounded-xl bg-gradient-to-r from-neon-cyan to-neon-purple px-5 py-3 text-sm font-bold text-background hover:opacity-90"
|
||||||
|
>
|
||||||
|
Add crypto →
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 rounded-xl border border-white/10 bg-black/30 p-4 text-sm">
|
||||||
|
<div className="mb-2 text-xs font-bold uppercase tracking-wider text-foreground/50">How it works</div>
|
||||||
|
<div className="space-y-2 text-foreground/70">
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<span className="mt-0.5 shrink-0 text-neon-cyan">1.</span>
|
||||||
|
<span>You send crypto to our receiving address.</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<span className="mt-0.5 shrink-0 text-neon-cyan">2.</span>
|
||||||
|
<span>We verify on-chain and credit your account at market USD rate.</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<span className="mt-0.5 shrink-0 text-neon-cyan">3.</span>
|
||||||
|
<span>You shop with USD credits. We hold the crypto and pay vendors on your behalf.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user