import { expect, test, type Page, type Route } from "@playwright/test"; const PORTAL_TOKEN = "portal-e2e-token"; const futureDate = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString(); const pastDate = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(); interface DispatchDetail { id: number; dispatchNumber: string; workOrderTitle?: string; internalWONumber?: string; status: string; nteAmount?: number; scheduledDate?: string; poNumber?: string; dispatchedAt?: string; completedDate?: string; refusedAt?: string; statusUpdatedAt?: string; description?: string; workOrder?: Record; location?: Record; dispatcherContact?: { name?: string; email?: string; phone?: string }; checklist: never[]; comments: never[]; upliftRequests: never[]; signoffs: never[]; documents: never[]; } const sentDetail: DispatchDetail = { id: 101, dispatchNumber: "DSP-101", workOrderTitle: "Emergency boiler repair", internalWONumber: "WO-8842", status: "Sent", nteAmount: 1250, scheduledDate: futureDate, poNumber: "PO-555", statusUpdatedAt: "2026-08-09T16:30:00Z", description: "Boiler is leaking; vendor visit required.", workOrder: { workerOrderTitle: "Emergency boiler repair", internalWONumber: "WO-8842", trade: "Plumbing", subTrade: "Backflow Preventers", siteCode: "STL-01", service: "Boiler Repair", }, location: { name: "Central Distribution Center", address: "100 Industrial Pkwy", city: "St. Louis", state: "MO", zipCode: "63101", }, dispatcherContact: { name: "Dana Field", email: "dana@seahavenind.com", phone: "314-555-0142", }, checklist: [], comments: [], upliftRequests: [], signoffs: [], documents: [], }; const listDispatches = [ { id: 101, dispatchNumber: "DSP-101", workOrderTitle: "Emergency boiler repair", internalWONumber: "WO-8842", locationName: "Central Distribution Center", locationCity: "St. Louis", locationState: "MO", status: "Sent", nteAmount: 1250, scheduledDate: futureDate, }, { id: 102, dispatchNumber: "DSP-102", workOrderTitle: "HVAC quarterly service", status: "Refused", refusedAt: "2026-08-08T10:00:00Z", }, { id: 103, dispatchNumber: "DSP-103", workOrderTitle: "PastAcknowledged job", status: "Acknowledged", scheduledDate: pastDate, }, ]; async function fulfill(route: Route, body: unknown, status = 200) { await route.fulfill({ status, contentType: "application/json", body: JSON.stringify(body), }); } async function mockPortalApi(page: Page, options: { sessionError?: boolean } = {}) { const detailState: { current: DispatchDetail } = { current: { ...sentDetail } }; await page.route("**/api/vendor-portal/session", (route) => { if (options.sessionError) { fulfill(route, { message: "Invalid or expired token" }, 401); return; } fulfill(route, { data: { companyName: "Gateway Plumbing", vendorId: 101 } }); }); await page.route(/\/api\/vendor-portal\/dispatches\/\d+\/refuse$/, async (route) => { const body = route.request().postDataJSON() as { reason?: string }; detailState.current = { ...detailState.current, status: "Refused", refusedAt: new Date().toISOString(), }; fulfill(route, { data: { id: detailState.current.id, status: "Refused", refusedAt: detailState.current.refusedAt, reason: body?.reason, }, }); }); await page.route(/\/api\/vendor-portal\/dispatches\/\d+$/, (route) => fulfill(route, { data: detailState.current }), ); await page.route(/\/api\/vendor-portal\/dispatches(?:\?.*)?$/, (route) => fulfill(route, { data: listDispatches }), ); return detailState; } test.describe("Vendor portal lifecycle", () => { test("upcoming filter, detail snapshot, refuse dialog, and locked Refused state", async ({ page, }) => { await mockPortalApi(page); await page.goto(`/v/${PORTAL_TOKEN}/dashboard`); await expect(page.getByRole("heading", { name: "Your Work" })).toBeVisible(); await expect(page.getByRole("button", { name: /^Upcoming/ })).toBeVisible(); await expect(page.getByRole("button", { name: /^Refused/ })).toBeVisible(); await page.getByRole("button", { name: /^Upcoming/ }).click(); await expect(page.getByText("DSP-101 — Emergency boiler repair")).toBeVisible(); await expect(page.getByText("DSP-103")).toHaveCount(0); await page.getByText("DSP-101 — Emergency boiler repair").click(); await expect(page).toHaveURL(/\/dispatch\/101$/); await expect(page.getByRole("heading", { name: "DSP-101" })).toBeVisible(); await expect(page.getByText("Boiler Repair", { exact: true })).toBeVisible(); await expect(page.getByText("STL-01")).toBeVisible(); await expect(page.getByText("$1250.00").first()).toBeVisible(); await expect(page.getByRole("link", { name: "dana@seahavenind.com" })).toBeVisible(); await expect(page.getByText("Dana Field")).toBeVisible(); await expect(page.getByText("314-555-0142")).toBeVisible(); await expect(page.getByText(/Status updated/i)).toBeVisible(); await page.getByRole("button", { name: "Refuse Dispatch" }).click(); const dialog = page.getByRole("dialog", { name: "Refuse dispatch" }); await expect(dialog).toBeVisible(); await dialog.getByLabel(/Reason/i).fill("Equipment unavailable this week."); await expect(dialog.getByText(/\d+\/500/)).toBeVisible(); await dialog.getByRole("button", { name: "Refuse dispatch" }).click(); await expect(dialog).toHaveCount(0); await expect(page.getByText("Refused", { exact: true })).toBeVisible(); await expect(page.getByText(/refused and locked/i)).toBeVisible(); await expect(page.getByRole("button", { name: "Accept Dispatch" })).toHaveCount(0); await expect(page.getByRole("button", { name: "Refuse Dispatch" })).toHaveCount(0); await expect(page.getByRole("button", { name: "Submit Signoff" })).toHaveCount(0); }); test("covers the refuse dialog at a mobile viewport", async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); await mockPortalApi(page); await page.goto(`/v/${PORTAL_TOKEN}/dashboard`); await page.getByText("DSP-101 — Emergency boiler repair").click(); await expect(page).toHaveURL(/\/dispatch\/101$/); await page.getByRole("button", { name: "Refuse Dispatch" }).click(); const dialog = page.getByRole("dialog", { name: "Refuse dispatch" }); await expect(dialog).toBeVisible(); await page.keyboard.press("Escape"); await expect(dialog).toHaveCount(0); await page.getByRole("button", { name: "Refuse Dispatch" }).click(); await page .getByRole("dialog", { name: "Refuse dispatch" }) .getByRole("button", { name: "Refuse dispatch", }) .click(); await expect(page.getByText("Refused", { exact: true })).toBeVisible(); await expect(page.getByText(/refused and locked/i)).toBeVisible(); }); test("shows an expired-token state when the session is rejected", async ({ page }) => { await mockPortalApi(page, { sessionError: true }); await page.goto(`/v/${PORTAL_TOKEN}/dashboard`); await expect(page.getByRole("heading", { name: "Access Denied" })).toBeVisible(); await expect(page.getByRole("alert")).toContainText(/invalid or expired/i); }); });