Files
AgendaPro/src/lib/publicApi.ts
T
AgendaPro Dev e8d2435cc2 AgendaPro v1.0 - multi-tenant SaaS, mobile calendar, public booking
Multi-tenant scheduling SaaS (AgendaPro-equivalent):
- Backend: Node + Express + node:sqlite, multi-tenant (admin/owner/employee),
  versioned migrations, 4 templates (estetica-spa, barberia, clinica, blank).
- Frontend: React 18 + TS + Vite + Tailwind, FullCalendar drag-and-drop,
  Recharts dashboard, mobile-first (day/list views on mobile).
- Features: calendar+services+employees+clients+tickets, dashboard with
  best employee/service, top tickets, top/frequent clients, commissions,
  cancellation policy + no-show tracking, public online booking (/b/:slug),
  cash register (cierre de caja), reminders/notifications center.
- Verification: 65/65 e2e API, 58/58 visual (0 overflow, 0 console errors),
  typecheck clean, vite build OK.
2026-07-25 13:45:53 -06:00

120 lines
2.6 KiB
TypeScript

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;
}
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;
}
export interface BookResponse {
appointment: {
id: number;
start_at: string;
price: number;
service_name: string;
employee_name: string;
};
business: { name: string; currency_symbol: string };
client_created: boolean;
}
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),
});
}