Harden onion boot flow and deepen site surfaces
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
This commit is contained in:
562
components/CheckoutFlow.tsx
Normal file
562
components/CheckoutFlow.tsx
Normal file
@@ -0,0 +1,562 @@
|
||||
"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;
|
||||
Reference in New Issue
Block a user