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:
@@ -114,6 +114,34 @@ export const NAV_CATALOG: NavCatalogEntry[] = [
|
||||
category: 'settings',
|
||||
keywords: ['alerts', 'email digest', 'in-app', 'push'],
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/user-settings',
|
||||
label: 'My profile & preferences',
|
||||
category: 'settings',
|
||||
keywords: [
|
||||
'profile',
|
||||
'avatar',
|
||||
'display name',
|
||||
'full name',
|
||||
'phone',
|
||||
'timezone',
|
||||
'locale',
|
||||
'country',
|
||||
'dark mode',
|
||||
'theme',
|
||||
'password',
|
||||
'change email',
|
||||
'security',
|
||||
'account',
|
||||
'me',
|
||||
],
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/dashboard?customize=1',
|
||||
label: 'Customize dashboard widgets',
|
||||
category: 'settings',
|
||||
keywords: ['widgets', 'tiles', 'dashboard layout', 'kpi', 'reorder widgets'],
|
||||
},
|
||||
|
||||
// ─── Admin ──────────────────────────────────────────────────────────────
|
||||
{
|
||||
@@ -150,6 +178,225 @@ export const NAV_CATALOG: NavCatalogEntry[] = [
|
||||
keywords: ['errors', 'exceptions', 'incidents', 'failures'],
|
||||
superAdminOnly: true,
|
||||
},
|
||||
|
||||
// ─── Admin → granular section cards (the AdminSectionsBrowser groups) ────
|
||||
// These deep-link to specific admin sub-pages. Each one's `keywords`
|
||||
// mirrors the corresponding entry in src/components/admin/
|
||||
// admin-sections-browser.tsx — so typing a setting key in the topbar
|
||||
// global search finds the same card the in-admin search would.
|
||||
{
|
||||
href: '/:portSlug/admin/settings',
|
||||
label: 'System Settings',
|
||||
category: 'admin',
|
||||
keywords: [
|
||||
'client portal',
|
||||
'client portal enabled',
|
||||
'ai interest scoring',
|
||||
'ai email drafts',
|
||||
'invoice net10 discount',
|
||||
'net-10',
|
||||
'pipeline weights',
|
||||
'pipeline stage weights',
|
||||
'forecast',
|
||||
'berth rules',
|
||||
'berth status rules',
|
||||
'inquiry contact email',
|
||||
'inquiry notification recipients',
|
||||
'residential notification recipients',
|
||||
'eoi signers',
|
||||
'developer',
|
||||
'approver',
|
||||
'countersign',
|
||||
'recommender max oversize',
|
||||
'recommender top n',
|
||||
'recommender default count',
|
||||
'fallthrough policy',
|
||||
'fallthrough cooldown',
|
||||
'heat weight recency',
|
||||
'heat weight furthest stage',
|
||||
'heat weight interest count',
|
||||
'heat weight eoi count',
|
||||
'tier ladder',
|
||||
'hide late stage',
|
||||
'documents show expired tab',
|
||||
'expired tab',
|
||||
'berths default currency',
|
||||
'default currency',
|
||||
],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/branding',
|
||||
label: 'Branding',
|
||||
category: 'admin',
|
||||
keywords: ['logo', 'app name', 'theme', 'colors', 'email header', 'white-label'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/email',
|
||||
label: 'Email settings',
|
||||
category: 'admin',
|
||||
keywords: ['smtp', 'imap', 'mail', 'from address', 'signature', 'mail server'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/documenso',
|
||||
label: 'EOI signing service',
|
||||
category: 'admin',
|
||||
keywords: ['documenso', 'signing', 'eoi', 'api credentials', 'webhook secret'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/reminders',
|
||||
label: 'Reminder settings',
|
||||
category: 'admin',
|
||||
keywords: ['reminders', 'daily digest', 'delivery window'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/webhooks',
|
||||
label: 'Webhooks',
|
||||
category: 'admin',
|
||||
keywords: ['webhook', 'outgoing', 'callback', 'delivery log'],
|
||||
requires: 'admin.manage_webhooks',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/forms',
|
||||
label: 'Forms',
|
||||
category: 'admin',
|
||||
keywords: ['form templates', 'inquiry', 'intake', 'public form'],
|
||||
requires: 'admin.manage_forms',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/templates',
|
||||
label: 'Document templates',
|
||||
category: 'admin',
|
||||
keywords: ['pdf templates', 'email templates', 'merge fields', 'eoi template'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/email-templates',
|
||||
label: 'Email templates',
|
||||
category: 'admin',
|
||||
keywords: ['transactional emails', 'subject lines', 'portal email', 'inquiry email'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/tags',
|
||||
label: 'Tags',
|
||||
category: 'admin',
|
||||
keywords: ['labels', 'color-coded', 'classification'],
|
||||
requires: 'admin.manage_tags',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/vocabularies',
|
||||
label: 'Vocabularies',
|
||||
category: 'admin',
|
||||
keywords: [
|
||||
'pick lists',
|
||||
'interest temperatures',
|
||||
'status reasons',
|
||||
'tenure types',
|
||||
'expense categories',
|
||||
'document types',
|
||||
],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/custom-fields',
|
||||
label: 'Custom fields',
|
||||
category: 'admin',
|
||||
keywords: ['custom fields', 'tenant fields', 'extra fields'],
|
||||
requires: 'admin.manage_custom_fields',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/duplicates',
|
||||
label: 'Duplicates queue',
|
||||
category: 'admin',
|
||||
keywords: ['dedup', 'duplicate clients', 'merge', 'review queue'],
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/import',
|
||||
label: 'Bulk import',
|
||||
category: 'admin',
|
||||
keywords: ['csv', 'import', 'bulk upload'],
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/sends',
|
||||
label: 'Send log',
|
||||
category: 'admin',
|
||||
keywords: ['email sends', 'brochures', 'send failures', 'retries'],
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/monitoring',
|
||||
label: 'Queue monitoring',
|
||||
category: 'admin',
|
||||
keywords: ['bullmq', 'queue', 'jobs', 'throughput', 'retries'],
|
||||
requires: 'admin.system_backup',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/backup',
|
||||
label: 'Backup & restore',
|
||||
category: 'admin',
|
||||
keywords: ['backup', 'restore', 'retention', 'disaster recovery'],
|
||||
requires: 'admin.system_backup',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/storage',
|
||||
label: 'Storage backend',
|
||||
category: 'admin',
|
||||
keywords: ['s3', 'minio', 'filesystem', 'storage backend', 'object store'],
|
||||
requires: 'admin.system_backup',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/ports',
|
||||
label: 'Ports',
|
||||
category: 'admin',
|
||||
keywords: ['marinas', 'tenancy', 'port management', 'multi-port'],
|
||||
superAdminOnly: true,
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/ai',
|
||||
label: 'AI configuration',
|
||||
category: 'admin',
|
||||
keywords: ['openai', 'anthropic', 'gpt', 'claude', 'llm', 'api key', 'embeddings'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/ocr',
|
||||
label: 'Receipt OCR',
|
||||
category: 'admin',
|
||||
keywords: ['receipt', 'scan', 'tesseract', 'expense scanner', 'confidence'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/website-analytics',
|
||||
label: 'Website analytics (Umami)',
|
||||
category: 'admin',
|
||||
keywords: ['umami', 'analytics', 'traffic', 'visitors', 'marketing', 'pageviews'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/residential-stages',
|
||||
label: 'Residential pipeline stages',
|
||||
category: 'admin',
|
||||
keywords: ['residential stages', 'pipeline', 'residential funnel'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/roles',
|
||||
label: 'Roles & permissions',
|
||||
category: 'admin',
|
||||
keywords: ['roles', 'permissions', 'access control', 'rbac'],
|
||||
requires: 'admin.manage_users',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/invitations',
|
||||
label: 'Invitations',
|
||||
category: 'admin',
|
||||
keywords: ['invite', 'pending invites', 'onboarding'],
|
||||
requires: 'admin.manage_users',
|
||||
},
|
||||
];
|
||||
|
||||
/** Substitute `:portSlug` placeholder for the current port. */
|
||||
|
||||
Reference in New Issue
Block a user