- Rebrand all user-facing text to AgendaMax - Fix sidebar scrolling: card container no longer overflows html (flex h-full min-h-0) - Fix calendar toolbar hover: active buttons keep readable contrast - Sticky calendar toolbar (month/week/day always visible on scroll) - Seed guarantees 2-4 appointments per day for current week (Mon-Sat)
112 lines
2.5 KiB
TypeScript
112 lines
2.5 KiB
TypeScript
import type { BookResponse } from "../../shared/types";
|
|
export type { BookResponse };
|
|
|
|
const BASE = "/api/public";
|
|
|
|
export interface PublicBusiness {
|
|
id: number;
|
|
name: string;
|
|
industry: string;
|
|
currency_symbol: string;
|
|
phone: string | null;
|
|
address: string | null;
|
|
booking_enabled: boolean;
|
|
cancel_window_hours: number;
|
|
cancel_penalty_pct: number;
|
|
require_deposit: boolean;
|
|
deposit_pct: number;
|
|
auto_assign_specialist: number | boolean;
|
|
}
|
|
|
|
export interface PublicService {
|
|
id: number;
|
|
name: string;
|
|
description: string | null;
|
|
category: string;
|
|
duration_min: number;
|
|
price: number;
|
|
color: string;
|
|
}
|
|
|
|
export interface PublicEmployee {
|
|
id: number;
|
|
name: string;
|
|
role: string;
|
|
color: string;
|
|
}
|
|
|
|
export interface PublicBusinessResponse {
|
|
business: PublicBusiness;
|
|
services: PublicService[];
|
|
employees: PublicEmployee[];
|
|
}
|
|
|
|
export interface PublicSlot {
|
|
time: string;
|
|
iso: string;
|
|
employee_id: number;
|
|
employee_name: string;
|
|
}
|
|
|
|
export interface PublicSlotsResponse {
|
|
slots: PublicSlot[];
|
|
service: Pick<PublicService, "id" | "name" | "price" | "duration_min">;
|
|
}
|
|
|
|
export interface BookClient {
|
|
name: string;
|
|
email?: string;
|
|
phone?: string;
|
|
notes?: string;
|
|
}
|
|
|
|
export interface BookPayload {
|
|
service_id: number;
|
|
employee_id?: number;
|
|
start_at: string;
|
|
client: BookClient;
|
|
}
|
|
|
|
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
...init,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(init?.headers as Record<string, string> | undefined),
|
|
},
|
|
});
|
|
if (!res.ok) {
|
|
let message = `Error ${res.status}`;
|
|
try {
|
|
const body = await res.json();
|
|
if (body?.error) message = body.error;
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
throw new Error(message);
|
|
}
|
|
return (await res.json()) as T;
|
|
}
|
|
|
|
export function getBusiness(slug: string): Promise<PublicBusinessResponse> {
|
|
return request<PublicBusinessResponse>(`/${slug}`);
|
|
}
|
|
|
|
export function getSlots(
|
|
slug: string,
|
|
serviceId: number,
|
|
date: string,
|
|
employeeId?: number
|
|
): Promise<PublicSlotsResponse> {
|
|
const q = new URLSearchParams({ service_id: String(serviceId), date });
|
|
if (employeeId) q.set("employee_id", String(employeeId));
|
|
return request<PublicSlotsResponse>(`/${slug}/slots?${q.toString()}`);
|
|
}
|
|
|
|
export function book(slug: string, payload: BookPayload): Promise<BookResponse> {
|
|
return request<BookResponse>(`/${slug}/book`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|