Files
dark-lord/components/PayWithUsdBalanceButton.tsx
drjones 04d64fb993 Update market, account, and onion operations
Capture the current CyberLux UI, commerce, messaging, and Tor ops updates so local main can be pushed to the remote.

Made-with: Cursor
2026-04-24 23:23:48 -07:00

112 lines
3.4 KiB
TypeScript

"use client";
import { useState } from "react";
import Link from "next/link";
import { useAccount } from "@/contexts/AccountContext";
import { useWallet } from "@/contexts/WalletContext";
type Props = {
/** USD amount to charge (2 decimals) */
amountUsd: number;
/** Receipt / vault title */
title: string;
description?: string;
className?: string;
size?: "sm" | "md";
/** Override LUX granted (default: based on USD amount) */
luxBonus?: number;
/** Called after a successful charge */
onSuccess?: () => void;
};
/**
* Spend verified Bitcoin-funded USD balance without going through full checkout cart.
*/
export default function PayWithUsdBalanceButton({
amountUsd,
title,
description,
className = "",
size = "md",
luxBonus,
onSuccess,
}: Props) {
const { user } = useAccount();
const { usdStoreCredit, spendUsdStoreCredit, earnLuxCredits, addVaultReceipt } = useWallet();
const [msg, setMsg] = useState<{ kind: "ok" | "err"; text: string } | null>(null);
const [busy, setBusy] = useState(false);
const safe = Math.round(amountUsd * 100) / 100;
const pad = size === "sm" ? "px-3 py-1.5 text-xs" : "px-5 py-2.5 text-sm";
const pay = () => {
setMsg(null);
if (!user) {
setMsg({ kind: "err", text: "Sign in to spend your USD balance." });
return;
}
if (!(safe > 0)) {
setMsg({ kind: "err", text: "Invalid amount." });
return;
}
setBusy(true);
try {
if (!spendUsdStoreCredit(safe)) {
setMsg({
kind: "err",
text: `Need $${safe.toFixed(2)} — you have $${usdStoreCredit.toFixed(2)}. Add funds first.`,
});
return;
}
const lux =
luxBonus != null && Number.isFinite(luxBonus)
? Math.max(0, Math.floor(luxBonus))
: Math.min(500, Math.max(10, Math.floor(safe)));
earnLuxCredits(lux);
const oid =
typeof crypto !== "undefined" && "randomUUID" in crypto
? `PAY-${crypto.randomUUID().slice(0, 8).toUpperCase()}`
: `PAY-${Date.now().toString(36).toUpperCase()}`;
addVaultReceipt({
id: oid,
title,
desc: description ?? `${title} · $${safe.toFixed(2)} USD`,
date: new Date().toISOString().slice(0, 10),
severity: "normal",
});
setMsg({ kind: "ok", text: `Paid $${safe.toFixed(2)}. +${lux} LUX · receipt ${oid}` });
onSuccess?.();
} finally {
setBusy(false);
}
};
return (
<div className="space-y-2">
<button
type="button"
disabled={busy || safe <= 0}
onClick={pay}
className={`rounded-full border border-neon-cyan/40 bg-neon-cyan/15 font-bold text-neon-cyan transition hover:bg-neon-cyan/25 disabled:opacity-40 ${pad} ${className}`}
>
{busy ? "Processing…" : `Pay $${safe.toFixed(2)} with balance`}
</button>
{!user ? (
<p className="text-xs text-foreground/55">
<Link href="/sign-in" className="text-neon-cyan underline">
Sign in
</Link>{" "}
to use BTC-funded USD credit.
</p>
) : (
<p className="text-xs text-foreground/45">
Balance: <span className="font-orbitron text-neon-green">${usdStoreCredit.toFixed(2)}</span>
</p>
)}
{msg ? (
<p className={`text-xs ${msg.kind === "ok" ? "text-neon-green" : "text-red-400"}`}>{msg.text}</p>
) : null}
</div>
);
}