- 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
160 lines
6.5 KiB
TypeScript
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>
|
|
);
|
|
}
|