Files
dark-lord/app/account/add-funds/page.tsx
drjones 2f928fbdc4 10x every page: real interactions, kill fake content, wire everything
- ChatWidget: remove illegal seeds, real localStorage per-handle chat,
  honest bot replies about market/forum/funds
- ForumBoard: wire to real forumState (loadForum/addThread/vote), kill
  fake stats and illegal seed posts
- Home page: privacy features list reflects reality, footer links real
- Links: kill all alert() calls, replace fake onions with real clearnet
  privacy resources + internal route grid
- Support: per-coin copied state, env-driven addresses, real BTC addr
- Inner circle: wire to AccountContext, tier system from LUX balance,
  remove hardcoded admin/shadow credentials and fake trading signals
- Drop box: real sealed-note localStorage system, honest about no
  anonymous upload capability, real file picker with receipt
- Messages: fully functional per-handle localStorage chat, AI-style
  contextual bot replies, clear history, honest about local storage
- Wallets: pivot from fake PayPal accounts to Digital Access Passes,
  wire Buy Now to cart via ShopProduct interface
- Testimonials: wire submit form to localStorage, interactive star
  rating 1-10, display submitted reviews above the fold
- Raffle: use real merchant BTC address, real per-handle entry storage,
  honest LUX-only prize disclaimer, fix 0x address
- Drops/Lotto: real number picker 1-49 with Quick Pick, ticket
  submission, match display against drawn numbers, demo disclaimer
- Sanctuary: real 4-4-6-2 breathing timer, meditation passage with
  timer, candle-lighting with localStorage notes
- Game: full playable Void Pong with canvas physics, CPU AI, scoring,
  rally counter, localStorage high score
- Security analysis: honest architecture breakdown with real grades,
  layer-by-layer analysis, practical OPSEC guide, fiction banner
- Trust: compute real scores from actual localStorage data (LUX,
  USD, forum posts, testimonials), FAQ accordion

Made-with: Cursor
2026-04-16 00:55:28 -07:00

160 lines
6.5 KiB
TypeScript

"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";
export default function AddFundsPage() {
const router = useRouter();
const { user, hydrated } = useAccount();
const { usdStoreCredit, verifyBtcDeposit } = useWallet();
const [txid, setTxid] = useState("");
const [busy, setBusy] = useState(false);
const [msg, setMsg] = useState<{ kind: "ok" | "err"; text: string } | null>(null);
const processorUrl = (process.env.NEXT_PUBLIC_BITCOIN_CHECKOUT_URL || "").trim();
useEffect(() => {
if (hydrated && !user) router.replace("/sign-in?next=/account/add-funds");
}, [hydrated, user, router]);
if (!hydrated || !user) {
return (
<div className="flex min-h-[50vh] items-center justify-center font-mono text-sm text-zinc-500">
Loading
</div>
);
}
const merchant = getMerchantBtcAddress();
const ready = isMerchantBtcConfigured();
const onVerify = async (e: FormEvent) => {
e.preventDefault();
setMsg(null);
setBusy(true);
try {
const res = await verifyBtcDeposit(txid.trim());
if (res.ok) {
setMsg({
kind: "ok",
text: `Credited $${(res.creditedUsd ?? 0).toFixed(2)} USD to @${user.username}.`,
});
setTxid("");
} else {
setMsg({ kind: "err", text: res.error || "Verification failed" });
}
} finally {
setBusy(false);
}
};
return (
<div className="min-h-screen bg-[#0a0a0a] text-zinc-200">
<Navbar />
<div className="mx-auto max-w-2xl 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">
<div className="flex flex-wrap items-end justify-between gap-4">
<div>
<p className="text-xs uppercase tracking-wider text-zinc-500">Current balance</p>
<p className="font-orbitron text-3xl text-neon-cyan">${usdStoreCredit.toFixed(2)} USD</p>
</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>
{processorUrl ? (
<div className="glass mt-6 rounded-2xl border border-neon-purple/25 p-6">
<h2 className="font-orbitron text-lg text-neon-purple">Bitcoin checkout</h2>
<p className="mt-2 text-sm text-zinc-500">
Open your hosted payment / BTCPay / processor page (set{" "}
<code className="rounded bg-white/10 px-1">NEXT_PUBLIC_BITCOIN_CHECKOUT_URL</code>).
</p>
<a
href={processorUrl}
target="_blank"
rel="noopener noreferrer"
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
</a>
</div>
) : null}
<div className="glass mt-6 rounded-2xl border border-white/10 p-6">
<h2 className="font-orbitron text-lg text-neon-green">On-chain deposit</h2>
{!ready ? (
<p className="mt-3 text-sm text-amber-300/90">
Set <code className="rounded bg-white/10 px-1">NEXT_PUBLIC_MERCHANT_BTC_ADDRESS</code> and{" "}
<code className="rounded bg-white/10 px-1">MERCHANT_BTC_ADDRESS</code> in{" "}
<code className="rounded bg-white/10 px-1">.env.local</code>, restart Next, then reload this page.
</p>
) : (
<>
<p className="mt-3 text-sm text-zinc-400">
Send from any wallet. After one confirmation, paste the 64-character txid. Credit is computed from
outputs paying this address only.
</p>
<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>
<form onSubmit={(e) => void onVerify(e)} className="mt-6 space-y-3">
<label className="block text-xs text-zinc-500">Transaction id</label>
<input
value={txid}
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"
placeholder="64 hex characters"
autoComplete="off"
/>
<button
type="submit"
disabled={busy}
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"
>
{busy ? "Verifying…" : "Verify & credit USD"}
</button>
{msg ? (
<p className={`text-sm ${msg.kind === "ok" ? "text-neon-green" : "text-red-400"}`}>{msg.text}</p>
) : null}
</form>
</>
)}
</div>
<p className="mt-10 text-center text-xs text-zinc-600">
<Link href="/dashboard" className="text-neon-cyan hover:underline">
Dashboard
</Link>
{" · "}
<Link href="/account/hidden-services" className="text-zinc-500 hover:text-zinc-400">
Cross-onion identity
</Link>
{" · "}
<Link href="/" className="text-zinc-500 hover:text-zinc-400">
Hub
</Link>
</p>
</div>
</div>
);
}