DONE: add customer pages, implement auth on teller page
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
"use client";
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
import {
|
||||
Branch,
|
||||
Service,
|
||||
Tenant,
|
||||
Ticket,
|
||||
createTicket,
|
||||
getBranchServices,
|
||||
getBranches,
|
||||
getTenant,
|
||||
} from "@/lib/api";
|
||||
import { getDeviceToken } from "@/lib/device";
|
||||
|
||||
type Step = "tenant" | "branch" | "service" | "ticket";
|
||||
|
||||
export default function CustomerTicketFlow() {
|
||||
const [step, setStep] = useState<Step>("tenant");
|
||||
const [tenantCode, setTenantCode] = useState("DFLT");
|
||||
const [tenant, setTenant] = useState<Tenant | null>(null);
|
||||
const [branches, setBranches] = useState<Branch[]>([]);
|
||||
const [services, setServices] = useState<Service[]>([]);
|
||||
const [selectedBranch, setSelectedBranch] = useState<Branch | null>(null);
|
||||
const [ticket, setTicket] = useState<Ticket | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function handleTenantSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
const code = tenantCode.trim().toUpperCase();
|
||||
if (!code) {
|
||||
setError("Enter a company code.");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const [tenantData, branchData] = await Promise.all([
|
||||
getTenant(code),
|
||||
getBranches(code),
|
||||
]);
|
||||
setTenant(tenantData);
|
||||
setBranches(branchData);
|
||||
setTenantCode(code);
|
||||
setStep("branch");
|
||||
} catch {
|
||||
setError("Could not find that company. Check the code and try again.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSelectBranch(branch: Branch) {
|
||||
if (!tenant) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setSelectedBranch(branch);
|
||||
|
||||
try {
|
||||
const branchServices = await getBranchServices(tenant.code, branch.id);
|
||||
setServices(branchServices);
|
||||
setStep("service");
|
||||
} catch {
|
||||
setError("Could not load services for this branch.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSelectService(service: Service) {
|
||||
if (!selectedBranch) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await createTicket({
|
||||
branchId: selectedBranch.id,
|
||||
serviceId: service.id,
|
||||
deviceToken: getDeviceToken(),
|
||||
});
|
||||
setTicket(response.ticket);
|
||||
setStep("ticket");
|
||||
} catch {
|
||||
setError(
|
||||
"Could not get a ticket number. Make sure this branch has services assigned."
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function resetFlow() {
|
||||
setStep(tenant ? "branch" : "tenant");
|
||||
setSelectedBranch(null);
|
||||
setServices([]);
|
||||
setTicket(null);
|
||||
setError(null);
|
||||
}
|
||||
|
||||
function startOver() {
|
||||
setStep("tenant");
|
||||
setTenant(null);
|
||||
setBranches([]);
|
||||
setSelectedBranch(null);
|
||||
setServices([]);
|
||||
setTicket(null);
|
||||
setError(null);
|
||||
}
|
||||
|
||||
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">
|
||||
<p className="text-sm tracking-wide text-zinc-500 uppercase">
|
||||
Queue Management
|
||||
</p>
|
||||
<h1 className="text-3xl font-semibold tracking-tight text-zinc-900">
|
||||
{tenant?.name ?? "Get a ticket"}
|
||||
</h1>
|
||||
<p className="text-base text-zinc-600">
|
||||
{tenant?.welcomeMessage ??
|
||||
"Enter your company code, pick a branch and service, then take a number."}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{error ? (
|
||||
<p className="rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{step === "tenant" ? (
|
||||
<form onSubmit={handleTenantSubmit} className="space-y-4">
|
||||
<label className="block space-y-2">
|
||||
<span className="text-sm font-medium text-zinc-700">
|
||||
Company code
|
||||
</span>
|
||||
<input
|
||||
value={tenantCode}
|
||||
onChange={(event) => setTenantCode(event.target.value)}
|
||||
className="w-full rounded-md border border-zinc-300 bg-white px-3 py-2 text-zinc-900 outline-none focus:border-zinc-900"
|
||||
placeholder="e.g. DFLT"
|
||||
autoComplete="off"
|
||||
disabled={loading}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full rounded-md bg-zinc-900 px-4 py-3 text-sm font-medium text-white disabled:opacity-60"
|
||||
>
|
||||
{loading ? "Loading…" : "Continue"}
|
||||
</button>
|
||||
</form>
|
||||
) : null}
|
||||
|
||||
{step === "branch" ? (
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h2 className="text-lg font-medium text-zinc-900">Choose a branch</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={startOver}
|
||||
className="text-sm text-zinc-500 underline-offset-2 hover:underline"
|
||||
>
|
||||
Change company
|
||||
</button>
|
||||
</div>
|
||||
{branches.length === 0 ? (
|
||||
<p className="text-sm text-zinc-600">No branches available.</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{branches.map((branch) => (
|
||||
<li key={branch.id}>
|
||||
<button
|
||||
type="button"
|
||||
disabled={loading}
|
||||
onClick={() => handleSelectBranch(branch)}
|
||||
className="flex w-full items-center justify-between rounded-md border border-zinc-200 bg-white px-4 py-3 text-left text-zinc-900 transition hover:border-zinc-400 disabled:opacity-60"
|
||||
>
|
||||
<span>{branch.name}</span>
|
||||
<span className="text-zinc-400">→</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{step === "service" && selectedBranch ? (
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h2 className="text-lg font-medium text-zinc-900">
|
||||
Service at {selectedBranch.name}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setStep("branch");
|
||||
setSelectedBranch(null);
|
||||
setServices([]);
|
||||
setError(null);
|
||||
}}
|
||||
className="text-sm text-zinc-500 underline-offset-2 hover:underline"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
{services.length === 0 ? (
|
||||
<p className="text-sm text-zinc-600">
|
||||
No services are assigned to this branch yet. Link services via a
|
||||
branch group in the admin app, then try again.
|
||||
</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{services.map((service) => (
|
||||
<li key={service.id}>
|
||||
<button
|
||||
type="button"
|
||||
disabled={loading}
|
||||
onClick={() => handleSelectService(service)}
|
||||
className="flex w-full items-center justify-between rounded-md border border-zinc-200 bg-white px-4 py-3 text-left text-zinc-900 transition hover:border-zinc-400 disabled:opacity-60"
|
||||
>
|
||||
<span>{service.name}</span>
|
||||
<span className="text-sm text-zinc-500">
|
||||
{loading ? "…" : "Get number"}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{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">
|
||||
Your ticket number
|
||||
</p>
|
||||
<p className="text-6xl font-semibold tracking-tight text-zinc-900">
|
||||
{ticket.number}
|
||||
</p>
|
||||
<div className="space-y-1 text-sm text-zinc-600">
|
||||
<p>{ticket.service.name}</p>
|
||||
<p>{ticket.branch.name}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetFlow}
|
||||
className="w-full rounded-md bg-zinc-900 px-4 py-3 text-sm font-medium text-white"
|
||||
>
|
||||
Get another ticket
|
||||
</button>
|
||||
</section>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user