A check-in date field that works out the last moment for free cancellation and tells the guest whether it is still open, closing soon or already past. Use this when a booking is free to cancel until a cut-off before the stay and the guest should see that date as they choose dates.
registry/patterns/cancellation-deadline-note/cancellation-deadline-note.tsx
"use client"
import * as React from "react"
import { DateField } from "@/components/ui/date-field"
import {
addMinutesToDateTime,
diffMinutes,
formatDateTime,
nowLocal,
toDateTime,
} from "@/components/ui/date-time-core"
export interface CancellationDeadlineNoteProps {
value?: string | null
defaultValue?: string | null
onValueChange?: (checkIn: string | null) => void
hoursBefore?: number
checkInTime?: string
now?: string
locale?: string
}
export function cancellationDeadline(
checkIn: string,
time: string,
hoursBefore: number
) {
return addMinutesToDateTime(toDateTime(checkIn, time), -hoursBefore * 60)
}
export function CancellationDeadlineNote({
value,
defaultValue = null,
onValueChange,
hoursBefore = 48,
checkInTime = "15:00",
now: nowProp,
locale,
}: CancellationDeadlineNoteProps) {
const now = nowProp ?? nowLocal()
const [inner, setInner] = React.useState<string | null>(defaultValue)
const checkIn = value === undefined ? inner : value
let note = "Choose a check-in date to see the cancellation deadline."
let state: "open" | "soon" | "closed" | null = null
if (checkIn) {
const deadline = cancellationDeadline(checkIn, checkInTime, hoursBefore)
const left = diffMinutes(now, deadline)
if (left <= 0) {
state = "closed"
note = "Non-refundable: the free cancellation period has ended."
} else {
state = left < 24 * 60 ? "soon" : "open"
note = `Free cancellation until ${formatDateTime(deadline, locale)}${state === "soon" ? " (less than 24 hours left)" : ""}.`
}
}
return (
<div className="flex flex-col gap-2">
<DateField
label="Check-in date"
locale={locale}
value={checkIn}
onValueChange={(next) => {
if (value === undefined) setInner(next)
onValueChange?.(next)
}}
/>
<p
role="status"
data-state={state ?? undefined}
className="data-[state=closed]:text-destructive text-sm data-[state=soon]:font-medium"
>
{note}
</p>
</div>
)
}