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:
@@ -18,23 +18,9 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
import { formatRole } from '@/lib/constants';
|
||||
import { RoleForm } from './role-form';
|
||||
|
||||
/**
|
||||
* Display-normalize a stored role name. Roles are stored with whatever
|
||||
* key the admin entered (snake_case, kebab-case, free text). For UI
|
||||
* display we titlecase + space-separate so "super_admin" reads as
|
||||
* "Super Admin" and "Some role!" reads as "Some Role!" Display-only —
|
||||
* code paths that compare role names use the stored verbatim value.
|
||||
*/
|
||||
function prettifyRoleName(name: string): string {
|
||||
return name
|
||||
.replace(/[_-]+/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
}
|
||||
|
||||
interface Role {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -109,7 +95,7 @@ export function RoleList() {
|
||||
created roles with arbitrary keys still read cleanly.
|
||||
The underlying name is stored verbatim and is what code
|
||||
checks against — display is purely cosmetic. */}
|
||||
<span className="font-medium">{prettifyRoleName(row.original.name)}</span>
|
||||
<span className="font-medium">{formatRole(row.original.name)}</span>
|
||||
{row.original.isSystem && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Lock className="mr-1 h-3 w-3" />
|
||||
@@ -219,7 +205,7 @@ export function RoleList() {
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Permissions — {viewingPermissions ? prettifyRoleName(viewingPermissions.name) : ''}
|
||||
Permissions — {viewingPermissions ? formatRole(viewingPermissions.name) : ''}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Granted vs total per resource. Click Edit to change.
|
||||
|
||||
Reference in New Issue
Block a user