"use client"; import { CSSProperties } from "react"; import { SatsDisplay } from "../../components/SatsDisplay"; import { components } from "../../generated/api"; import { formatDate, formatTime } from "../../utils/date"; import { formatEur } from "../../utils/exchange"; import { bannerStyles } from "../../styles/shared"; import { useTranslation } from "../../hooks/useTranslation"; type BookableSlot = components["schemas"]["BookableSlot"]; type ExchangeResponse = components["schemas"]["ExchangeResponse"]; type Direction = "buy" | "sell"; type BitcoinTransferMethod = "onchain" | "lightning"; interface BookingStepProps { direction: Direction; bitcoinTransferMethod: BitcoinTransferMethod; eurAmount: number; satsAmount: number; dates: Date[]; selectedDate: Date | null; availableSlots: BookableSlot[]; selectedSlot: BookableSlot | null; datesWithAvailability: Set; isLoadingSlots: boolean; isLoadingAvailability: boolean; existingTradeOnSelectedDate: ExchangeResponse | null; userTrades: ExchangeResponse[]; onDateSelect: (date: Date) => void; onSlotSelect: (slot: BookableSlot) => void; onBackToDetails: () => void; } const styles: Record = { summaryCard: { 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", }, summaryHeader: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "0.5rem", }, summaryTitle: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", color: "rgba(255, 255, 255, 0.5)", }, editButton: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.75rem", color: "#a78bfa", background: "transparent", border: "none", cursor: "pointer", padding: 0, }, summaryDetails: { display: "flex", alignItems: "center", gap: "0.75rem", flexWrap: "wrap", fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "1rem", color: "#fff", }, summaryDirection: { fontWeight: 600, }, 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)", }, satsValue: { fontFamily: "'DM Mono', monospace", color: "#f7931a", // Bitcoin orange }, section: { marginBottom: "2rem", }, sectionTitle: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "1.1rem", fontWeight: 500, color: "#fff", marginBottom: "1rem", }, dateGrid: { display: "flex", flexWrap: "wrap", gap: "0.5rem", }, dateButton: { fontFamily: "'DM Sans', system-ui, sans-serif", padding: "0.75rem 1rem", background: "rgba(255, 255, 255, 0.03)", border: "1px solid rgba(255, 255, 255, 0.08)", borderRadius: "10px", cursor: "pointer", minWidth: "90px", textAlign: "center" as const, transition: "all 0.2s", }, dateButtonSelected: { background: "rgba(167, 139, 250, 0.15)", border: "1px solid #a78bfa", }, dateButtonDisabled: { opacity: 0.4, cursor: "not-allowed", background: "rgba(255, 255, 255, 0.01)", border: "1px solid rgba(255, 255, 255, 0.04)", }, dateButtonHasTrade: { border: "1px solid rgba(251, 146, 60, 0.5)", background: "rgba(251, 146, 60, 0.1)", }, dateWeekday: { color: "#fff", fontWeight: 500, fontSize: "0.875rem", marginBottom: "0.25rem", }, dateDay: { color: "rgba(255, 255, 255, 0.5)", fontSize: "0.8rem", }, dateWarning: { fontSize: "0.7rem", marginTop: "0.25rem", opacity: 0.8, }, errorLink: { marginTop: "0.75rem", paddingTop: "0.75rem", borderTop: "1px solid rgba(255, 255, 255, 0.1)", }, errorLinkAnchor: { fontFamily: "'DM Sans', system-ui, sans-serif", color: "#a78bfa", textDecoration: "none", fontWeight: 500, fontSize: "0.9rem", }, slotGrid: { display: "flex", flexWrap: "wrap", gap: "0.5rem", }, slotButton: { fontFamily: "'DM Sans', system-ui, sans-serif", padding: "0.6rem 1.25rem", background: "rgba(255, 255, 255, 0.03)", border: "1px solid rgba(255, 255, 255, 0.08)", borderRadius: "8px", color: "#fff", cursor: "pointer", fontSize: "0.9rem", transition: "all 0.2s", }, slotButtonSelected: { background: "rgba(167, 139, 250, 0.15)", border: "1px solid #a78bfa", }, emptyState: { fontFamily: "'DM Sans', system-ui, sans-serif", color: "rgba(255, 255, 255, 0.4)", padding: "1rem 0", }, }; /** * Check if a date has an existing trade (only consider booked trades, not cancelled ones) */ function getExistingTradeOnDate( date: Date, userTrades: ExchangeResponse[] ): ExchangeResponse | null { const dateStr = formatDate(date); return ( userTrades.find((trade) => { const tradeDate = formatDate(new Date(trade.slot_start)); return tradeDate === dateStr && trade.status === "booked"; }) || null ); } /** * Step 2 of the exchange wizard: Booking * Allows user to select a date and time slot for the exchange. */ export function BookingStep({ direction, bitcoinTransferMethod, eurAmount, satsAmount, dates, selectedDate, availableSlots, selectedSlot, datesWithAvailability, isLoadingSlots, isLoadingAvailability, existingTradeOnSelectedDate, userTrades, onDateSelect, onSlotSelect, onBackToDetails, }: BookingStepProps) { const t = useTranslation("exchange"); return ( <> {/* Trade Summary Card */}
{t("bookingStep.yourExchange")}
{direction === "buy" ? t("bookingStep.buy") : t("bookingStep.sell")} BTC {formatEur(eurAmount)} {direction === "buy" ? t("bookingStep.receiveVia") : t("bookingStep.sendVia")}{" "} {bitcoinTransferMethod === "onchain" ? t("transferMethod.onchain") : t("transferMethod.lightning")}
{/* Date Selection */}

{t("bookingStep.selectDate")}

{dates.map((date) => { const dateStr = formatDate(date); const isSelected = selectedDate && formatDate(selectedDate) === dateStr; const hasAvailability = datesWithAvailability.has(dateStr); const isDisabled = !hasAvailability || isLoadingAvailability; const hasExistingTrade = getExistingTradeOnDate(date, userTrades) !== null; return ( ); })}
{/* Warning for existing trade on selected date */} {existingTradeOnSelectedDate && (
{t("bookingStep.existingTradeWarning")}
{t("bookingStep.viewExistingTrade")}
)} {/* Available Slots */} {selectedDate && !existingTradeOnSelectedDate && (

{t("bookingStep.availableSlots")}{" "} {selectedDate.toLocaleDateString("es-ES", { weekday: "long", month: "long", day: "numeric", })}

{isLoadingSlots ? (
{t("bookingStep.loadingSlots")}
) : availableSlots.length === 0 ? (
{t("bookingStep.noSlots")}
) : (
{availableSlots.map((slot) => { const isSelected = selectedSlot?.start_time === slot.start_time; return ( ); })}
)}
)} ); }