Add payment method selector to exchange form with threshold enforcement
This commit is contained in:
parent
cb173a7442
commit
4481c6b71a
1 changed files with 130 additions and 1 deletions
|
|
@ -19,9 +19,14 @@ type BookableSlot = components["schemas"]["BookableSlot"];
|
|||
type AvailableSlotsResponse = components["schemas"]["AvailableSlotsResponse"];
|
||||
|
||||
// Constants from shared config
|
||||
const { minAdvanceDays: MIN_ADVANCE_DAYS, maxAdvanceDays: MAX_ADVANCE_DAYS } = constants.exchange;
|
||||
const {
|
||||
minAdvanceDays: MIN_ADVANCE_DAYS,
|
||||
maxAdvanceDays: MAX_ADVANCE_DAYS,
|
||||
lightningMaxEur: LIGHTNING_MAX_EUR,
|
||||
} = constants.exchange;
|
||||
|
||||
type Direction = "buy" | "sell";
|
||||
type BitcoinTransferMethod = "onchain" | "lightning";
|
||||
type WizardStep = "details" | "booking";
|
||||
|
||||
/**
|
||||
|
|
@ -49,6 +54,8 @@ export default function ExchangePage() {
|
|||
|
||||
// Trade form state
|
||||
const [direction, setDirection] = useState<Direction>("buy");
|
||||
const [bitcoinTransferMethod, setBitcoinTransferMethod] =
|
||||
useState<BitcoinTransferMethod>("onchain");
|
||||
const [eurAmount, setEurAmount] = useState<number>(10000); // €100 in cents
|
||||
|
||||
// Date/slot selection state
|
||||
|
|
@ -95,6 +102,18 @@ export default function ExchangePage() {
|
|||
return Math.floor(btcAmount * 100_000_000);
|
||||
}, [eurAmount, agreedPrice]);
|
||||
|
||||
// Check if Lightning is disabled due to threshold
|
||||
const isLightningDisabled = useMemo(() => {
|
||||
return eurAmount > LIGHTNING_MAX_EUR * 100;
|
||||
}, [eurAmount]);
|
||||
|
||||
// Auto-switch to onchain if Lightning becomes disabled
|
||||
useEffect(() => {
|
||||
if (isLightningDisabled && bitcoinTransferMethod === "lightning") {
|
||||
setBitcoinTransferMethod("onchain");
|
||||
}
|
||||
}, [isLightningDisabled, bitcoinTransferMethod]);
|
||||
|
||||
// Fetch price data
|
||||
const fetchPrice = useCallback(async () => {
|
||||
setIsPriceLoading(true);
|
||||
|
|
@ -240,6 +259,7 @@ export default function ExchangePage() {
|
|||
await api.post<ExchangeResponse>("/api/exchange", {
|
||||
slot_start: selectedSlot.start_time,
|
||||
direction,
|
||||
bitcoin_transfer_method: bitcoinTransferMethod,
|
||||
eur_amount: eurAmount,
|
||||
});
|
||||
|
||||
|
|
@ -356,6 +376,43 @@ export default function ExchangePage() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{/* Payment Method Selector */}
|
||||
<div style={styles.paymentMethodSection}>
|
||||
<div style={styles.paymentMethodLabel}>
|
||||
Payment Method <span style={styles.required}>*</span>
|
||||
</div>
|
||||
<div style={styles.paymentMethodRow}>
|
||||
<button
|
||||
onClick={() => setBitcoinTransferMethod("onchain")}
|
||||
disabled={false}
|
||||
style={{
|
||||
...styles.paymentMethodBtn,
|
||||
...(bitcoinTransferMethod === "onchain" ? styles.paymentMethodBtnActive : {}),
|
||||
}}
|
||||
>
|
||||
<span style={styles.paymentMethodIcon}>🔗</span>
|
||||
<span>Onchain</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setBitcoinTransferMethod("lightning")}
|
||||
disabled={isLightningDisabled}
|
||||
style={{
|
||||
...styles.paymentMethodBtn,
|
||||
...(bitcoinTransferMethod === "lightning" ? styles.paymentMethodBtnActive : {}),
|
||||
...(isLightningDisabled ? styles.paymentMethodBtnDisabled : {}),
|
||||
}}
|
||||
>
|
||||
<span style={styles.paymentMethodIcon}>⚡</span>
|
||||
<span>Lightning</span>
|
||||
</button>
|
||||
</div>
|
||||
{isLightningDisabled && (
|
||||
<div style={styles.thresholdMessage}>
|
||||
Lightning payments are only available for amounts up to €{LIGHTNING_MAX_EUR}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Amount Section */}
|
||||
<div style={styles.amountSection}>
|
||||
<div style={styles.amountHeader}>
|
||||
|
|
@ -445,6 +502,11 @@ export default function ExchangePage() {
|
|||
<span style={styles.satsValue}>
|
||||
<SatsDisplay sats={satsAmount} />
|
||||
</span>
|
||||
<span style={styles.summaryDivider}>•</span>
|
||||
<span style={styles.summaryPaymentMethod}>
|
||||
{direction === "buy" ? "Receive via " : "Send via "}
|
||||
{bitcoinTransferMethod === "onchain" ? "Onchain" : "Lightning"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -559,6 +621,13 @@ export default function ExchangePage() {
|
|||
<span style={styles.confirmLabel}>Rate:</span>
|
||||
<span style={styles.confirmValue}>{formatPrice(agreedPrice)}/BTC</span>
|
||||
</div>
|
||||
<div style={styles.confirmRow}>
|
||||
<span style={styles.confirmLabel}>Payment:</span>
|
||||
<span style={styles.confirmValue}>
|
||||
{direction === "buy" ? "Receive via " : "Send via "}
|
||||
{bitcoinTransferMethod === "onchain" ? "Onchain" : "Lightning"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.buttonRow}>
|
||||
|
|
@ -869,6 +938,66 @@ const styles: Record<string, CSSProperties> = {
|
|||
summaryDivider: {
|
||||
color: "rgba(255, 255, 255, 0.3)",
|
||||
},
|
||||
summaryPaymentMethod: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
fontSize: "0.875rem",
|
||||
color: "rgba(255, 255, 255, 0.6)",
|
||||
},
|
||||
paymentMethodSection: {
|
||||
marginBottom: "1.5rem",
|
||||
},
|
||||
paymentMethodLabel: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
fontSize: "0.9rem",
|
||||
marginBottom: "0.75rem",
|
||||
},
|
||||
required: {
|
||||
color: "#f87171",
|
||||
},
|
||||
paymentMethodRow: {
|
||||
display: "flex",
|
||||
gap: "0.5rem",
|
||||
},
|
||||
paymentMethodBtn: {
|
||||
flex: 1,
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
fontSize: "0.95rem",
|
||||
fontWeight: 600,
|
||||
padding: "0.875rem",
|
||||
background: "rgba(255, 255, 255, 0.05)",
|
||||
border: "1px solid rgba(255, 255, 255, 0.1)",
|
||||
borderRadius: "8px",
|
||||
color: "rgba(255, 255, 255, 0.6)",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: "0.5rem",
|
||||
},
|
||||
paymentMethodBtnActive: {
|
||||
background: "rgba(167, 139, 250, 0.15)",
|
||||
border: "1px solid #a78bfa",
|
||||
color: "#a78bfa",
|
||||
},
|
||||
paymentMethodBtnDisabled: {
|
||||
opacity: 0.4,
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
paymentMethodIcon: {
|
||||
fontSize: "1.2rem",
|
||||
},
|
||||
thresholdMessage: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
fontSize: "0.75rem",
|
||||
color: "rgba(251, 146, 60, 0.9)",
|
||||
marginTop: "0.5rem",
|
||||
padding: "0.5rem",
|
||||
background: "rgba(251, 146, 60, 0.1)",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid rgba(251, 146, 60, 0.2)",
|
||||
},
|
||||
section: {
|
||||
marginBottom: "2rem",
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue