Files
temetro/frontend/lib/nav.ts
T
Khalid Abdi 6213da9477 feat: admin-provisioned staff, username login & role-based access
Replace the email-invitation flow with admin-provisioned staff accounts and
add role-based access that changes what each member sees.

Backend:
- Enable Better Auth `username` plugin (staff sign in by username); regenerate
  auth schema (+ username/displayUsername on user) and migration 0007.
- Add `doctor` and `reception` roles to the access-control RBAC. `reception` is
  scoped to scheduling + registration (no `prescription` statement).
- New `/api/staff` route: POST creates a user (auth.api.signUpEmail) and adds
  them to the active clinic (auth.api.addMember); GET lists members + usernames.
  Gated by requirePermission({ member: ["create"] }).
- Redact clinical PHI for the reception role in the patients service (read,
  create and update) so demographics-only is enforced server-side.

Frontend:
- usernameClient + Email|Username tabs on the login form.
- lib/roles.ts: useActiveRole + Better-Auth-permission-driven nav visibility,
  default landing, and a route guard (reception -> /appointments, blocked from
  clinical routes). Applied to the sidebar, command palette and auth guard.
- Care team page now provisions staff via a two-step Add-team-member dialog
  (details -> username/password) hitting /api/staff; removes the email-invite
  and pending-invitation UI. New members are contactable from Messages
  automatically (they become org members).
- Hide clinical sections of the patient form and the admin-only settings tabs
  for non-clinical/non-admin roles.

All permission management stays in Better Auth (per the better-auth skills now
referenced in backend/CLAUDE.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 19:12:07 +03:00

95 lines
2.2 KiB
TypeScript

import {
BarChart3,
CalendarClock,
History,
ListTodo,
type LucideIcon,
Mail,
NotebookPen,
Pill,
Plus,
Settings,
Users,
} from "lucide-react";
export type NavSubItem = {
id: string;
// i18n key resolved with t() at render time.
labelKey: string;
icon?: LucideIcon;
link: string;
// Hidden from non-clinical roles (e.g. reception). See lib/roles.ts.
requiresClinical?: boolean;
};
export type NavItem = {
id: string;
// i18n key resolved with t() at render time.
labelKey: string;
icon: LucideIcon;
link: string;
// Optional sub-pages revealed under this item in the sidebar.
subs?: NavSubItem[];
// Hidden from non-clinical roles (e.g. reception). See lib/roles.ts.
requiresClinical?: boolean;
};
// Single source of truth for the primary navigation. Consumed by the sidebar
// (components/sidebar-02/app-sidebar.tsx) and the command palette
// (components/command-palette.tsx) so the two never drift.
export const navItems: NavItem[] = [
{
id: "new-chat",
labelKey: "nav.newChat",
icon: Plus,
link: "/",
requiresClinical: true,
},
{
id: "patients",
labelKey: "nav.patients",
icon: Users,
link: "/patients",
subs: [
{ id: "patients-list", labelKey: "nav.patients", link: "/patients" },
{
id: "appointments",
labelKey: "nav.appointments",
icon: CalendarClock,
link: "/appointments",
},
{
id: "prescriptions",
labelKey: "nav.prescriptions",
icon: Pill,
link: "/prescriptions",
requiresClinical: true,
},
],
},
{
id: "analysis",
labelKey: "nav.analysis",
icon: BarChart3,
link: "/analysis",
requiresClinical: true,
},
{ id: "messages", labelKey: "nav.messages", icon: Mail, link: "/messages" },
{
id: "notes",
labelKey: "nav.notes",
icon: NotebookPen,
link: "/notes",
requiresClinical: true,
},
{ id: "tasks", labelKey: "nav.tasks", icon: ListTodo, link: "/tasks" },
{
id: "activity",
labelKey: "nav.activity",
icon: History,
link: "/activity",
requiresClinical: true,
},
{ id: "settings", labelKey: "nav.settings", icon: Settings, link: "/settings" },
];