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.
This commit is contained in:
AgendaPro Dev
2026-07-25 13:45:53 -06:00
commit e8d2435cc2
63 changed files with 16539 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
import { Navigate, Route, Routes } from "react-router-dom";
import { AuthProvider, useAuth } from "./lib/auth";
import { LoginPage } from "./pages/LoginPage";
import { AppShell } from "./components/AppShell";
import { AdminShell } from "./components/AdminShell";
import { DashboardPage } from "./pages/DashboardPage";
import { CalendarPage } from "./pages/CalendarPage";
import { EmployeesPage } from "./pages/EmployeesPage";
import { ServicesPage } from "./pages/ServicesPage";
import { ClientsPage } from "./pages/ClientsPage";
import { TicketsPage } from "./pages/TicketsPage";
import { ClientDetailPage } from "./pages/ClientDetailPage";
import { CashPage } from "./pages/CashPage";
import { NotificationsPage } from "./pages/NotificationsPage";
import { SettingsPage } from "./pages/SettingsPage";
import { AdminOverviewPage } from "./pages/admin/AdminOverviewPage";
import { AdminBusinessesPage } from "./pages/admin/AdminBusinessesPage";
import { AdminBusinessDetailPage } from "./pages/admin/AdminBusinessDetailPage";
import BookingPage from "./pages/public/BookingPage";
function Protected() {
const { user, loading } = useAuth();
if (loading) {
return (
<div className="flex h-screen items-center justify-center bg-[#f6f7fb]">
<div className="flex flex-col items-center gap-3 text-slate-500">
<div className="h-9 w-9 animate-spin rounded-full border-2 border-brand-500 border-t-transparent" />
<span className="text-sm font-medium">Cargando AgendaPro</span>
</div>
</div>
);
}
if (!user) return <LoginPage />;
const isAdmin = user.role === "admin";
return (
<Routes>
{/* Admin routes */}
{isAdmin && (
<Route element={<AdminShell />}>
<Route path="/" element={<Navigate to="/admin" replace />} />
<Route path="/admin" element={<AdminOverviewPage />} />
<Route path="/admin/businesses" element={<AdminBusinessesPage />} />
<Route path="/admin/businesses/:id" element={<AdminBusinessDetailPage />} />
<Route path="*" element={<Navigate to="/admin" replace />} />
</Route>
)}
{/* Business routes */}
{!isAdmin && (
<Route element={<AppShell />}>
<Route path="/" element={<Navigate to={user.role === "owner" ? "/dashboard" : "/calendar"} replace />} />
{user.role === "owner" ? (
<Route path="/dashboard" element={<DashboardPage />} />
) : (
<Route path="/dashboard" element={<Navigate to="/calendar" replace />} />
)}
<Route path="/calendar" element={<CalendarPage />} />
<Route path="/clients" element={<ClientsPage />} />
<Route path="/clients/:id" element={<ClientDetailPage />} />
{user.role === "owner" && <Route path="/employees" element={<EmployeesPage />} />}
{user.role === "owner" && <Route path="/services" element={<ServicesPage />} />}
{user.role === "owner" && <Route path="/tickets" element={<TicketsPage />} />}
{user.role === "owner" && <Route path="/cash" element={<CashPage />} />}
{user.role === "owner" && <Route path="/notifications" element={<NotificationsPage />} />}
{user.role === "owner" && <Route path="/settings" element={<SettingsPage />} />}
{/* Block business users from admin */}
<Route path="/admin/*" element={<Navigate to="/" replace />} />
<Route path="*" element={<Navigate to={user.role === "owner" ? "/dashboard" : "/calendar"} replace />} />
</Route>
)}
</Routes>
);
}
export default function App() {
return (
<Routes>
<Route path="/b/:slug" element={<BookingPage />} />
<Route path="*" element={<AuthProvider><Protected /></AuthProvider>} />
</Routes>
);
}