265 lines
8.1 KiB
TypeScript
265 lines
8.1 KiB
TypeScript
"use client";
|
|
|
|
import { CSSProperties } from "react";
|
|
import { SatsDisplay } from "../../components/SatsDisplay";
|
|
import { components } from "../../generated/api";
|
|
import { formatTime } from "../../utils/date";
|
|
import { formatEur } from "../../utils/exchange";
|
|
import { buttonStyles } from "../../styles/shared";
|
|
import { useTranslation } from "../../hooks/useTranslation";
|
|
import { useLanguage } from "../../hooks/useLanguage";
|
|
|
|
type BookableSlot = components["schemas"]["BookableSlot"];
|
|
type Direction = "buy" | "sell";
|
|
type BitcoinTransferMethod = "onchain" | "lightning";
|
|
|
|
interface ConfirmationStepProps {
|
|
selectedSlot: BookableSlot;
|
|
selectedDate: Date | null;
|
|
direction: Direction;
|
|
bitcoinTransferMethod: BitcoinTransferMethod;
|
|
eurAmount: number;
|
|
satsAmount: number;
|
|
agreedPrice: number;
|
|
isBooking: boolean;
|
|
isPriceStale: boolean;
|
|
onConfirm: () => void;
|
|
onBack: () => void;
|
|
}
|
|
|
|
/**
|
|
* Format price for display
|
|
*/
|
|
function formatPrice(price: number): string {
|
|
return `€${price.toLocaleString("es-ES", { maximumFractionDigits: 0 })}`;
|
|
}
|
|
|
|
const styles: Record<string, CSSProperties> = {
|
|
confirmCard: {
|
|
background: "rgba(255, 255, 255, 0.03)",
|
|
border: "1px solid rgba(255, 255, 255, 0.08)",
|
|
borderRadius: "12px",
|
|
padding: "1.5rem",
|
|
maxWidth: "400px",
|
|
},
|
|
confirmTitle: {
|
|
fontFamily: "'DM Sans', system-ui, sans-serif",
|
|
fontSize: "1.1rem",
|
|
fontWeight: 500,
|
|
color: "#fff",
|
|
marginBottom: "1rem",
|
|
},
|
|
confirmDetails: {
|
|
marginBottom: "1.5rem",
|
|
},
|
|
confirmRow: {
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
padding: "0.5rem 0",
|
|
borderBottom: "1px solid rgba(255, 255, 255, 0.05)",
|
|
},
|
|
confirmLabel: {
|
|
fontFamily: "'DM Sans', system-ui, sans-serif",
|
|
color: "rgba(255, 255, 255, 0.5)",
|
|
fontSize: "0.875rem",
|
|
},
|
|
confirmValue: {
|
|
fontFamily: "'DM Sans', system-ui, sans-serif",
|
|
color: "#fff",
|
|
fontSize: "0.875rem",
|
|
fontWeight: 500,
|
|
},
|
|
satsValue: {
|
|
fontFamily: "'DM Mono', monospace",
|
|
color: "#f7931a", // Bitcoin orange
|
|
},
|
|
buttonRow: {
|
|
display: "flex",
|
|
gap: "0.75rem",
|
|
},
|
|
bookButton: {
|
|
fontFamily: "'DM Sans', system-ui, sans-serif",
|
|
flex: 1,
|
|
padding: "0.875rem",
|
|
border: "none",
|
|
borderRadius: "8px",
|
|
color: "#fff",
|
|
fontWeight: 600,
|
|
cursor: "pointer",
|
|
transition: "all 0.2s",
|
|
},
|
|
cancelButton: {
|
|
fontFamily: "'DM Sans', system-ui, sans-serif",
|
|
padding: "0.875rem 1.25rem",
|
|
background: "rgba(255, 255, 255, 0.05)",
|
|
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
borderRadius: "8px",
|
|
color: "rgba(255, 255, 255, 0.7)",
|
|
cursor: "pointer",
|
|
transition: "all 0.2s",
|
|
},
|
|
compressedBookingCard: {
|
|
background: "rgba(255, 255, 255, 0.03)",
|
|
border: "1px solid rgba(255, 255, 255, 0.08)",
|
|
borderRadius: "12px",
|
|
padding: "1rem 1.5rem",
|
|
marginBottom: "1.5rem",
|
|
},
|
|
compressedBookingHeader: {
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
marginBottom: "0.5rem",
|
|
},
|
|
compressedBookingTitle: {
|
|
fontFamily: "'DM Sans', system-ui, sans-serif",
|
|
fontSize: "0.875rem",
|
|
color: "rgba(255, 255, 255, 0.5)",
|
|
},
|
|
compressedBookingDetails: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "0.75rem",
|
|
flexWrap: "wrap",
|
|
fontFamily: "'DM Sans', system-ui, sans-serif",
|
|
fontSize: "1rem",
|
|
color: "#fff",
|
|
},
|
|
summaryDivider: {
|
|
color: "rgba(255, 255, 255, 0.3)",
|
|
},
|
|
editButton: {
|
|
fontFamily: "'DM Sans', system-ui, sans-serif",
|
|
fontSize: "0.75rem",
|
|
color: "#a78bfa",
|
|
background: "transparent",
|
|
border: "none",
|
|
cursor: "pointer",
|
|
padding: 0,
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Step 3 of the exchange wizard: Confirmation
|
|
* Shows compressed booking summary and final confirmation form.
|
|
*/
|
|
export function ConfirmationStep({
|
|
selectedSlot,
|
|
selectedDate,
|
|
direction,
|
|
bitcoinTransferMethod,
|
|
eurAmount,
|
|
satsAmount,
|
|
agreedPrice,
|
|
isBooking,
|
|
isPriceStale,
|
|
onConfirm,
|
|
onBack,
|
|
}: ConfirmationStepProps) {
|
|
const t = useTranslation("exchange");
|
|
const { locale } = useLanguage();
|
|
|
|
// Map locale codes to Intl locale strings
|
|
const intlLocale = locale === "es" ? "es-ES" : locale === "ca" ? "ca-ES" : "en-US";
|
|
return (
|
|
<>
|
|
{/* Compressed Booking Summary */}
|
|
<div style={styles.compressedBookingCard}>
|
|
<div style={styles.compressedBookingHeader}>
|
|
<span style={styles.compressedBookingTitle}>{t("confirmationStep.appointment")}</span>
|
|
<button onClick={onBack} style={styles.editButton}>
|
|
{t("confirmationStep.edit")}
|
|
</button>
|
|
</div>
|
|
<div style={styles.compressedBookingDetails}>
|
|
<span>
|
|
{selectedDate?.toLocaleDateString(intlLocale, {
|
|
weekday: "short",
|
|
month: "short",
|
|
day: "numeric",
|
|
})}
|
|
</span>
|
|
<span style={styles.summaryDivider}>•</span>
|
|
<span>
|
|
{formatTime(selectedSlot.start_time)} - {formatTime(selectedSlot.end_time)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Confirmation Card */}
|
|
<div style={styles.confirmCard}>
|
|
<h3 style={styles.confirmTitle}>{t("confirmationStep.confirmTrade")}</h3>
|
|
<div style={styles.confirmDetails}>
|
|
<div style={styles.confirmRow}>
|
|
<span style={styles.confirmLabel}>{t("confirmationStep.time")}</span>
|
|
<span style={styles.confirmValue}>
|
|
{formatTime(selectedSlot.start_time)} - {formatTime(selectedSlot.end_time)}
|
|
</span>
|
|
</div>
|
|
<div style={styles.confirmRow}>
|
|
<span style={styles.confirmLabel}>{t("confirmationStep.direction")}</span>
|
|
<span
|
|
style={{
|
|
...styles.confirmValue,
|
|
color: direction === "buy" ? "#4ade80" : "#f87171",
|
|
}}
|
|
>
|
|
{direction === "buy" ? t("direction.buyShort") : t("direction.sellShort")}
|
|
</span>
|
|
</div>
|
|
<div style={styles.confirmRow}>
|
|
<span style={styles.confirmLabel}>{t("confirmationStep.eur")}</span>
|
|
<span style={styles.confirmValue}>{formatEur(eurAmount)}</span>
|
|
</div>
|
|
<div style={styles.confirmRow}>
|
|
<span style={styles.confirmLabel}>{t("confirmationStep.btc")}</span>
|
|
<span style={{ ...styles.confirmValue, ...styles.satsValue }}>
|
|
<SatsDisplay sats={satsAmount} />
|
|
</span>
|
|
</div>
|
|
<div style={styles.confirmRow}>
|
|
<span style={styles.confirmLabel}>{t("confirmationStep.rate")}</span>
|
|
<span style={styles.confirmValue}>{formatPrice(agreedPrice)}/BTC</span>
|
|
</div>
|
|
<div style={styles.confirmRow}>
|
|
<span style={styles.confirmLabel}>{t("confirmationStep.payment")}</span>
|
|
<span style={styles.confirmValue}>
|
|
{direction === "buy"
|
|
? t("confirmationStep.receiveVia")
|
|
: t("confirmationStep.sendVia")}{" "}
|
|
{bitcoinTransferMethod === "onchain"
|
|
? t("transferMethod.onchain")
|
|
: t("transferMethod.lightning")}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div style={styles.buttonRow}>
|
|
<button
|
|
onClick={onConfirm}
|
|
disabled={isBooking || isPriceStale}
|
|
style={{
|
|
...styles.bookButton,
|
|
background:
|
|
direction === "buy"
|
|
? "linear-gradient(135deg, #4ade80 0%, #22c55e 100%)"
|
|
: "linear-gradient(135deg, #f87171 0%, #ef4444 100%)",
|
|
...(isBooking || isPriceStale ? buttonStyles.buttonDisabled : {}),
|
|
}}
|
|
>
|
|
{isBooking
|
|
? t("confirmationStep.booking")
|
|
: isPriceStale
|
|
? t("confirmationStep.priceStale")
|
|
: direction === "buy"
|
|
? t("confirmationStep.confirmBuy")
|
|
: t("confirmationStep.confirmSell")}
|
|
</button>
|
|
<button onClick={onBack} disabled={isBooking} style={styles.cancelButton}>
|
|
{t("confirmationStep.back")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|