mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 17:29:45 +00:00
PR 5 of the multi-user accounts series. Surfaces the account + role state from the profile row through useAuth so every UI gate downstream can read it without its own fetch, and lands two reusable patterns the upcoming Members tab / Join page / sidebar gating PRs will all consume. What's new in useAuth - Profile SELECT now includes `account_id`, `account_role`, and the joined `accounts(id, name)` via the existing `account_id` FK. `!inner` mirrors the server-side `getCurrentAccount` helper from PR #169. - Context value adds `accountId`, `accountRole`, `account` (`{id, name}` summary), and six derived booleans: `isOwner`, `isAdmin`, `isAgent`, `isViewer`, plus the capability-level `canManageMembers`, `canEditSettings`, `canSendMessages`. - All derived booleans memoise off `profile.account_role`, so consumers can use them as React dep-array entries without re-renders cascading on unrelated profile fields. - The no-provider fallback returns every new field at its least-privileged value (null / false) — UI gates fail closed if a component is somehow rendered outside the AuthProvider. `<RequireRole min="admin" fallback={…}>{children}</RequireRole>` - Renders `children` iff `accountRole >= min`, else `fallback`. - Treats `profileLoading` as "below threshold" so under- privileged users never see a flash of gated content during the initial profile fetch. `useCan(action)` hook - Typed action union: `manage-members` | `edit-settings` | `send-messages` | `view-only` | `delete-account` | `transfer-ownership`. Closed set lets the compiler catch typos at every call site. - Returns `false` while loading. The boolean alternative to `<RequireRole>` — useful for `disabled={!useCan('edit-settings')}` on buttons / inputs / tooltip copy without restructuring JSX. Why no React-renderer tests - The wacrm test convention is pure unit tests (`vitest` without any React testing library). The role predicates that back every new piece here are already covered by the test suite added in PR #169 — the new code is thin glue, and adding a React testing harness for it would be a much larger change than this PR warrants. Verification - `npm run lint` → 0 errors. Warnings drop from 20 → 19 because the rewritten useEffect now lists `fetchProfile` in its deps. - `npm run typecheck` → clean - `npm test` → 366/366 pass - `npm run build` → succeeds Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import type { ReactNode } from "react";
|
|
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { hasMinRole, type AccountRole } from "@/lib/auth/roles";
|
|
|
|
interface RequireRoleProps {
|
|
/** Minimum role to render `children`. Uses the standard hierarchy
|
|
* owner > admin > agent > viewer. */
|
|
min: AccountRole;
|
|
/** What to render while the role is below `min` OR while we don't
|
|
* yet know the role (`profileLoading` is true). Defaults to
|
|
* `null` — most call sites just want the gated element to be
|
|
* absent until we're sure. Pass a placeholder if a layout slot
|
|
* would collapse and re-flow when the role resolves. */
|
|
fallback?: ReactNode;
|
|
children: ReactNode;
|
|
}
|
|
|
|
/**
|
|
* `<RequireRole min="admin">…</RequireRole>` — conditional render
|
|
* helper for UI gated by account role.
|
|
*
|
|
* Three states:
|
|
* 1. profileLoading → render `fallback` (we don't know the role
|
|
* yet; fail closed so we never flash the gated content to an
|
|
* under-privileged user).
|
|
* 2. role ≥ min → render `children`.
|
|
* 3. role < min → render `fallback`.
|
|
*
|
|
* Mirrors the server-side `requireRole(min)` from `@/lib/auth/account`
|
|
* so client and server gates stay aligned by construction.
|
|
*/
|
|
export function RequireRole({
|
|
min,
|
|
fallback = null,
|
|
children,
|
|
}: RequireRoleProps) {
|
|
const { profileLoading, accountRole } = useAuth();
|
|
|
|
if (profileLoading) return <>{fallback}</>;
|
|
if (!accountRole) return <>{fallback}</>;
|
|
if (!hasMinRole(accountRole, min)) return <>{fallback}</>;
|
|
|
|
return <>{children}</>;
|
|
}
|