diff --git a/src/__tests__/BookingCancelled.test.tsx b/src/__tests__/BookingCancelled.test.tsx
new file mode 100644
index 0000000..2d6ada1
--- /dev/null
+++ b/src/__tests__/BookingCancelled.test.tsx
@@ -0,0 +1,27 @@
+import { describe, it, expect } from "vitest";
+import { render, screen } from "@testing-library/react";
+import { BookingCancelledPage } from "../pages/BookingCancelled.tsx";
+
+describe("BookingCancelledPage", () => {
+ it("renders the cancelled heading", () => {
+ render();
+ expect(screen.getByRole("heading", { name: /Appointment Cancelled/i })).toBeInTheDocument();
+ });
+
+ it("renders the cancelled body text", () => {
+ render();
+ expect(screen.getByText(/Your appointment has been cancelled/i)).toBeInTheDocument();
+ });
+
+ it("has a Book again link pointing to /admin/book", () => {
+ render();
+ const link = screen.getByRole("link", { name: /Book again/i });
+ expect(link).toHaveAttribute("href", "/admin/book");
+ });
+
+ it("has a Back to Portal link pointing to /", () => {
+ render();
+ const link = screen.getByRole("link", { name: /Back to Portal/i });
+ expect(link).toHaveAttribute("href", "/");
+ });
+});
diff --git a/src/__tests__/BookingError.test.tsx b/src/__tests__/BookingError.test.tsx
new file mode 100644
index 0000000..0f30f6e
--- /dev/null
+++ b/src/__tests__/BookingError.test.tsx
@@ -0,0 +1,38 @@
+import { describe, it, expect } from "vitest";
+import { render, screen } from "@testing-library/react";
+import { BookingErrorPage } from "../pages/BookingError.tsx";
+import { BUSINESS_CONTACT_INFO } from "../lib/contact.ts";
+
+describe("BookingErrorPage", () => {
+ it("renders the error heading", () => {
+ render();
+ expect(screen.getByRole("heading", { name: /Link Invalid or Expired/i })).toBeInTheDocument();
+ });
+
+ it("renders the error body text", () => {
+ render();
+ expect(screen.getByText(/This confirmation link is invalid/i)).toBeInTheDocument();
+ });
+
+ it("has a Start a new booking link pointing to /admin/book", () => {
+ render();
+ const link = screen.getByRole("link", { name: /Start a new booking/i });
+ expect(link).toHaveAttribute("href", "/admin/book");
+ });
+
+ it("has a Back to Portal link pointing to /", () => {
+ render();
+ const link = screen.getByRole("link", { name: /Back to Portal/i });
+ expect(link).toHaveAttribute("href", "/");
+ });
+
+ it("displays business contact phone", () => {
+ render();
+ expect(screen.getByText(BUSINESS_CONTACT_INFO.phone)).toBeInTheDocument();
+ });
+
+ it("displays business contact email", () => {
+ render();
+ expect(screen.getByText(BUSINESS_CONTACT_INFO.email)).toBeInTheDocument();
+ });
+});
diff --git a/src/index.css b/src/index.css
index 61c98ed..32b3b5e 100644
--- a/src/index.css
+++ b/src/index.css
@@ -8,6 +8,19 @@
--color-accent-dark: color-mix(in srgb, var(--color-accent) 78%, #000);
--color-accent-light: color-mix(in srgb, var(--color-accent) 18%, #fff);
--color-accent-lighter: color-mix(in srgb, var(--color-accent) 9%, #fff);
+
+ /* Semantic / booking page tokens */
+ --color-error: #dc2626;
+ --color-error-dark: #b91c1c;
+ --color-error-bg: #fef2f2;
+ --color-cancelled: #ea580c;
+ --color-cancelled-dark: #c2410c;
+ --color-cancelled-bg: #fff7ed;
+ --color-success: #16a34a;
+ --color-success-dark: #15803d;
+ --color-success-bg: #f0fdf4;
+ --color-text-secondary: #4b5563;
+ --color-surface: #fff;
}
*, *::before, *::after {
diff --git a/src/lib/contact.ts b/src/lib/contact.ts
new file mode 100644
index 0000000..d2908cf
--- /dev/null
+++ b/src/lib/contact.ts
@@ -0,0 +1,7 @@
+// Business contact information — update values to reflect actual business details.
+// Used on error/cancellation pages to help customers reach the business.
+export const BUSINESS_CONTACT_INFO = {
+ phone: "(555) 000-1234",
+ email: "hello@groombook.example.com",
+ address: "123 Main St, Anytown, USA",
+} as const;
diff --git a/src/pages/BookingCancelled.tsx b/src/pages/BookingCancelled.tsx
index 9b2ab4a..6ded7af 100644
--- a/src/pages/BookingCancelled.tsx
+++ b/src/pages/BookingCancelled.tsx
@@ -1,3 +1,10 @@
+const STRINGS = {
+ heading: "Appointment Cancelled",
+ body: "Your appointment has been cancelled. If this was a mistake or you'd like to rebook, please contact us.",
+ bookAgain: "Book again",
+ backToPortal: "Back to Portal",
+} as const;
+
export function BookingCancelledPage() {
return (
✗
-
- Appointment Cancelled
+
+ {STRINGS.heading}
-
- Your appointment has been cancelled. If this was a mistake or you'd
- like to rebook, please contact us.
+
+ {STRINGS.body}
-
- Back to Portal
-
+
+
);
diff --git a/src/pages/BookingError.tsx b/src/pages/BookingError.tsx
index 62639d9..ba5d43d 100644
--- a/src/pages/BookingError.tsx
+++ b/src/pages/BookingError.tsx
@@ -1,3 +1,13 @@
+import { BUSINESS_CONTACT_INFO } from "../lib/contact";
+
+const STRINGS = {
+ heading: "Link Invalid or Expired",
+ body: "This confirmation link is invalid, has already been used, or your appointment has already passed. Please contact us if you need help.",
+ newBooking: "Start a new booking",
+ backToPortal: "Back to Portal",
+ contactLabel: "Need help?",
+} as const;
+
export function BookingErrorPage() {
return (
⚠️
-
- Link Invalid or Expired
+
+ {STRINGS.heading}
-
- This confirmation link is invalid, has already been used, or your
- appointment has already passed. Please contact us if you need help.
+
+ {STRINGS.body}
-
- Back to Portal
-
+
+
+
+
+
{STRINGS.contactLabel}
+
+ {BUSINESS_CONTACT_INFO.phone} · {BUSINESS_CONTACT_INFO.email}
+
+
);