mirror of
https://github.com/temetro/temetro.git
synced 2026-09-03 06:17:55 +00:00
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>
This commit is contained in:
@@ -27,7 +27,7 @@ import {
|
||||
CommandPanel,
|
||||
} from "@/components/ui/command";
|
||||
import { Kbd, KbdGroup } from "@/components/ui/kbd";
|
||||
import { navItems } from "@/lib/nav";
|
||||
import { useActiveRole, visibleNavItems } from "@/lib/roles";
|
||||
|
||||
type CommandPaletteContextValue = { open: () => void };
|
||||
|
||||
@@ -50,6 +50,7 @@ export function useCommandPalette(): CommandPaletteContextValue {
|
||||
export function CommandPaletteProvider({ children }: { children: ReactNode }) {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const role = useActiveRole();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -70,7 +71,8 @@ export function CommandPaletteProvider({ children }: { children: ReactNode }) {
|
||||
value: "pages",
|
||||
label: t("nav.commandGroup"),
|
||||
// Flatten sub-pages so e.g. "Appointments & Schedule" is reachable.
|
||||
items: navItems.flatMap((item) =>
|
||||
// Filtered by role so reception can't jump to clinical pages.
|
||||
items: visibleNavItems(role).flatMap((item) =>
|
||||
item.subs?.length
|
||||
? item.subs.map((sub) => ({
|
||||
id: sub.id,
|
||||
@@ -89,7 +91,7 @@ export function CommandPaletteProvider({ children }: { children: ReactNode }) {
|
||||
),
|
||||
},
|
||||
],
|
||||
[t],
|
||||
[t, role],
|
||||
);
|
||||
|
||||
type Group = (typeof groups)[number];
|
||||
|
||||
Reference in New Issue
Block a user