Files
dark-lord/components/CheckoutFlow.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

576 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useAccount } from "@/contexts/AccountContext";
import { useCart } from "@/contexts/CartContext";
import { useWallet } from "@/contexts/WalletContext";
import { getMerchantBtcAddress, isMerchantBtcConfigured } from "@/lib/merchantBtc";
import { estimateUsdForCryptoAmount } from "@/lib/shopFx";
const ENCRYPTION_BTC_FEE: Record<"standard" | "enhanced" | "quantum", number> = {
standard: 0,
enhanced: 0.001,
quantum: 0.005,
};
const CheckoutFlow = () => {
const { lines, setLineQty, removeLine, clearCart, hydrated: cartHydrated } = useCart();
const [step, setStep] = useState(1);
const [paymentMethod, setPaymentMethod] = useState<"crypto" | "guest" | "card">("crypto");
const [guestEmail, setGuestEmail] = useState("");
const [encryptionLevel, setEncryptionLevel] = useState<"standard" | "enhanced" | "quantum">("enhanced");
const [btcUsd, setBtcUsd] = useState<number | null>(null);
const [txidInput, setTxidInput] = useState("");
const [verifyMsg, setVerifyMsg] = useState<{ kind: "ok" | "err"; text: string } | null>(null);
const [verifyBusy, setVerifyBusy] = useState(false);
const [payError, setPayError] = useState<string | null>(null);
const [orderComplete, setOrderComplete] = useState(false);
const [completedOrderId, setCompletedOrderId] = useState<string | null>(null);
const { user } = useAccount();
const { usdStoreCredit, spendUsdStoreCredit, verifyBtcDeposit, earnLuxCredits, addVaultReceipt } = useWallet();
useEffect(() => {
let cancelled = false;
(async () => {
try {
const res = await fetch(
"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
);
const j = (await res.json()) as { bitcoin?: { usd?: number } };
const p = j.bitcoin?.usd;
if (!cancelled && p && Number.isFinite(p)) setBtcUsd(p);
} catch {
if (!cancelled) setBtcUsd(96000);
}
})();
return () => {
cancelled = true;
};
}, []);
const spotBtc = btcUsd ?? 96000;
const subtotalUsd = useMemo(
() =>
Math.round(
lines.reduce(
(sum, l) => sum + estimateUsdForCryptoAmount(l.price, l.currency, spotBtc) * l.qty,
0,
) * 100,
) / 100,
[lines, spotBtc],
);
const encryptionFeeUsd = useMemo(() => {
const rate = btcUsd ?? 96000;
return Math.round(ENCRYPTION_BTC_FEE[encryptionLevel] * rate * 100) / 100;
}, [encryptionLevel, btcUsd]);
const orderTotalUsd = useMemo(
() => Math.round((subtotalUsd + encryptionFeeUsd) * 100) / 100,
[subtotalUsd, encryptionFeeUsd],
);
const orderTotalBtc = useMemo(() => {
const r = btcUsd && btcUsd > 0 ? btcUsd : spotBtc;
if (!r || r <= 0) return null;
return Math.round((orderTotalUsd / r) * 1e8) / 1e8;
}, [orderTotalUsd, btcUsd, spotBtc]);
const merchantAddr = getMerchantBtcAddress();
const merchantReady = isMerchantBtcConfigured();
const btcPayUri = useMemo(() => {
if (!merchantReady || orderTotalBtc == null) return "";
return `bitcoin:${merchantAddr}?amount=${orderTotalBtc}`;
}, [merchantReady, merchantAddr, orderTotalBtc]);
const handleVerify = useCallback(async () => {
setVerifyMsg(null);
setVerifyBusy(true);
try {
const result = await verifyBtcDeposit(txidInput.trim());
if (result.ok) {
setVerifyMsg({
kind: "ok",
text: `Confirmed. Added $${result.creditedUsd?.toFixed(2) ?? "0.00"} USD to your spendable balance.`,
});
setTxidInput("");
} else {
setVerifyMsg({ kind: "err", text: result.error || "Verification failed" });
}
} finally {
setVerifyBusy(false);
}
}, [txidInput, verifyBtcDeposit]);
const tryCompleteOrder = () => {
setPayError(null);
if (paymentMethod === "crypto") {
if (!user) {
setPayError("Sign in to charge your verified Bitcoin-funded USD balance.");
return;
}
if (!spendUsdStoreCredit(orderTotalUsd)) {
setPayError(
`Insufficient USD balance. You need $${orderTotalUsd.toFixed(2)}. Add funds under Account → Add funds, then verify your txid.`,
);
return;
}
}
const oid =
typeof crypto !== "undefined" && "randomUUID" in crypto
? `CYBR-${crypto.randomUUID().slice(0, 8).toUpperCase()}`
: `CYBR-${Date.now().toString(36).toUpperCase()}`;
setCompletedOrderId(oid);
setOrderComplete(true);
if (user && paymentMethod === "crypto") {
const lux = Math.min(500, Math.max(25, Math.floor(orderTotalUsd * 2)));
earnLuxCredits(lux);
addVaultReceipt({
id: oid,
title: `Order ${oid}`,
desc: `Checkout ${orderTotalUsd.toFixed(2)} USD · ${encryptionLevel} tier.`,
date: new Date().toISOString().slice(0, 10),
severity: "normal",
});
}
clearCart();
};
const steps = [
{ number: 1, title: "Cart", description: "Review items" },
{ number: 2, title: "Payment", description: "Choose method" },
{ number: 3, title: "Encryption", description: "Select privacy level" },
{ number: 4, title: "Confirmation", description: "Complete ritual" },
];
const handleNext = () => {
if (step === 1 && lines.length === 0) return;
if (step < 4) setStep(step + 1);
};
const handlePrev = () => {
if (step > 1) setStep(step - 1);
};
return (
<div className="glass mx-auto max-w-4xl rounded-3xl border border-white/10 p-8 md:p-12">
<div className="mb-12">
<div className="flex justify-between">
{steps.map((s) => (
<div key={s.number} className="flex flex-col items-center">
<div
className={`flex h-12 w-12 items-center justify-center rounded-full border-2 text-lg font-bold ${step >= s.number
? "border-neon-cyan bg-neon-cyan/20 text-neon-cyan"
: "border-white/20 text-foreground/40"
}`}
>
{s.number}
</div>
<div className="mt-2 text-center">
<div className="text-sm font-medium">{s.title}</div>
<div className="text-xs text-foreground/50">{s.description}</div>
</div>
</div>
))}
</div>
<div className="relative -top-6 -z-10 h-1 bg-white/10">
<div
className="h-1 bg-gradient-to-r from-neon-cyan to-neon-purple transition-all duration-500"
style={{ width: `${((step - 1) / 3) * 100}%` }}
/>
</div>
</div>
<div className="mb-10">
{step === 1 && (
<div>
<h3 className="mb-6 font-orbitron text-2xl font-bold">YOUR CART</h3>
{!cartHydrated ? (
<p className="text-sm text-foreground/50">Loading cart</p>
) : lines.length === 0 ? (
<div className="rounded-xl border border-dashed border-white/15 p-10 text-center">
<p className="text-foreground/70">Your cart is empty.</p>
<p className="mt-2 text-sm text-foreground/50">Add SKUs from the hub or candy market.</p>
<Link
href="/market"
className="mt-6 inline-block rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-8 py-3 text-sm font-bold text-background"
>
Browse catalog
</Link>
</div>
) : (
<div className="space-y-4">
{lines.map((item) => {
const unitUsd = estimateUsdForCryptoAmount(item.price, item.currency, spotBtc);
const lineUsd = Math.round(unitUsd * item.qty * 100) / 100;
return (
<div
key={item.productId}
className="flex flex-col gap-4 rounded-xl bg-white/5 p-4 sm:flex-row sm:items-center sm:justify-between"
>
<div className="min-w-0 flex-1">
<div className="font-medium">{item.name}</div>
<div className="mt-1 font-mono text-xs text-foreground/50">
{item.price} {item.currency} each · ${unitUsd.toFixed(2)} USD @ spot
</div>
<div className="mt-3 flex flex-wrap items-center gap-2">
<span className="text-xs text-foreground/50">Qty</span>
<button
type="button"
className="rounded border border-white/15 px-2 py-0.5 text-sm hover:bg-white/10"
onClick={() => setLineQty(item.productId, item.qty - 1)}
aria-label="Decrease quantity"
>
</button>
<span className="w-8 text-center font-mono text-sm">{item.qty}</span>
<button
type="button"
className="rounded border border-white/15 px-2 py-0.5 text-sm hover:bg-white/10"
onClick={() => setLineQty(item.productId, item.qty + 1)}
aria-label="Increase quantity"
>
+
</button>
<button
type="button"
className="ml-2 text-xs text-red-400 hover:underline"
onClick={() => removeLine(item.productId)}
>
Remove
</button>
</div>
</div>
<div className="text-right sm:shrink-0">
<div className="font-orbitron text-xl text-neon-cyan">${lineUsd.toLocaleString()}</div>
<div className="text-xs text-foreground/50">USD est.</div>
</div>
</div>
);
})}
</div>
)}
{lines.length > 0 ? (
<div className="mt-8 flex justify-between border-t border-white/10 pt-6 text-xl">
<span>Subtotal (USD est.)</span>
<span className="font-orbitron font-bold text-neon-cyan">
${subtotalUsd.toLocaleString()}
</span>
</div>
) : null}
</div>
)}
{step === 2 && (
<div>
<h3 className="mb-6 font-orbitron text-2xl font-bold">SELECT PAYMENT METHOD</h3>
<div className="mb-6 rounded-xl border border-neon-green/30 bg-neon-green/5 px-4 py-3 text-sm">
<span className="font-bold text-neon-green">USD balance: </span>
<span className="font-orbitron">${usdStoreCredit.toFixed(2)}</span>
<span className="text-foreground/60">
{" "}
per signed-in handle after verified Bitcoin deposits.
</span>
</div>
{paymentMethod === "crypto" && !user ? (
<p className="mb-4 rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-200">
<Link href="/sign-in?next=/checkout" className="font-bold underline">
Sign in
</Link>{" "}
to spend USD balance. You can still browse deposit instructions below.
</p>
) : null}
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
<button
type="button"
className={`glass rounded-2xl border p-6 text-center transition-all ${paymentMethod === "crypto" ? "border-neon-cyan ring-2 ring-neon-cyan/30" : "border-white/10"
}`}
onClick={() => setPaymentMethod("crypto")}
>
<div className="mb-4 text-4xl"></div>
<div className="font-bold">Cryptocurrency</div>
<div className="mt-2 text-sm text-foreground/60">Bitcoin checkout balance</div>
</button>
<button
type="button"
className={`glass rounded-2xl border p-6 text-center transition-all ${paymentMethod === "guest" ? "border-neon-cyan ring-2 ring-neon-cyan/30" : "border-white/10"
}`}
onClick={() => setPaymentMethod("guest")}
>
<div className="mb-4 text-4xl">👤</div>
<div className="font-bold">Guest Checkout</div>
<div className="mt-2 text-sm text-foreground/60">No USD charge order completes locally only</div>
</button>
<button
type="button"
className={`glass rounded-2xl border p-6 text-center transition-all ${paymentMethod === "card" ? "border-neon-cyan ring-2 ring-neon-cyan/30" : "border-white/10"
}`}
onClick={() => setPaymentMethod("card")}
>
<div className="mb-4 text-4xl">💳</div>
<div className="font-bold">Card</div>
<div className="mt-2 text-sm text-foreground/60">Not enabled use Bitcoin balance instead</div>
</button>
</div>
{paymentMethod === "crypto" && (
<div className="mt-8 space-y-4 rounded-xl bg-white/5 p-4">
<div className="text-sm text-foreground/60">Bitcoin-only · estimated order total after encryption step</div>
<div className="font-orbitron text-2xl text-neon-cyan">
${orderTotalUsd.toLocaleString()} USD
{orderTotalBtc != null && btcUsd != null && (
<span className="ml-3 text-lg text-foreground/70">
( {orderTotalBtc} BTC @ ${btcUsd.toLocaleString()}/BTC)
</span>
)}
</div>
<p className="text-xs text-foreground/50">
Final total includes the encryption add-on you pick in the next step. Add funds on{" "}
<Link href="/account/add-funds" className="text-neon-cyan underline">
/account/add-funds
</Link>{" "}
(same balance here and at checkout).
</p>
</div>
)}
{paymentMethod === "guest" && (
<div className="mt-8">
<label className="mb-2 block font-medium">Email for receipt (optional)</label>
<input
type="email"
className="glass w-full rounded-xl border border-white/10 bg-transparent px-4 py-3"
placeholder="anonymous@example.com"
value={guestEmail}
onChange={(e) => setGuestEmail(e.target.value)}
/>
<p className="mt-2 text-sm text-foreground/60">
Guest checkout does not touch your USD balance; nothing is sent to a payment processor.
</p>
</div>
)}
<div className="mt-10 border-t border-white/10 pt-8">
<h4 className="mb-4 font-orbitron text-lg font-bold text-neon-cyan">Bitcoin deposit · processor</h4>
{!merchantReady ? (
<p className="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>, then restart the app.
</p>
) : (
<>
<p className="mb-3 text-sm text-foreground/70">
Send Bitcoin to this address. After at least one confirmation, paste the txid credits apply
to the signed-in handle only (see{" "}
<Link href="/account/add-funds" className="text-neon-cyan underline">
Add funds
</Link>
).
</p>
<div className="rounded-xl border border-white/10 bg-black/40 p-4 font-mono text-sm break-all">
{merchantAddr}
</div>
{btcPayUri && (
<div className="mt-4 flex flex-col gap-4 md:flex-row md:items-start">
<img
src={`https://api.qrserver.com/v1/create-qr-code/?size=160x160&data=${encodeURIComponent(btcPayUri)}`}
alt="Bitcoin payment QR"
width={160}
height={160}
className="rounded-lg border border-white/10"
/>
<div className="text-xs text-foreground/50">
QR encodes a suggested payment URI for the current cart estimate (you may send any
amount; all verified BTC to this address becomes USD credit).
</div>
</div>
)}
<div className="mt-6 space-y-2">
<label className="text-sm font-medium">Transaction ID (after confirmed)</label>
<input
className="glass w-full rounded-xl border border-white/10 bg-transparent px-4 py-3 font-mono text-sm"
placeholder="64-character txid"
value={txidInput}
onChange={(e) => setTxidInput(e.target.value.replace(/\s+/g, ""))}
/>
<button
type="button"
disabled={verifyBusy}
onClick={() => void handleVerify()}
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-6 py-2 text-sm font-bold text-background disabled:opacity-40"
>
{verifyBusy ? "Verifying…" : "Verify & add USD credit"}
</button>
{verifyMsg && (
<p
className={`text-sm ${verifyMsg.kind === "ok" ? "text-neon-green" : "text-red-400"}`}
>
{verifyMsg.text}
</p>
)}
</div>
</>
)}
</div>
</div>
)}
{step === 3 && (
<div>
<h3 className="mb-6 font-orbitron text-2xl font-bold">ENCRYPTION LEVEL</h3>
<p className="mb-8 text-foreground/70">
Choose how securely your data is protected. Higher levels add to your order total in USD.
</p>
<div className="space-y-4">
{[
{ level: "standard" as const, name: "Stealth Encryption", desc: "Militarygrade encryption, basic anonymity", icon: "🕵️" },
{ level: "enhanced" as const, name: "Ghost Protocol", desc: "Postquantum cryptography, zerotrace", icon: "👻" },
{ level: "quantum" as const, name: "ZeroTrace", desc: "Experimental, quantumentangled proofs", icon: "⚛️" },
].map((opt) => (
<button
key={opt.level}
type="button"
className={`glass flex w-full items-center justify-between rounded-2xl border p-6 text-left ${encryptionLevel === opt.level ? "border-neon-cyan ring-2 ring-neon-cyan/30" : "border-white/10"
}`}
onClick={() => setEncryptionLevel(opt.level)}
>
<div>
<div className="flex items-center gap-4">
<span className="text-3xl">{opt.icon}</span>
<div>
<div className="font-bold">{opt.name}</div>
<div className="text-sm text-foreground/60">{opt.desc}</div>
</div>
</div>
</div>
<div className="text-right font-orbitron text-xl font-bold text-neon-cyan">
{opt.level === "standard"
? "FREE"
: `+$${(Math.round(ENCRYPTION_BTC_FEE[opt.level] * (btcUsd ?? 96000) * 100) / 100).toFixed(2)}`}
</div>
</button>
))}
</div>
<div className="mt-8 rounded-xl bg-gradient-to-r from-neon-cyan/10 to-neon-purple/10 p-6">
<div className="font-orbitron font-bold">Order total so far</div>
<div className="mt-2 text-2xl text-neon-cyan">
${orderTotalUsd.toLocaleString()} USD
</div>
</div>
</div>
)}
{step === 4 && !orderComplete && (
<div className="text-center">
<h3 className="mb-4 font-orbitron text-3xl font-bold">COMPLETE PAYMENT</h3>
<div className="glass mx-auto mb-8 max-w-md rounded-2xl border border-white/10 p-6 text-left">
<div className="flex justify-between text-sm">
<span className="text-foreground/60">Total due</span>
<span className="font-orbitron font-bold text-neon-cyan">
${orderTotalUsd.toLocaleString()}
</span>
</div>
<div className="mt-2 flex justify-between text-sm">
<span className="text-foreground/60">Your USD balance</span>
<span className="font-orbitron">${usdStoreCredit.toFixed(2)}</span>
</div>
<div className="mt-2 flex justify-between text-sm">
<span className="text-foreground/60">Payment path</span>
<span className="font-bold">
{paymentMethod === "crypto" ? "Bitcoin-funded USD balance" : "Guest / card (local only)"}
</span>
</div>
</div>
{payError && <p className="mx-auto mb-6 max-w-xl text-sm text-red-400">{payError}</p>}
<button
type="button"
onClick={tryCompleteOrder}
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-10 py-3 font-bold text-background"
>
{paymentMethod === "crypto" ? "Charge my USD balance & complete" : "Complete order (local)"}
</button>
{paymentMethod === "crypto" && (
<p className="mx-auto mt-6 max-w-xl text-xs text-foreground/50">
Crypto checkout pulls from your verified USD balance only. Send BTC on the payment step and
verify the txid so your balance matches the total above.
</p>
)}
</div>
)}
{step === 4 && orderComplete && (
<div className="text-center">
<div className="mb-8">
<div className="inline-flex h-24 w-24 items-center justify-center rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple">
<span className="text-4xl"></span>
</div>
</div>
<h3 className="mb-4 font-orbitron text-3xl font-bold">RITUAL COMPLETE</h3>
<p className="mx-auto max-w-2xl text-foreground/70">
Your order is now encrypted with {encryptionLevel} protection. A unique digital artwork receipt
has been generated and stored in your vault.
</p>
<div className="my-10">
<div className="glass mx-auto max-w-md rounded-2xl border border-white/10 p-6">
<div className="font-orbitron text-2xl font-bold text-neon-cyan">
ORDER #{completedOrderId ?? "—"}
</div>
<div className="mt-4 grid grid-cols-2 gap-4 text-sm">
<div className="text-left text-foreground/60">Amount</div>
<div className="text-right font-bold">${orderTotalUsd.toLocaleString()} USD</div>
<div className="text-left text-foreground/60">Privacy</div>
<div className="text-right font-bold">{encryptionLevel.toUpperCase()}</div>
<div className="text-left text-foreground/60">Payment</div>
<div className="truncate text-right font-mono text-neon-green">
{paymentMethod === "crypto" ? "USD balance (BTC-funded)" : "Guest or card (no processor)"}
</div>
</div>
</div>
</div>
<p className="text-sm text-foreground/50">
Your digital receipt is available in your vault. Thank you for choosing CyberLux.
</p>
</div>
)}
</div>
<div className="flex justify-between">
<button
type="button"
className="glass rounded-full px-8 py-3 font-medium disabled:opacity-30"
onClick={handlePrev}
disabled={step === 1 || (step === 4 && orderComplete)}
>
Previous
</button>
<div className="flex items-center gap-4">
{step < 4 ? (
<button
type="button"
disabled={step === 1 && lines.length === 0}
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-8 py-3 font-bold text-background disabled:opacity-40"
onClick={handleNext}
>
Continue
</button>
) : orderComplete ? (
<button
type="button"
className="rounded-full bg-gradient-to-r from-neon-green to-neon-cyan px-10 py-3 font-bold text-background"
>
ENTER THE VAULT
</button>
) : null}
</div>
</div>
</div>
);
};
export default CheckoutFlow;