mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 12:08:15 +00:00
93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Group, Stack, Text } from "@mantine/core";
|
|
import { Timer } from "lucide-react";
|
|
|
|
import { SectionCard } from "./SectionCard";
|
|
|
|
export interface BookingPaymentCountdownCardProps {
|
|
/** ISO timestamp marking the end of the pay window. */
|
|
paymentDeadline: string;
|
|
}
|
|
|
|
interface Remaining {
|
|
days: number;
|
|
hours: number;
|
|
minutes: number;
|
|
seconds: number;
|
|
expired: boolean;
|
|
}
|
|
|
|
function getRemaining(deadlineMs: number): Remaining {
|
|
const diff = deadlineMs - Date.now();
|
|
if (diff <= 0) {
|
|
return { days: 0, hours: 0, minutes: 0, seconds: 0, expired: true };
|
|
}
|
|
const totalSeconds = Math.floor(diff / 1000);
|
|
return {
|
|
days: Math.floor(totalSeconds / 86400),
|
|
hours: Math.floor((totalSeconds % 86400) / 3600),
|
|
minutes: Math.floor((totalSeconds % 3600) / 60),
|
|
seconds: totalSeconds % 60,
|
|
expired: false,
|
|
};
|
|
}
|
|
|
|
function Segment({ value, label }: { value: number; label: string }) {
|
|
return (
|
|
<Stack gap={2} align="center" style={{ minWidth: 56 }}>
|
|
<Text fw={700} size="2rem" style={{ lineHeight: 1, fontVariantNumeric: "tabular-nums" }}>
|
|
{String(value).padStart(2, "0")}
|
|
</Text>
|
|
<Text size="xs" c="dimmed" tt="uppercase" lts="0.06em">
|
|
{label}
|
|
</Text>
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
/** Live countdown to the payment deadline. Ticks every second; shows an expired state past the deadline. */
|
|
export function BookingPaymentCountdownCard({ paymentDeadline }: BookingPaymentCountdownCardProps) {
|
|
const deadlineMs = new Date(paymentDeadline).getTime();
|
|
const [remaining, setRemaining] = useState<Remaining>(() => getRemaining(deadlineMs));
|
|
|
|
useEffect(() => {
|
|
setRemaining(getRemaining(deadlineMs));
|
|
const interval = setInterval(() => {
|
|
const next = getRemaining(deadlineMs);
|
|
setRemaining(next);
|
|
if (next.expired) {
|
|
clearInterval(interval);
|
|
}
|
|
}, 1000);
|
|
return () => clearInterval(interval);
|
|
}, [deadlineMs]);
|
|
|
|
const accent = remaining.expired ? "red" : "orange";
|
|
|
|
return (
|
|
<SectionCard
|
|
icon={Timer}
|
|
title="Payment Deadline"
|
|
subtitle={
|
|
remaining.expired
|
|
? "The pay window has closed"
|
|
: "Time remaining to complete payment"
|
|
}
|
|
accent={accent}
|
|
>
|
|
{remaining.expired ? (
|
|
<Text fw={600} c="red.7">
|
|
Expired
|
|
</Text>
|
|
) : (
|
|
<Group justify="center" gap="lg" wrap="nowrap">
|
|
<Segment value={remaining.days} label="Days" />
|
|
<Segment value={remaining.hours} label="Hours" />
|
|
<Segment value={remaining.minutes} label="Mins" />
|
|
<Segment value={remaining.seconds} label="Secs" />
|
|
</Group>
|
|
)}
|
|
</SectionCard>
|
|
);
|
|
}
|