Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation. Made-with: Cursor
563 lines
25 KiB
TypeScript
563 lines
25 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||
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 [cryptoType, setCryptoType] = useState<"BTC" | "ETH" | "XMR">("BTC");
|
||
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 { usdStoreCredit, spendUsdStoreCredit, verifyBtcDeposit } = 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 (!spendUsdStoreCredit(orderTotalUsd)) {
|
||
setPayError(
|
||
`Insufficient USD balance. You need $${orderTotalUsd.toFixed(2)}. Send Bitcoin to the address below, wait for at least one confirmation, then paste the transaction ID and click Verify.`,
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
setOrderComplete(true);
|
||
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">
|
||
{" "}
|
||
— use after Bitcoin deposit is verified (on-chain).
|
||
</span>
|
||
</div>
|
||
<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">Demo — no USD charge</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 (Secure)</div>
|
||
<div className="mt-2 text-sm text-foreground/60">Demo — no USD charge</div>
|
||
</button>
|
||
</div>
|
||
|
||
{paymentMethod === "crypto" && (
|
||
<div className="mt-8">
|
||
<label className="mb-2 block font-medium">Select Cryptocurrency</label>
|
||
<div className="flex gap-4">
|
||
{(["BTC", "ETH", "XMR"] as const).map((coin) => (
|
||
<button
|
||
key={coin}
|
||
type="button"
|
||
className={`glass flex-1 rounded-xl border py-4 ${cryptoType === coin ? "border-neon-cyan bg-neon-cyan/10" : "border-white/10"
|
||
}`}
|
||
onClick={() => setCryptoType(coin)}
|
||
>
|
||
<div className="text-2xl">{coin === "BTC" ? "₿" : coin === "ETH" ? "Ξ" : "⏣"}</div>
|
||
<div className="font-bold">{coin}</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
{cryptoType !== "BTC" && (
|
||
<p className="mt-4 rounded-lg bg-white/5 p-4 text-sm text-foreground/70">
|
||
Deposits that add USD balance use <strong>Bitcoin</strong> only. Switch to BTC to see the
|
||
deposit address. (ETH/XMR shown for display.)
|
||
</p>
|
||
)}
|
||
{cryptoType === "BTC" && (
|
||
<div className="mt-6 space-y-4 rounded-xl bg-white/5 p-4">
|
||
<div className="text-sm text-foreground/60">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. Fund your balance
|
||
with <strong>any amount</strong> of BTC; verified value is credited in USD at spot rate.
|
||
You can pay this order once your balance covers the total.
|
||
</p>
|
||
</div>
|
||
)}
|
||
</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 is a demo and does not charge your USD balance.
|
||
</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 (all checkouts)</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 the network confirms your payment (at least one
|
||
block), paste the transaction ID to add the equivalent USD to your balance.
|
||
</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" ? "Crypto (USD balance)" : "Demo (guest/card)"}
|
||
</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 demo order"}
|
||
</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 #CYBR‑9A2F‑B8E1</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)" : "Demo guest/card"}
|
||
</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;
|