- 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
576 lines
26 KiB
TypeScript
576 lines
26 KiB
TypeScript
"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: "Military‑grade encryption, basic anonymity", icon: "🕵️" },
|
||
{ level: "enhanced" as const, name: "Ghost Protocol", desc: "Post‑quantum cryptography, zero‑trace", icon: "👻" },
|
||
{ level: "quantum" as const, name: "Zero‑Trace", desc: "Experimental, quantum‑entangled 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;
|