feat(admin+search): user-mgmt polish, role labels, search keyword index
Admin search now matches against per-card keyword lists so typing "client portal", "smtp", "tier ladder" lands on the System Settings card (which hosts those flags). The same keyword list extends the topbar global search (NAV_CATALOG) so any setting key resolves from the cmd-K input — settings results sort to the bottom of the dropdown beneath entity hits. User management: - Third action button (Power/PowerOff) enables/disables sign-in from the desktop list; mobile card dropdown gains the same item. Backed by the existing userProfiles.isActive flag — withAuth already refuses disabled sessions with 403. - UserForm collects first + last name (canonical) alongside displayName, with admin email-change behind a confirmation modal. On confirm we send the OLD address an automated "your admin changed your sign-in email" notice (new template at admin-email-change.ts) and rewrite the Better Auth user row. - Phone field swaps the bare tel input for the shared PhoneInput (country combobox + AsYouType formatting + E.164 storage). - "Manage permissions" link points to /admin/roles?focusUser=… as a stepping stone for the future fine-tuned-permissions UI. Role names normalize through a new ROLE_LABELS + formatRole() helper in constants.ts. Replaces the ad-hoc humanizeRole in sidebar and the prettifyRoleName in role-list; user-list and user-card now render "Sales Agent" instead of "sales_agent". Custom roles pass through unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -235,6 +235,39 @@ export function formatSource(source: string | null | undefined): string | null {
|
||||
return source.charAt(0).toUpperCase() + source.slice(1);
|
||||
}
|
||||
|
||||
// ─── Role names ──────────────────────────────────────────────────────────────
|
||||
// Roles are stored verbatim in the `roles` table as the seeded snake_case
|
||||
// identifier (super_admin, sales_agent, …) so every comparison + permission
|
||||
// lookup keeps using the stable name. UI surfaces should render through
|
||||
// `formatRole()` so customers see "Sales Agent" instead of "sales_agent".
|
||||
// Custom roles created by admins keep their typed name; we only Title-Case
|
||||
// snake_case identifiers, so a hand-typed role like "Marina Lead" comes
|
||||
// through untouched.
|
||||
|
||||
export const ROLE_LABELS: Record<string, string> = {
|
||||
super_admin: 'Super Admin',
|
||||
director: 'Director',
|
||||
sales_manager: 'Sales Manager',
|
||||
sales_agent: 'Sales Agent',
|
||||
finance_manager: 'Finance Manager',
|
||||
viewer: 'Viewer',
|
||||
residential_partner: 'Residential Partner',
|
||||
};
|
||||
|
||||
/** Returns the human label for a stored role name. Falls back to a
|
||||
* Title-Case rendering for legacy / custom roles. */
|
||||
export function formatRole(role: string | null | undefined): string {
|
||||
if (!role) return 'Staff';
|
||||
if (role in ROLE_LABELS) return ROLE_LABELS[role]!;
|
||||
// Title-Case any snake_case input (covers custom roles that happen to be
|
||||
// entered in lowercase_with_underscores). Free-text role names that
|
||||
// already contain spaces pass through unchanged.
|
||||
return role
|
||||
.split('_')
|
||||
.map((part) => (part ? part[0]!.toUpperCase() + part.slice(1) : part))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
// ─── Document Types ──────────────────────────────────────────────────────────
|
||||
|
||||
export const DOCUMENT_TYPES = ['eoi', 'contract', 'nda', 'reservation_agreement', 'other'] as const;
|
||||
|
||||
Reference in New Issue
Block a user