Bugfix/testing (#4)
Build Docker Image / build-backend (push) Successful in 58s
Build Docker Image / build-admin (push) Successful in 6s
Build Docker Image / build-teller (push) Successful in 8s
Build Docker Image / build-customer (push) Successful in 2m6s

Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
2026-07-30 16:22:51 +08:00
parent 318e0c3eb4
commit 0420194541
9 changed files with 179 additions and 117 deletions
@@ -22,6 +22,9 @@ type Props = {
initialBranchId?: number;
};
/** Prevents spam of "Dapatkan tiket lain" right after getting a number. */
const ANOTHER_TICKET_COOLDOWN_MS = 10_000;
function pickLatestTicket(
tickets: Ticket[],
branchId?: number
@@ -61,8 +64,27 @@ export default function CustomerTicketFlow({
const [ticket, setTicket] = useState<Ticket | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [cooldownLeftMs, setCooldownLeftMs] = useState(0);
const bootstrapped = useRef(false);
useEffect(() => {
if (step !== "ticket" || !ticket) {
setCooldownLeftMs(0);
return;
}
const tick = () => {
const elapsed = Date.now() - new Date(ticket.createdAt).getTime();
setCooldownLeftMs(
Math.max(0, ANOTHER_TICKET_COOLDOWN_MS - elapsed)
);
};
tick();
const id = window.setInterval(tick, 250);
return () => window.clearInterval(id);
}, [step, ticket]);
useEffect(() => {
if (bootstrapped.current) return;
bootstrapped.current = true;
@@ -281,6 +303,9 @@ export default function CustomerTicketFlow({
? "Pilih perkhidmatan untuk mendapatkan nombor."
: "Imbas QR code di branch anda, atau masukkan kod syarikat di bawah.";
const canGetAnotherTicket = cooldownLeftMs <= 0;
const cooldownSeconds = Math.ceil(cooldownLeftMs / 1000);
return (
<main className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-8 px-6 py-12">
<header className="space-y-2">
@@ -423,7 +448,7 @@ export default function CustomerTicketFlow({
{step === "ticket" && ticket ? (
<section className="space-y-6 rounded-md border border-zinc-200 bg-white px-6 py-8 text-center">
<p className="text-sm tracking-wide text-zinc-500 uppercase">
Nombor tiket anda
Nombor giliran anda
</p>
<p className="text-6xl font-semibold tracking-tight text-zinc-900">
{ticket.number}
@@ -432,12 +457,36 @@ export default function CustomerTicketFlow({
<p>{ticket.service.name}</p>
<p>{ticket.branch.name}</p>
</div>
<div
className="rounded-md border border-amber-300 bg-amber-50 px-4 py-3 text-left text-sm text-amber-950"
role="status"
>
<p className="font-semibold">Anda sudah mempunyai nombor giliran.</p>
<p className="mt-1 text-amber-900">
Sila tunggu sehingga nombor anda dipanggil.
</p>
{!canGetAnotherTicket ? (
<p className="mt-2 font-medium tabular-nums text-amber-950">
Tiket lain boleh diambil dalam {cooldownSeconds}s
</p>
) : null}
</div>
<button
type="button"
onClick={resetFlow}
className="w-full rounded-md bg-zinc-900 px-4 py-3 text-sm font-medium text-white"
disabled={!canGetAnotherTicket || loading}
aria-disabled={!canGetAnotherTicket || loading}
className={
canGetAnotherTicket
? "w-full rounded-md border border-zinc-300 bg-white px-4 py-3 text-sm font-medium text-zinc-700 transition hover:border-zinc-500 hover:bg-zinc-50 disabled:opacity-60"
: "w-full cursor-not-allowed rounded-md bg-zinc-200 px-4 py-3 text-sm font-medium text-zinc-500"
}
>
Dapatkan tiket lain
{canGetAnotherTicket
? "Dapatkan nombor giliran lain"
: `Tunggu ${cooldownSeconds}s…`}
</button>
</section>
) : null}