@invect/user-auth 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/backend/index.cjs +12 -12
- package/dist/backend/index.cjs.map +1 -1
- package/dist/backend/index.d.ts +9 -8
- package/dist/backend/index.d.ts.map +1 -1
- package/dist/backend/index.mjs +11 -11
- package/dist/backend/index.mjs.map +1 -1
- package/dist/backend/plugin.d.ts +8 -8
- package/dist/backend/plugin.d.ts.map +1 -1
- package/dist/backend/types.d.ts +7 -5
- package/dist/backend/types.d.ts.map +1 -1
- package/dist/frontend/components/AuthenticatedInvect.d.ts +1 -1
- package/dist/frontend/index.cjs.map +1 -1
- package/dist/frontend/index.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["UserIcon"],"sources":["../../src/frontend/providers/AuthProvider.tsx","../../src/frontend/components/SignInForm.tsx","../../src/frontend/components/SignInPage.tsx","../../src/frontend/components/UserButton.tsx","../../src/frontend/components/AuthGate.tsx","../../src/frontend/components/UserManagement.tsx","../../src/frontend/components/AuthenticatedInvect.tsx","../../src/frontend/components/UserManagementPage.tsx","../../src/frontend/components/ProfilePage.tsx","../../src/frontend/components/SidebarUserMenu.tsx","../../src/frontend/plugins/authFrontendPlugin.ts"],"sourcesContent":["/**\n * AuthProvider — Context provider for authentication state.\n *\n * Fetches the current session from the better-auth proxy endpoints\n * and caches it via React Query. Provides sign-in, sign-up, and\n * sign-out actions to child components.\n */\n\nimport { createContext, useContext, useCallback, useMemo, type ReactNode } from 'react';\nimport { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';\nimport type {\n AuthSession,\n AuthUser,\n SignInCredentials,\n SignUpCredentials,\n} from '../../shared/types';\n\n// ─────────────────────────────────────────────────────────────\n// Context Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface AuthContextValue {\n /** Current authenticated user, null if not signed in */\n user: AuthUser | null;\n /** Whether the user is authenticated */\n isAuthenticated: boolean;\n /** Whether the session query is still loading */\n isLoading: boolean;\n /** Sign in with email/password */\n signIn: (credentials: SignInCredentials) => Promise<void>;\n /** Sign up with email/password */\n signUp: (credentials: SignUpCredentials) => Promise<void>;\n /** Sign out the current session */\n signOut: () => Promise<void>;\n /** Whether a sign-in is in progress */\n isSigningIn: boolean;\n /** Whether a sign-up is in progress */\n isSigningUp: boolean;\n /** Last auth error, if any */\n error: string | null;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Context\n// ─────────────────────────────────────────────────────────────\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\n// ─────────────────────────────────────────────────────────────\n// Provider\n// ─────────────────────────────────────────────────────────────\n\nexport interface AuthProviderProps {\n children: ReactNode;\n /**\n * Base URL for the Invect API (e.g. 'http://localhost:3000/invect').\n * Auth endpoints are at `${baseUrl}/plugins/auth/api/auth/*`.\n */\n baseUrl: string;\n}\n\nexport function AuthProvider({ children, baseUrl }: AuthProviderProps) {\n const queryClient = useQueryClient();\n\n // Construct auth API base URL\n const authApiBase = `${baseUrl}/plugins/auth/api/auth`;\n\n // ── Session Query ──────────────────────────────────────────\n\n const { data: session, isLoading } = useQuery<AuthSession>({\n queryKey: ['auth', 'session'],\n queryFn: async () => {\n const response = await fetch(`${authApiBase}/get-session`, {\n credentials: 'include',\n });\n if (!response.ok) {\n return { user: null as unknown as AuthUser, isAuthenticated: false };\n }\n const data = await response.json();\n if (!data?.user) {\n return { user: null as unknown as AuthUser, isAuthenticated: false };\n }\n return {\n user: {\n id: data.user.id,\n name: data.user.name ?? undefined,\n email: data.user.email ?? undefined,\n image: data.user.image ?? undefined,\n role: data.user.role ?? undefined,\n },\n isAuthenticated: true,\n };\n },\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 1,\n });\n\n // ── Sign In ────────────────────────────────────────────────\n\n const signInMutation = useMutation({\n mutationFn: async (credentials: SignInCredentials) => {\n const response = await fetch(`${authApiBase}/sign-in/email`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(credentials),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({ message: 'Sign in failed' }));\n throw new Error(err.message || `Sign in failed (${response.status})`);\n }\n return response.json();\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['auth', 'session'] });\n },\n });\n\n // ── Sign Up ────────────────────────────────────────────────\n\n const signUpMutation = useMutation({\n mutationFn: async (credentials: SignUpCredentials) => {\n const response = await fetch(`${authApiBase}/sign-up/email`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(credentials),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({ message: 'Sign up failed' }));\n throw new Error(err.message || `Sign up failed (${response.status})`);\n }\n return response.json();\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['auth', 'session'] });\n },\n });\n\n // ── Sign Out ───────────────────────────────────────────────\n\n const signOutMutation = useMutation({\n mutationFn: async () => {\n const response = await fetch(`${authApiBase}/sign-out`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({}),\n });\n if (!response.ok) {\n const err = await response.json().catch(async () => ({\n message: (await response.text().catch(() => '')) || 'Sign out failed',\n }));\n throw new Error(err.message || `Sign out failed (${response.status})`);\n }\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['auth', 'session'] });\n },\n });\n\n // ── Callbacks ──────────────────────────────────────────────\n\n const signIn = useCallback(\n async (credentials: SignInCredentials) => {\n await signInMutation.mutateAsync(credentials);\n },\n [signInMutation],\n );\n\n const signUp = useCallback(\n async (credentials: SignUpCredentials) => {\n await signUpMutation.mutateAsync(credentials);\n },\n [signUpMutation],\n );\n\n const signOut = useCallback(async () => {\n await signOutMutation.mutateAsync();\n }, [signOutMutation]);\n\n // ── Error ──────────────────────────────────────────────────\n\n const error =\n signInMutation.error?.message ??\n signUpMutation.error?.message ??\n signOutMutation.error?.message ??\n null;\n\n // ── Context Value ──────────────────────────────────────────\n\n const value = useMemo<AuthContextValue>(\n () => ({\n user: session?.isAuthenticated ? session.user : null,\n isAuthenticated: session?.isAuthenticated ?? false,\n isLoading,\n signIn,\n signUp,\n signOut,\n isSigningIn: signInMutation.isPending,\n isSigningUp: signUpMutation.isPending,\n error,\n }),\n [\n session,\n isLoading,\n signIn,\n signUp,\n signOut,\n signInMutation.isPending,\n signUpMutation.isPending,\n error,\n ],\n );\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Hook\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Access auth context — current user, sign-in/sign-up/sign-out actions.\n *\n * Must be used within an `<AuthProvider>`.\n * Returns a safe fallback (unauthenticated) if provider is missing.\n */\nexport function useAuth(): AuthContextValue {\n const ctx = useContext(AuthContext);\n\n if (!ctx) {\n // Graceful fallback\n return {\n user: null,\n isAuthenticated: false,\n isLoading: false,\n signIn: async () => {\n throw new Error('AuthProvider not found');\n },\n signUp: async () => {\n throw new Error('AuthProvider not found');\n },\n signOut: async () => {\n throw new Error('AuthProvider not found');\n },\n isSigningIn: false,\n isSigningUp: false,\n error: null,\n };\n }\n\n return ctx;\n}\n","/**\n * SignInForm — Email/password sign-in form component.\n *\n * Uses the AuthProvider's signIn action. Styled to match the Invect\n * design system with grouped fields, clean labels, and themed inputs.\n */\n\nimport { useState, type FormEvent } from 'react';\nimport { useAuth } from '../providers/AuthProvider';\n\nexport interface SignInFormProps {\n /** Called after successful sign-in */\n onSuccess?: () => void;\n /** Additional CSS class names */\n className?: string;\n}\n\nexport function SignInForm({ onSuccess, className }: SignInFormProps) {\n const { signIn, isSigningIn, error } = useAuth();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [localError, setLocalError] = useState<string | null>(null);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setLocalError(null);\n\n if (!email.trim() || !password.trim()) {\n setLocalError('Email and password are required');\n return;\n }\n\n try {\n await signIn({ email, password });\n onSuccess?.();\n } catch (err) {\n setLocalError(err instanceof Error ? err.message : 'Sign in failed');\n }\n };\n\n const displayError = localError ?? error;\n\n return (\n <form onSubmit={handleSubmit} className={className}>\n <div className=\"flex flex-col gap-6\">\n {/* Email field */}\n <div className=\"grid gap-2\">\n <label htmlFor=\"auth-signin-email\" className=\"text-sm font-medium leading-none\">\n Email\n </label>\n <input\n id=\"auth-signin-email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"you@example.com\"\n autoComplete=\"email\"\n required\n className=\"flex h-9 w-full rounded-md border border-imp-border bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-imp-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-imp-ring disabled:cursor-not-allowed disabled:opacity-50\"\n />\n </div>\n\n {/* Password field */}\n <div className=\"grid gap-2\">\n <label htmlFor=\"auth-signin-password\" className=\"text-sm font-medium leading-none\">\n Password\n </label>\n <input\n id=\"auth-signin-password\"\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"••••••••\"\n autoComplete=\"current-password\"\n required\n className=\"flex h-9 w-full rounded-md border border-imp-border bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-imp-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-imp-ring disabled:cursor-not-allowed disabled:opacity-50\"\n />\n </div>\n\n {/* Error message */}\n {displayError && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900/50 dark:bg-red-950/20 dark:text-red-400\">\n {displayError}\n </div>\n )}\n\n {/* Submit */}\n <button\n type=\"submit\"\n disabled={isSigningIn}\n className=\"inline-flex h-9 w-full items-center justify-center gap-2 whitespace-nowrap rounded-md bg-imp-foreground px-4 py-2 text-sm font-medium text-imp-background shadow transition-colors hover:bg-imp-foreground/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-imp-ring disabled:pointer-events-none disabled:opacity-50\"\n >\n {isSigningIn ? 'Signing in…' : 'Login'}\n </button>\n </div>\n </form>\n );\n}\n","/**\n * SignInPage — Full-page sign-in component with layout.\n *\n * Renders the SignInForm centered on the page with a logo, title,\n * and grouped fields matching the Invect design system.\n * Sign-up is not offered — new users are created by admins.\n */\n\nimport { SignInForm } from './SignInForm';\n\nexport interface SignInPageProps {\n /** Called after successful sign-in */\n onSuccess?: () => void;\n /** Called when user clicks \"Sign Up\" link (optional — hidden if omitted) */\n onNavigateToSignUp?: () => void;\n /** Page title */\n title?: string;\n /** Page subtitle */\n subtitle?: string;\n}\n\nexport function SignInPage({\n onSuccess,\n onNavigateToSignUp,\n title = 'Welcome back',\n subtitle = 'Sign in to your account to continue',\n}: SignInPageProps) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-imp-background p-4 text-imp-foreground\">\n <div className=\"flex w-full max-w-sm flex-col gap-6\">\n {/* Header: logo + title + subtitle */}\n <div className=\"flex flex-col items-center gap-2 text-center\">\n <div className=\"flex items-center justify-center\">\n <ImpLogo className=\"h-10 w-auto\" />\n </div>\n <h1 className=\"text-xl font-bold\">{title}</h1>\n <p className=\"text-sm text-imp-muted-foreground\">{subtitle}</p>\n </div>\n\n {/* Form */}\n <SignInForm onSuccess={onSuccess} />\n\n {/* Separator + admin note */}\n <div className=\"relative text-center text-sm\">\n <div className=\"absolute inset-0 top-1/2 border-t border-imp-border\" />\n <span className=\"relative bg-imp-background px-2 text-imp-muted-foreground\">\n Admin-managed accounts\n </span>\n </div>\n\n <p className=\"px-6 text-center text-xs text-imp-muted-foreground\">\n New accounts are created by your administrator.\n {onNavigateToSignUp ? ' Or ' : ' Contact your admin if you need access.'}\n {onNavigateToSignUp && (\n <button\n type=\"button\"\n onClick={onNavigateToSignUp}\n className=\"font-medium underline underline-offset-4 hover:text-imp-foreground\"\n >\n Sign up\n </button>\n )}\n </p>\n </div>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Internal: Imperat logo\n// ─────────────────────────────────────────────────────────────\n\nfunction ImpLogo({ className }: { className?: string }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 109 209\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"9\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n >\n <path d=\"M31.7735 104.5L4.50073 18.7859L54.5007 33.0716L31.7735 104.5Z\" />\n <path d=\"M54.5007 4.5L104.501 18.7857L54.5007 33.0714L4.50073 18.7857L54.5007 4.5Z\" />\n <path d=\"M4.50073 190.214L54.5007 33.0716L104.501 18.7859L4.50073 190.214Z\" />\n <path d=\"M54.5007 204.5L81.4238 104.5L104.501 18.7859L4.50073 190.214L54.5007 204.5Z\" />\n <path d=\"M54.5007 204.5L81.4238 104.5L104.501 190.214L54.5007 204.5Z\" />\n </svg>\n );\n}\n","/**\n * UserButton — Compact user avatar + dropdown for the authenticated user.\n *\n * Shows the user's avatar/initials when signed in, with a dropdown\n * containing their name, email, and sign-out button.\n * Shows a \"Sign In\" button when not authenticated.\n */\n\nimport { useState, useRef, useEffect } from 'react';\nimport { LogOut, User } from 'lucide-react';\nimport { useAuth } from '../providers/AuthProvider';\nimport { formatAuthRoleLabel } from '../../shared/roles';\n\nexport interface UserButtonProps {\n /** Called when the sign-in button is clicked (unauthenticated state) */\n onSignInClick?: () => void;\n /** Additional CSS class names */\n className?: string;\n}\n\nexport function UserButton({ onSignInClick, className }: UserButtonProps) {\n const { user, isAuthenticated, isLoading, signOut } = useAuth();\n const [isOpen, setIsOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n const handleClick = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setIsOpen(false);\n }\n };\n if (isOpen) {\n document.addEventListener('mousedown', handleClick);\n }\n return () => document.removeEventListener('mousedown', handleClick);\n }, [isOpen]);\n\n if (isLoading) {\n return <div className={`h-8 w-8 animate-pulse rounded-full bg-imp-muted ${className ?? ''}`} />;\n }\n\n if (!isAuthenticated || !user) {\n return (\n <button\n onClick={onSignInClick}\n className={`inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium text-imp-foreground hover:bg-imp-muted ${className ?? ''}`}\n >\n <User className=\"h-4 w-4\" />\n Sign In\n </button>\n );\n }\n\n const initials = (user.name ?? user.email ?? user.id)[0]?.toUpperCase() ?? '?';\n\n return (\n <div ref={ref} className={`relative ${className ?? ''}`}>\n {/* Avatar trigger */}\n <button\n onClick={() => setIsOpen(!isOpen)}\n className=\"flex h-8 w-8 items-center justify-center rounded-full bg-imp-primary/10 text-sm font-medium text-imp-primary hover:bg-imp-primary/20 transition-colors\"\n title={user.name ?? user.email ?? user.id}\n >\n {user.image ? (\n <img\n src={user.image}\n alt={user.name ?? ''}\n className=\"h-8 w-8 rounded-full object-cover\"\n />\n ) : (\n initials\n )}\n </button>\n\n {/* Dropdown */}\n {isOpen && (\n <div className=\"absolute right-0 top-full z-50 mt-2 w-56 rounded-lg border border-imp-border bg-imp-background shadow-lg\">\n {/* User info */}\n <div className=\"border-b border-imp-border px-4 py-3\">\n <p className=\"truncate text-sm font-medium\">{user.name ?? 'User'}</p>\n {user.email && (\n <p className=\"truncate text-xs text-imp-muted-foreground\">{user.email}</p>\n )}\n {user.role && (\n <span className=\"mt-1 inline-block rounded-full bg-imp-muted px-2 py-0.5 text-xs font-medium\">\n {formatAuthRoleLabel(user.role)}\n </span>\n )}\n </div>\n\n {/* Actions */}\n <div className=\"p-1\">\n <button\n onClick={async () => {\n setIsOpen(false);\n await signOut();\n }}\n className=\"flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm text-imp-foreground hover:bg-imp-muted\"\n >\n <LogOut className=\"h-4 w-4\" />\n Sign Out\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n","/**\n * AuthGate — Conditionally renders children based on auth state.\n *\n * Useful for protecting routes or showing different content for\n * authenticated vs unauthenticated users.\n */\n\nimport type { ReactNode } from 'react';\nimport { useAuth } from '../providers/AuthProvider';\n\nexport interface AuthGateProps {\n /** Content to show when authenticated */\n children: ReactNode;\n /** Content to show when NOT authenticated (defaults to null) */\n fallback?: ReactNode;\n /** Content to show while loading (defaults to null) */\n loading?: ReactNode;\n}\n\nexport function AuthGate({ children, fallback = null, loading = null }: AuthGateProps) {\n const { isAuthenticated, isLoading } = useAuth();\n\n if (isLoading) {\n return <>{loading}</>;\n }\n\n if (!isAuthenticated) {\n return <>{fallback}</>;\n }\n\n return <>{children}</>;\n}\n","/**\n * UserManagement — Admin panel for managing users.\n *\n * Displays a list of users with the ability to:\n * - Create new users (email/password/role)\n * - Change user roles\n * - Delete users\n *\n * Only visible to admin users. Uses the auth plugin's\n * `/plugins/auth/users` endpoints.\n */\n\nimport { useState, useMemo, useCallback, type FormEvent } from 'react';\nimport {\n ArrowDown,\n ArrowUp,\n ChevronsUpDown,\n ChevronDown,\n Search,\n Trash2,\n UserPlus,\n} from 'lucide-react';\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@invect/frontend';\nimport { useAuth } from '../providers/AuthProvider';\nimport {\n AUTH_ADMIN_ROLE,\n AUTH_ASSIGNABLE_ROLES,\n AUTH_DEFAULT_ROLE,\n formatAuthRoleLabel,\n isAuthAssignableRole,\n} from '../../shared/roles';\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\ninterface ManagedUser {\n id: string;\n name?: string;\n email?: string;\n role?: string;\n createdAt?: string;\n}\n\nexport interface UserManagementProps {\n /** Base URL for the Invect API (same as AuthProvider's baseUrl) */\n apiBaseUrl: string;\n /** Additional CSS class names */\n className?: string;\n}\n\nconst ASSIGNABLE_ROLE_OPTIONS: Array<{\n value: (typeof AUTH_ASSIGNABLE_ROLES)[number];\n label: string;\n description: string;\n}> = [\n {\n value: AUTH_DEFAULT_ROLE,\n label: 'None',\n description: 'No global access; flow access can still be granted via RBAC.',\n },\n { value: 'owner', label: 'Owner', description: 'Can edit and manage sharing for all flows.' },\n { value: 'editor', label: 'Editor', description: 'Can inspect, run, and edit flows.' },\n { value: 'operator', label: 'Operator', description: 'Can inspect and run flows.' },\n { value: 'viewer', label: 'Viewer', description: 'Can inspect flows.' },\n];\n\nconst ROLE_BADGE_CLASSES = 'border-imp-border bg-imp-muted/50 text-imp-foreground';\n\nfunction getInitials(user: ManagedUser): string {\n if (user.name) {\n const parts = user.name.trim().split(/\\s+/);\n if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n return parts[0][0].toUpperCase();\n }\n if (user.email) return user.email[0].toUpperCase();\n return '?';\n}\n\nfunction formatDate(iso: string): string {\n try {\n return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(new Date(iso));\n } catch {\n return iso;\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Sortable column header\n// ─────────────────────────────────────────────────────────────\n\nfunction SortHeader({\n label,\n field,\n sortField,\n sortDir,\n onSort,\n align = 'left',\n}: {\n label: string;\n field: 'name' | 'createdAt' | 'role';\n sortField: 'name' | 'createdAt' | 'role';\n sortDir: 'asc' | 'desc';\n onSort: (field: 'name' | 'createdAt' | 'role') => void;\n align?: 'left' | 'right';\n}) {\n const active = sortField === field;\n const Icon = active ? (sortDir === 'asc' ? ArrowUp : ArrowDown) : ChevronsUpDown;\n return (\n <th className={`px-4 py-2.5 text-${align} font-medium`}>\n <button\n type=\"button\"\n onClick={() => onSort(field)}\n className={`inline-flex items-center gap-1 rounded transition-colors hover:text-imp-foreground ${active ? 'text-imp-foreground' : ''}`}\n >\n {label}\n <Icon className=\"w-3 h-3 shrink-0\" />\n </button>\n </th>\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Role Dropdown\n// ─────────────────────────────────────────────────────────────\n\nfunction RoleDropdown({\n value,\n userId,\n disabled,\n onChange,\n}: {\n value: string;\n userId: string;\n disabled: boolean;\n onChange: (userId: string, role: string) => void;\n}) {\n const current = isAuthAssignableRole(value) ? value : AUTH_DEFAULT_ROLE;\n const currentLabel = ASSIGNABLE_ROLE_OPTIONS.find((o) => o.value === current)?.label ?? current;\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild disabled={disabled}>\n <button\n type=\"button\"\n className={`inline-flex w-28 items-center justify-between gap-1.5 rounded-full border px-2.5 py-0.5 text-sm font-medium transition-colors hover:bg-imp-muted disabled:cursor-not-allowed disabled:opacity-50 ${ROLE_BADGE_CLASSES}`}\n >\n {currentLabel}\n {!disabled && <ChevronDown className=\"w-3 h-3 shrink-0\" />}\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-56\">\n <DropdownMenuLabel className=\"text-xs\">Set role</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {ASSIGNABLE_ROLE_OPTIONS.map((option) => (\n <DropdownMenuItem\n key={option.value}\n onSelect={() => onChange(userId, option.value)}\n className={`items-start gap-0 px-2 py-2 ${current === option.value ? 'bg-accent text-accent-foreground' : ''}`}\n >\n <div className=\"min-w-0 text-left\">\n <div className=\"text-sm font-medium\">{option.label}</div>\n <div className=\"text-xs text-muted-foreground\">{option.description}</div>\n </div>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────\n\nexport function UserManagement({ apiBaseUrl, className }: UserManagementProps) {\n const { user, isAuthenticated } = useAuth();\n const [users, setUsers] = useState<ManagedUser[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [showCreateDialog, setShowCreateDialog] = useState(false);\n const [pendingDeleteUser, setPendingDeleteUser] = useState<ManagedUser | null>(null);\n const [hasFetched, setHasFetched] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n const [sortField, setSortField] = useState<'name' | 'createdAt' | 'role'>('name');\n const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');\n\n const PAGE_SIZE = 10;\n\n const handleSort = useCallback((field: 'name' | 'createdAt' | 'role') => {\n setSortField((prev) => {\n if (prev === field) {\n setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));\n return prev;\n }\n setSortDir('asc');\n return field;\n });\n setCurrentPage(1);\n }, []);\n\n const filteredUsers = useMemo(() => {\n let result = users;\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (u) =>\n u.name?.toLowerCase().includes(q) ||\n u.email?.toLowerCase().includes(q) ||\n u.role?.toLowerCase().includes(q),\n );\n }\n return [...result].sort((a, b) => {\n let aVal = '';\n let bVal = '';\n if (sortField === 'name') {\n aVal = (a.name || a.email || '').toLowerCase();\n bVal = (b.name || b.email || '').toLowerCase();\n } else if (sortField === 'createdAt') {\n aVal = a.createdAt ?? '';\n bVal = b.createdAt ?? '';\n } else if (sortField === 'role') {\n aVal = (a.role ?? '').toLowerCase();\n bVal = (b.role ?? '').toLowerCase();\n }\n const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;\n return sortDir === 'asc' ? cmp : -cmp;\n });\n }, [users, searchQuery, sortField, sortDir]);\n\n const totalPages = Math.max(1, Math.ceil(filteredUsers.length / PAGE_SIZE));\n const paginatedUsers = filteredUsers.slice(\n (currentPage - 1) * PAGE_SIZE,\n currentPage * PAGE_SIZE,\n );\n\n const authApiBase = `${apiBaseUrl}/plugins/auth`;\n\n // ── Fetch Users ────────────────────────────────────────────\n\n const fetchUsers = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const res = await fetch(`${authApiBase}/users`, { credentials: 'include' });\n if (!res.ok) {\n const data = await res.json().catch(() => ({ error: 'Failed to fetch users' }));\n throw new Error(data.error || data.message || `HTTP ${res.status}`);\n }\n const data = await res.json();\n setUsers(data.users ?? []);\n setHasFetched(true);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to fetch users');\n } finally {\n setIsLoading(false);\n }\n }, [authApiBase]);\n\n // ── Delete User ────────────────────────────────────────────\n\n const deleteUser = useCallback(\n async (userId: string) => {\n try {\n const res = await fetch(`${authApiBase}/users/${userId}`, {\n method: 'DELETE',\n credentials: 'include',\n });\n if (!res.ok) {\n const data = await res.json().catch(() => ({}));\n throw new Error(data.error || `HTTP ${res.status}`);\n }\n setUsers((prev) => prev.filter((u) => u.id !== userId));\n setPendingDeleteUser(null);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to delete user');\n setPendingDeleteUser(null);\n }\n },\n [authApiBase],\n );\n\n // ── Update Role ────────────────────────────────────────────\n\n const updateRole = useCallback(\n async (userId: string, role: string) => {\n try {\n const res = await fetch(`${authApiBase}/users/${userId}/role`, {\n method: 'PATCH',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ role }),\n });\n if (!res.ok) {\n const data = await res.json().catch(() => ({}));\n throw new Error(data.error || `HTTP ${res.status}`);\n }\n setUsers((prev) => prev.map((u) => (u.id === userId ? { ...u, role } : u)));\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update role');\n }\n },\n [authApiBase],\n );\n\n // Guard: only admins see this\n if (!isAuthenticated || user?.role !== 'admin') {\n return null;\n }\n\n // Auto-fetch on first render\n if (!hasFetched && !isLoading) {\n fetchUsers();\n }\n\n return (\n <div className={`space-y-4 ${className ?? ''}`}>\n {/* Search + Create User */}\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1 max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 pointer-events-none text-imp-muted-foreground\" />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setCurrentPage(1);\n }}\n placeholder=\"Search users…\"\n className=\"w-full py-2 pr-3 text-sm border rounded-lg outline-none border-imp-border bg-transparent pl-9 placeholder:text-imp-muted-foreground focus:border-imp-primary/50\"\n />\n </div>\n <button\n type=\"button\"\n onClick={() => setShowCreateDialog(true)}\n className=\"flex items-center gap-1.5 rounded-lg border border-imp-border px-3 py-2 text-sm font-medium text-imp-muted-foreground transition-colors hover:border-imp-primary/50 hover:text-imp-foreground\"\n >\n <UserPlus className=\"w-4 h-4\" /> Create User\n </button>\n </div>\n\n {error && (\n <div className=\"p-3 text-sm text-red-600 rounded-md bg-red-50 dark:bg-red-950/20 dark:text-red-400\">\n {error}\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"ml-2 underline hover:no-underline\"\n >\n Dismiss\n </button>\n </div>\n )}\n\n {/* Users Table */}\n <div className=\"overflow-hidden border rounded-xl border-imp-border bg-imp-background/40\">\n <table className=\"w-full text-sm table-fixed\">\n <colgroup>\n <col className=\"w-[40%]\" />\n <col className=\"w-[20%]\" />\n <col className=\"w-[20%]\" />\n <col className=\"w-[20%]\" />\n </colgroup>\n <thead>\n <tr className=\"text-xs font-medium border-b border-imp-border bg-imp-muted/20 text-imp-muted-foreground\">\n <SortHeader\n label=\"User\"\n field=\"name\"\n sortField={sortField}\n sortDir={sortDir}\n onSort={handleSort}\n />\n <SortHeader\n label=\"Global Role\"\n field=\"role\"\n sortField={sortField}\n sortDir={sortDir}\n onSort={handleSort}\n />\n <SortHeader\n label=\"Created\"\n field=\"createdAt\"\n sortField={sortField}\n sortDir={sortDir}\n onSort={handleSort}\n align=\"right\"\n />\n <th className=\"px-4 py-2.5 text-right font-medium\"></th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-imp-border\">\n {paginatedUsers.length === 0 && (\n <tr>\n <td colSpan={4} className=\"px-4 py-8 text-sm text-center text-imp-muted-foreground\">\n {!hasFetched || isLoading\n ? 'Loading…'\n : searchQuery\n ? 'No users match your search.'\n : 'No users found.'}\n </td>\n </tr>\n )}\n {paginatedUsers.map((u) => (\n <tr key={u.id} className=\"group hover:bg-imp-muted/20\">\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center w-8 h-8 text-xs font-semibold rounded-full shrink-0 bg-imp-primary/10 text-imp-primary\">\n {getInitials(u)}\n </div>\n <div className=\"min-w-0\">\n <div className=\"font-medium truncate text-imp-foreground\">\n {u.name || 'Unnamed'}\n </div>\n <div className=\"text-xs truncate text-imp-muted-foreground\">{u.email}</div>\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n {u.role === AUTH_ADMIN_ROLE ? (\n <span\n className={`inline-flex rounded-full border px-2.5 py-0.5 text-sm font-medium ${ROLE_BADGE_CLASSES}`}\n >\n {formatAuthRoleLabel(u.role)}\n </span>\n ) : (\n <RoleDropdown\n value={u.role ?? AUTH_DEFAULT_ROLE}\n userId={u.id}\n disabled={u.id === user?.id}\n onChange={updateRole}\n />\n )}\n </td>\n <td className=\"px-4 py-3 text-xs text-right text-imp-muted-foreground\">\n {u.createdAt ? formatDate(u.createdAt) : '—'}\n </td>\n <td className=\"px-4 py-3 text-right\">\n {u.role === AUTH_ADMIN_ROLE ? (\n <span className=\"text-xs text-imp-muted-foreground\">Config managed</span>\n ) : u.id !== user?.id ? (\n <button\n type=\"button\"\n onClick={() => setPendingDeleteUser(u)}\n className=\"rounded-md p-1.5 text-imp-muted-foreground opacity-0 transition-opacity hover:bg-red-50 hover:text-red-600 group-hover:opacity-100 dark:hover:bg-red-950/20 dark:hover:text-red-400\"\n >\n <Trash2 className=\"w-4 h-4\" />\n </button>\n ) : null}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n {/* Pagination */}\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-xs text-imp-muted-foreground\">\n {filteredUsers.length} user{filteredUsers.length !== 1 ? 's' : ''}\n {searchQuery && ` matching \"${searchQuery}\"`}\n </span>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}\n disabled={currentPage === 1}\n className=\"rounded-md border border-imp-border px-2.5 py-1 text-xs hover:bg-imp-muted disabled:opacity-50\"\n >\n Previous\n </button>\n <span className=\"text-xs text-imp-muted-foreground\">\n {currentPage} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}\n disabled={currentPage === totalPages}\n className=\"rounded-md border border-imp-border px-2.5 py-1 text-xs hover:bg-imp-muted disabled:opacity-50\"\n >\n Next\n </button>\n </div>\n </div>\n\n {/* Create User Dialog */}\n <Dialog open={showCreateDialog} onOpenChange={(open) => !open && setShowCreateDialog(false)}>\n <DialogContent className=\"max-w-md border-imp-border bg-imp-background text-imp-foreground sm:max-w-md\">\n <DialogHeader>\n <DialogTitle className=\"text-sm font-semibold\">Create New User</DialogTitle>\n </DialogHeader>\n <CreateUserForm\n apiBaseUrl={authApiBase}\n onCreated={(newUser) => {\n setUsers((prev) => [...prev, newUser]);\n setShowCreateDialog(false);\n }}\n onCancel={() => setShowCreateDialog(false)}\n />\n </DialogContent>\n </Dialog>\n\n {/* Delete Confirmation Dialog */}\n <Dialog\n open={pendingDeleteUser !== null}\n onOpenChange={(open) => !open && setPendingDeleteUser(null)}\n >\n <DialogContent className=\"max-w-sm border-imp-border bg-imp-background text-imp-foreground sm:max-w-sm\">\n <DialogHeader>\n <DialogTitle className=\"text-sm font-semibold\">Delete user</DialogTitle>\n </DialogHeader>\n <p className=\"text-sm text-imp-muted-foreground\">\n Are you sure you want to delete{' '}\n <span className=\"font-medium text-imp-foreground\">\n {pendingDeleteUser?.name || pendingDeleteUser?.email || 'this user'}\n </span>\n ? This action cannot be undone.\n </p>\n <DialogFooter className=\"gap-2\">\n <button\n type=\"button\"\n onClick={() => setPendingDeleteUser(null)}\n className=\"rounded-md border border-imp-border px-3 py-1.5 text-sm hover:bg-imp-muted\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => pendingDeleteUser && deleteUser(pendingDeleteUser.id)}\n className=\"rounded-md bg-red-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-red-700\"\n >\n Delete\n </button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Internal: Create User Form\n// ─────────────────────────────────────────────────────────────\n\nfunction CreateUserForm({\n apiBaseUrl,\n onCreated,\n onCancel,\n}: {\n apiBaseUrl: string;\n onCreated: (user: ManagedUser) => void;\n onCancel: () => void;\n}) {\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [name, setName] = useState('');\n const [role, setRole] = useState<string>(AUTH_DEFAULT_ROLE);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!email.trim() || !password.trim()) {\n setError('Email and password are required');\n return;\n }\n if (password.length < 8) {\n setError('Password must be at least 8 characters');\n return;\n }\n\n setIsSubmitting(true);\n try {\n const res = await fetch(`${apiBaseUrl}/users`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ email, password, name: name.trim() || undefined, role }),\n });\n const data = await res.json();\n if (!res.ok) {\n throw new Error(data.error || data.message || `HTTP ${res.status}`);\n }\n onCreated(data.user);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create user');\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block mb-1 text-xs font-medium text-imp-foreground\">Name</label>\n <input\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"User name\"\n className=\"w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm placeholder:text-imp-muted-foreground focus:outline-none focus:border-imp-primary/50\"\n />\n </div>\n <div>\n <label className=\"block mb-1 text-xs font-medium text-imp-foreground\">Role</label>\n <select\n value={role}\n onChange={(e) => setRole(e.target.value)}\n className=\"w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm focus:outline-none focus:border-imp-primary/50\"\n >\n {ASSIGNABLE_ROLE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-imp-muted-foreground\">\n Flow access can still be granted via RBAC.\n </p>\n </div>\n </div>\n\n <div>\n <label className=\"block mb-1 text-xs font-medium text-imp-foreground\">\n Email <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"user@example.com\"\n required\n autoComplete=\"off\"\n className=\"w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm placeholder:text-imp-muted-foreground focus:outline-none focus:border-imp-primary/50\"\n />\n </div>\n\n <div>\n <label className=\"block mb-1 text-xs font-medium text-imp-foreground\">\n Password <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Min 8 characters\"\n required\n minLength={8}\n autoComplete=\"new-password\"\n className=\"w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm placeholder:text-imp-muted-foreground focus:outline-none focus:border-imp-primary/50\"\n />\n </div>\n\n {error && (\n <div className=\"p-2 text-xs text-red-600 rounded-md bg-red-50 dark:bg-red-950/20 dark:text-red-400\">\n {error}\n </div>\n )}\n\n <div className=\"flex justify-end gap-2 pt-1\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"rounded-md border border-imp-border px-3 py-1.5 text-sm hover:bg-imp-muted\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-4 py-2 text-sm font-semibold rounded-md bg-imp-primary text-imp-primary-foreground hover:bg-imp-primary/90 disabled:opacity-50\"\n >\n {isSubmitting ? 'Creating…' : 'Create User'}\n </button>\n </div>\n </form>\n );\n}\n","/**\n * AuthenticatedInvect — Wraps the Invect component with auth gating.\n *\n * Renders InvectShell → AuthProvider → AuthGate around Invect.\n * The shell establishes the `.invect` CSS scope so all theme tokens\n * work for both the sign-in page and the Invect editor.\n *\n * When the user is not authenticated, shows the sign-in page.\n * When authenticated, renders the full Invect UI.\n *\n * Sign-up is disabled — initial admin users are configured explicitly via\n * `betterAuthPlugin({ globalAdmins: [...] })`, and subsequent users are\n * created by the admin through the User Management panel.\n *\n * @example\n * ```tsx\n * import { AuthenticatedInvect } from '@invect/user-auth/ui';\n * import { Invect, InvectShell } from '@invect/frontend';\n * import '@invect/frontend/styles';\n *\n * export default function Page() {\n * return (\n * <AuthenticatedInvect\n * apiBaseUrl=\"/api/invect\"\n * basePath=\"/invect\"\n * InvectComponent={Invect}\n * ShellComponent={InvectShell}\n * />\n * );\n * }\n * ```\n */\n\nimport { type ReactNode, type ComponentType, type MemoExoticComponent } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { AuthProvider } from '../providers/AuthProvider';\nimport { AuthGate } from './AuthGate';\nimport { SignInPage } from './SignInPage';\n\n/**\n * Accepts both a plain component and a React.memo-wrapped component.\n * React.memo returns MemoExoticComponent which isn't directly assignable\n * to ComponentType in TypeScript, but is valid in JSX.\n */\ntype ComponentOrMemo<P> = ComponentType<P> | MemoExoticComponent<ComponentType<P>>;\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Generic over TPlugin so that passing a typed InvectComponent (e.g. one that\n * expects `plugins?: InvectFrontendPlugin[]`) causes TypeScript to infer the\n * correct element type for the `plugins` prop on AuthenticatedInvect itself.\n * Defaults to `unknown` for the no-plugins case.\n */\nexport interface AuthenticatedInvectProps<TPlugin = unknown> {\n /**\n * Base URL for the Invect API.\n * Used for both auth endpoints and the Invect component.\n * @example '/api/invect' or 'http://localhost:3000/invect'\n */\n apiBaseUrl?: string;\n /**\n * Base path where Invect is mounted in the browser.\n * @default '/invect'\n */\n basePath?: string;\n /**\n * The Invect component to render when authenticated.\n * Pass this to avoid a direct dependency on @invect/frontend.\n * Accepts both plain and React.memo-wrapped components.\n *\n * @example\n * ```tsx\n * import { Invect } from '@invect/frontend';\n * <AuthenticatedInvect InvectComponent={Invect} />\n * ```\n */\n InvectComponent: ComponentOrMemo<{\n apiBaseUrl?: string;\n basePath?: string;\n reactQueryClient?: QueryClient;\n plugins?: TPlugin[];\n }>;\n /**\n * The InvectShell component that provides the `.invect` CSS scope.\n * This ensures theme tokens work for both the sign-in page and the\n * Invect editor. Import from `@invect/frontend`.\n *\n * `children` is typed as `unknown` rather than `ReactNode` to avoid a\n * structural incompatibility between `@types/react@18` (used here) and\n * `@types/react@19` (used by `@invect/frontend`) where `ReactPortal`\n * changed between versions.\n *\n * If not provided, the auth UI renders without the Invect CSS scope\n * and must rely on the host app's styling.\n *\n * @example\n * ```tsx\n * import { InvectShell } from '@invect/frontend';\n * <AuthenticatedInvect ShellComponent={InvectShell} />\n * ```\n */\n ShellComponent?: ComponentOrMemo<{\n children: ReactNode;\n theme?: 'light' | 'dark' | 'system';\n className?: string;\n }>;\n /**\n * Optional React Query client. If provided, it's shared between\n * the auth provider and the Invect component.\n */\n reactQueryClient?: QueryClient;\n /**\n * Content to display while checking session status.\n */\n loading?: ReactNode;\n /**\n * Theme for the shell wrapper.\n * @default 'system'\n */\n theme?: 'light' | 'dark' | 'system';\n /**\n * Frontend plugins forwarded to InvectComponent.\n * The element type is inferred from InvectComponent's `plugins` prop type,\n * so this stays consistent with whatever component you pass.\n *\n * @example\n * ```tsx\n * import { rbacFrontendPlugin } from '@invect/rbac/ui';\n * <AuthenticatedInvect plugins={[rbacFrontendPlugin]} />\n * ```\n */\n plugins?: TPlugin[];\n}\n\n// ─────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────\n\nconst defaultQueryClient = new QueryClient({\n defaultOptions: {\n queries: { staleTime: 5 * 60 * 1000, retry: 1 },\n },\n});\n\nexport function AuthenticatedInvect<TPlugin = unknown>({\n apiBaseUrl = 'http://localhost:3000/invect',\n basePath = '/invect',\n InvectComponent,\n ShellComponent,\n reactQueryClient,\n loading,\n theme = 'light',\n plugins,\n}: AuthenticatedInvectProps<TPlugin>) {\n const client = reactQueryClient ?? defaultQueryClient;\n const Invect = InvectComponent as ComponentType<{\n apiBaseUrl?: string;\n basePath?: string;\n reactQueryClient?: QueryClient;\n plugins?: TPlugin[];\n }>;\n const Shell = ShellComponent as\n | ComponentType<{\n children?: ReactNode;\n theme?: 'light' | 'dark' | 'system';\n className?: string;\n }>\n | undefined;\n\n const content = (\n <QueryClientProvider client={client}>\n <AuthProvider baseUrl={apiBaseUrl}>\n <AuthGate loading={loading ?? <LoadingSpinner />} fallback={<SignInOnly />}>\n <Invect\n apiBaseUrl={apiBaseUrl}\n basePath={basePath}\n reactQueryClient={client}\n plugins={plugins}\n />\n </AuthGate>\n </AuthProvider>\n </QueryClientProvider>\n );\n\n // Wrap in the shell if provided — gives us the .invect CSS scope\n if (Shell) {\n return (\n <Shell theme={theme} className=\"h-full\">\n {content}\n </Shell>\n );\n }\n\n return content;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Internal: Sign-in only view (no sign-up)\n// ─────────────────────────────────────────────────────────────\n\nfunction SignInOnly() {\n return (\n <SignInPage\n onSuccess={() => {\n // Auth state change will cause AuthGate to re-render with children\n }}\n subtitle=\"Sign in to access Invect\"\n />\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Internal: Loading spinner\n// ─────────────────────────────────────────────────────────────\n\nfunction LoadingSpinner() {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-imp-background\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-imp-muted border-t-imp-primary\" />\n </div>\n );\n}\n","/**\n * UserManagementPage — Standalone page for user management.\n *\n * Wraps the existing UserManagement component in a page layout\n * consistent with the Access Control page style. Registered as a\n * plugin route contribution at '/users'.\n */\n\nimport { Users } from 'lucide-react';\nimport { useApiClient, PageLayout } from '@invect/frontend';\nimport { UserManagement } from './UserManagement';\nimport { useAuth } from '../providers/AuthProvider';\n\nexport function UserManagementPage() {\n const api = useApiClient();\n const { user, isAuthenticated } = useAuth();\n const apiBaseUrl = api.getBaseURL();\n\n if (!isAuthenticated) {\n return (\n <div className=\"imp-page w-full h-full min-h-0 overflow-y-auto bg-imp-background text-imp-foreground flex items-center justify-center\">\n <p className=\"text-sm text-imp-muted-foreground\">Please sign in to access this page.</p>\n </div>\n );\n }\n\n if (user?.role !== 'admin') {\n return (\n <PageLayout\n title=\"User Management\"\n subtitle=\"Manage users for your Invect instance.\"\n icon={Users}\n >\n <div className=\"rounded-md bg-yellow-50 p-3 text-sm text-yellow-800 dark:bg-yellow-950/20 dark:text-yellow-400\">\n Only administrators can manage users. Contact an admin for access.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"User Management\"\n subtitle=\"Create, manage, and remove users for your Invect instance.\"\n icon={Users}\n >\n <UserManagement apiBaseUrl={apiBaseUrl} />\n </PageLayout>\n );\n}\n","/**\n * ProfilePage — Standalone page for the current authenticated user.\n *\n * Shows basic account information and provides a sign-out action.\n */\n\nimport { LogOut, Mail, Shield, User as UserIcon } from 'lucide-react';\nimport { PageLayout } from '@invect/frontend';\nimport { useAuth } from '../providers/AuthProvider';\nimport { formatAuthRoleLabel } from '../../shared/roles';\n\nexport interface ProfilePageProps {\n basePath: string;\n}\n\nexport function ProfilePage({ basePath }: ProfilePageProps) {\n void basePath;\n const { user, isAuthenticated, isLoading, signOut } = useAuth();\n\n if (isLoading) {\n return (\n <div className=\"imp-page flex h-full min-h-0 w-full items-center justify-center overflow-y-auto bg-imp-background text-imp-foreground\">\n <p className=\"text-sm text-imp-muted-foreground\">Loading profile…</p>\n </div>\n );\n }\n\n if (!isAuthenticated || !user) {\n return (\n <div className=\"imp-page flex h-full min-h-0 w-full items-center justify-center overflow-y-auto bg-imp-background text-imp-foreground\">\n <p className=\"text-sm text-imp-muted-foreground\">Please sign in to view your profile.</p>\n </div>\n );\n }\n\n const displayName = user.name ?? user.email ?? user.id;\n const initials = displayName[0]?.toUpperCase() ?? '?';\n\n return (\n <PageLayout\n title=\"Profile\"\n subtitle=\"View your account details and manage your current session.\"\n icon={UserIcon}\n >\n <div className=\"max-w-2xl rounded-xl border border-imp-border bg-imp-card p-6 shadow-sm\">\n <div className=\"mb-6 flex items-center gap-4\">\n <div className=\"flex h-16 w-16 shrink-0 items-center justify-center overflow-hidden rounded-full bg-imp-primary/10 text-lg font-semibold text-imp-primary\">\n {user.image ? (\n <img src={user.image} alt={displayName} className=\"h-16 w-16 object-cover\" />\n ) : (\n initials\n )}\n </div>\n\n <div className=\"min-w-0\">\n <p className=\"truncate text-lg font-semibold text-imp-foreground\">{displayName}</p>\n <p className=\"truncate text-sm text-imp-muted-foreground\">{user.email ?? user.id}</p>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"rounded-lg border border-imp-border bg-imp-background p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-imp-foreground\">\n <UserIcon className=\"h-4 w-4 text-imp-muted-foreground\" />\n Name\n </div>\n <p className=\"text-sm text-imp-muted-foreground\">{user.name ?? 'Not set'}</p>\n </div>\n\n <div className=\"rounded-lg border border-imp-border bg-imp-background p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-imp-foreground\">\n <Mail className=\"h-4 w-4 text-imp-muted-foreground\" />\n Email\n </div>\n <p className=\"text-sm text-imp-muted-foreground\">{user.email ?? 'Not available'}</p>\n </div>\n\n <div className=\"rounded-lg border border-imp-border bg-imp-background p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-imp-foreground\">\n <Shield className=\"h-4 w-4 text-imp-muted-foreground\" />\n Role\n </div>\n <p className=\"text-sm text-imp-muted-foreground\">{formatAuthRoleLabel(user.role)}</p>\n </div>\n\n <div className=\"rounded-lg border border-imp-border bg-imp-background p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-imp-foreground\">\n <UserIcon className=\"h-4 w-4 text-imp-muted-foreground\" />\n User ID\n </div>\n <p className=\"break-all text-sm text-imp-muted-foreground\">{user.id}</p>\n </div>\n </div>\n\n <div className=\"mt-6 flex justify-end border-t border-imp-border pt-4\">\n <button\n onClick={async () => {\n await signOut();\n }}\n className=\"inline-flex items-center gap-2 rounded-md border border-imp-border px-4 py-2 text-sm font-medium text-imp-foreground transition-colors hover:bg-imp-muted\"\n >\n <LogOut className=\"h-4 w-4\" />\n Sign Out\n </button>\n </div>\n </div>\n </PageLayout>\n );\n}\n","/**\n * SidebarUserMenu — User avatar link in the sidebar footer.\n *\n * Clicking navigates directly to the profile page.\n * Sign-out is available on the profile page itself.\n */\n\nimport { Link, useLocation } from 'react-router';\nimport { useAuth } from '../providers/AuthProvider';\n\nexport interface SidebarUserMenuProps {\n collapsed?: boolean;\n basePath?: string;\n}\n\nexport function SidebarUserMenu({ collapsed = false, basePath = '' }: SidebarUserMenuProps) {\n const { user, isAuthenticated, isLoading } = useAuth();\n const location = useLocation();\n\n if (isLoading || !isAuthenticated || !user) {\n return null;\n }\n\n const initials = (user.name ?? user.email ?? user.id)[0]?.toUpperCase() ?? '?';\n const displayName = user.name ?? user.email ?? 'User';\n const profilePath = `${basePath}/profile`;\n const isActive = location.pathname === profilePath;\n\n return (\n <Link\n to={profilePath}\n title={`${displayName}${user.role ? ` — ${user.role}` : ''}`}\n className={[\n 'flex w-full items-center gap-3 rounded-md px-2 py-2 transition-colors',\n 'hover:bg-imp-muted/60',\n isActive ? 'bg-imp-muted/60' : '',\n collapsed ? 'justify-center' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {/* Avatar */}\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-imp-primary/10 text-sm font-medium text-imp-primary\">\n {user.image ? (\n <img src={user.image} alt={displayName} className=\"h-8 w-8 rounded-full object-cover\" />\n ) : (\n initials\n )}\n </div>\n\n {/* Name + role (only when expanded) */}\n {!collapsed && (\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\">{displayName}</p>\n {user.role && (\n <p className=\"truncate text-xs capitalize text-imp-muted-foreground\">{user.role}</p>\n )}\n </div>\n )}\n </Link>\n );\n}\n","/**\n * @invect/user-auth — Auth Frontend Plugin Definition\n *\n * Registers the auth plugin's frontend contributions:\n * - Sidebar item: \"Users\" (admin-only)\n * - Route: /users → UserManagementPage\n *\n * Note: AuthProvider is NOT included as a plugin provider because\n * AuthenticatedInvect already wraps the tree with it. This plugin\n * only adds the user management UI.\n */\n\nimport { Users } from 'lucide-react';\nimport { ProfilePage } from '../components/ProfilePage';\nimport { UserManagementPage } from '../components/UserManagementPage';\nimport { SidebarUserMenu } from '../components/SidebarUserMenu';\nimport type { InvectFrontendPlugin } from '@invect/frontend';\n\nexport const authFrontendPlugin: InvectFrontendPlugin = {\n id: 'user-auth',\n name: 'User Authentication',\n\n // ─── Sidebar ───\n sidebar: [\n {\n label: 'Users',\n icon: Users,\n path: '/users',\n position: 'top',\n permission: 'admin:*',\n },\n ],\n\n // ─── Sidebar Footer (user avatar + sign-out menu) ───\n sidebarFooter: SidebarUserMenu,\n\n // ─── Routes ───\n routes: [\n {\n path: '/profile',\n component: ProfilePage,\n },\n {\n path: '/users',\n component: UserManagementPage,\n },\n ],\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["UserIcon"],"sources":["../../src/frontend/providers/AuthProvider.tsx","../../src/frontend/components/SignInForm.tsx","../../src/frontend/components/SignInPage.tsx","../../src/frontend/components/UserButton.tsx","../../src/frontend/components/AuthGate.tsx","../../src/frontend/components/UserManagement.tsx","../../src/frontend/components/AuthenticatedInvect.tsx","../../src/frontend/components/UserManagementPage.tsx","../../src/frontend/components/ProfilePage.tsx","../../src/frontend/components/SidebarUserMenu.tsx","../../src/frontend/plugins/authFrontendPlugin.ts"],"sourcesContent":["/**\n * AuthProvider — Context provider for authentication state.\n *\n * Fetches the current session from the better-auth proxy endpoints\n * and caches it via React Query. Provides sign-in, sign-up, and\n * sign-out actions to child components.\n */\n\nimport { createContext, useContext, useCallback, useMemo, type ReactNode } from 'react';\nimport { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';\nimport type {\n AuthSession,\n AuthUser,\n SignInCredentials,\n SignUpCredentials,\n} from '../../shared/types';\n\n// ─────────────────────────────────────────────────────────────\n// Context Types\n// ─────────────────────────────────────────────────────────────\n\nexport interface AuthContextValue {\n /** Current authenticated user, null if not signed in */\n user: AuthUser | null;\n /** Whether the user is authenticated */\n isAuthenticated: boolean;\n /** Whether the session query is still loading */\n isLoading: boolean;\n /** Sign in with email/password */\n signIn: (credentials: SignInCredentials) => Promise<void>;\n /** Sign up with email/password */\n signUp: (credentials: SignUpCredentials) => Promise<void>;\n /** Sign out the current session */\n signOut: () => Promise<void>;\n /** Whether a sign-in is in progress */\n isSigningIn: boolean;\n /** Whether a sign-up is in progress */\n isSigningUp: boolean;\n /** Last auth error, if any */\n error: string | null;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Context\n// ─────────────────────────────────────────────────────────────\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\n// ─────────────────────────────────────────────────────────────\n// Provider\n// ─────────────────────────────────────────────────────────────\n\nexport interface AuthProviderProps {\n children: ReactNode;\n /**\n * Base URL for the Invect API (e.g. 'http://localhost:3000/invect').\n * Auth endpoints are at `${baseUrl}/plugins/auth/api/auth/*`.\n */\n baseUrl: string;\n}\n\nexport function AuthProvider({ children, baseUrl }: AuthProviderProps) {\n const queryClient = useQueryClient();\n\n // Construct auth API base URL\n const authApiBase = `${baseUrl}/plugins/auth/api/auth`;\n\n // ── Session Query ──────────────────────────────────────────\n\n const { data: session, isLoading } = useQuery<AuthSession>({\n queryKey: ['auth', 'session'],\n queryFn: async () => {\n const response = await fetch(`${authApiBase}/get-session`, {\n credentials: 'include',\n });\n if (!response.ok) {\n return { user: null as unknown as AuthUser, isAuthenticated: false };\n }\n const data = await response.json();\n if (!data?.user) {\n return { user: null as unknown as AuthUser, isAuthenticated: false };\n }\n return {\n user: {\n id: data.user.id,\n name: data.user.name ?? undefined,\n email: data.user.email ?? undefined,\n image: data.user.image ?? undefined,\n role: data.user.role ?? undefined,\n },\n isAuthenticated: true,\n };\n },\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 1,\n });\n\n // ── Sign In ────────────────────────────────────────────────\n\n const signInMutation = useMutation({\n mutationFn: async (credentials: SignInCredentials) => {\n const response = await fetch(`${authApiBase}/sign-in/email`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(credentials),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({ message: 'Sign in failed' }));\n throw new Error(err.message || `Sign in failed (${response.status})`);\n }\n return response.json();\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['auth', 'session'] });\n },\n });\n\n // ── Sign Up ────────────────────────────────────────────────\n\n const signUpMutation = useMutation({\n mutationFn: async (credentials: SignUpCredentials) => {\n const response = await fetch(`${authApiBase}/sign-up/email`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(credentials),\n });\n if (!response.ok) {\n const err = await response.json().catch(() => ({ message: 'Sign up failed' }));\n throw new Error(err.message || `Sign up failed (${response.status})`);\n }\n return response.json();\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['auth', 'session'] });\n },\n });\n\n // ── Sign Out ───────────────────────────────────────────────\n\n const signOutMutation = useMutation({\n mutationFn: async () => {\n const response = await fetch(`${authApiBase}/sign-out`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({}),\n });\n if (!response.ok) {\n const err = await response.json().catch(async () => ({\n message: (await response.text().catch(() => '')) || 'Sign out failed',\n }));\n throw new Error(err.message || `Sign out failed (${response.status})`);\n }\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['auth', 'session'] });\n },\n });\n\n // ── Callbacks ──────────────────────────────────────────────\n\n const signIn = useCallback(\n async (credentials: SignInCredentials) => {\n await signInMutation.mutateAsync(credentials);\n },\n [signInMutation],\n );\n\n const signUp = useCallback(\n async (credentials: SignUpCredentials) => {\n await signUpMutation.mutateAsync(credentials);\n },\n [signUpMutation],\n );\n\n const signOut = useCallback(async () => {\n await signOutMutation.mutateAsync();\n }, [signOutMutation]);\n\n // ── Error ──────────────────────────────────────────────────\n\n const error =\n signInMutation.error?.message ??\n signUpMutation.error?.message ??\n signOutMutation.error?.message ??\n null;\n\n // ── Context Value ──────────────────────────────────────────\n\n const value = useMemo<AuthContextValue>(\n () => ({\n user: session?.isAuthenticated ? session.user : null,\n isAuthenticated: session?.isAuthenticated ?? false,\n isLoading,\n signIn,\n signUp,\n signOut,\n isSigningIn: signInMutation.isPending,\n isSigningUp: signUpMutation.isPending,\n error,\n }),\n [\n session,\n isLoading,\n signIn,\n signUp,\n signOut,\n signInMutation.isPending,\n signUpMutation.isPending,\n error,\n ],\n );\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Hook\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Access auth context — current user, sign-in/sign-up/sign-out actions.\n *\n * Must be used within an `<AuthProvider>`.\n * Returns a safe fallback (unauthenticated) if provider is missing.\n */\nexport function useAuth(): AuthContextValue {\n const ctx = useContext(AuthContext);\n\n if (!ctx) {\n // Graceful fallback\n return {\n user: null,\n isAuthenticated: false,\n isLoading: false,\n signIn: async () => {\n throw new Error('AuthProvider not found');\n },\n signUp: async () => {\n throw new Error('AuthProvider not found');\n },\n signOut: async () => {\n throw new Error('AuthProvider not found');\n },\n isSigningIn: false,\n isSigningUp: false,\n error: null,\n };\n }\n\n return ctx;\n}\n","/**\n * SignInForm — Email/password sign-in form component.\n *\n * Uses the AuthProvider's signIn action. Styled to match the Invect\n * design system with grouped fields, clean labels, and themed inputs.\n */\n\nimport { useState, type FormEvent } from 'react';\nimport { useAuth } from '../providers/AuthProvider';\n\nexport interface SignInFormProps {\n /** Called after successful sign-in */\n onSuccess?: () => void;\n /** Additional CSS class names */\n className?: string;\n}\n\nexport function SignInForm({ onSuccess, className }: SignInFormProps) {\n const { signIn, isSigningIn, error } = useAuth();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [localError, setLocalError] = useState<string | null>(null);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setLocalError(null);\n\n if (!email.trim() || !password.trim()) {\n setLocalError('Email and password are required');\n return;\n }\n\n try {\n await signIn({ email, password });\n onSuccess?.();\n } catch (err) {\n setLocalError(err instanceof Error ? err.message : 'Sign in failed');\n }\n };\n\n const displayError = localError ?? error;\n\n return (\n <form onSubmit={handleSubmit} className={className}>\n <div className=\"flex flex-col gap-6\">\n {/* Email field */}\n <div className=\"grid gap-2\">\n <label htmlFor=\"auth-signin-email\" className=\"text-sm font-medium leading-none\">\n Email\n </label>\n <input\n id=\"auth-signin-email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"you@example.com\"\n autoComplete=\"email\"\n required\n className=\"flex h-9 w-full rounded-md border border-imp-border bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-imp-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-imp-ring disabled:cursor-not-allowed disabled:opacity-50\"\n />\n </div>\n\n {/* Password field */}\n <div className=\"grid gap-2\">\n <label htmlFor=\"auth-signin-password\" className=\"text-sm font-medium leading-none\">\n Password\n </label>\n <input\n id=\"auth-signin-password\"\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"••••••••\"\n autoComplete=\"current-password\"\n required\n className=\"flex h-9 w-full rounded-md border border-imp-border bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-imp-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-imp-ring disabled:cursor-not-allowed disabled:opacity-50\"\n />\n </div>\n\n {/* Error message */}\n {displayError && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900/50 dark:bg-red-950/20 dark:text-red-400\">\n {displayError}\n </div>\n )}\n\n {/* Submit */}\n <button\n type=\"submit\"\n disabled={isSigningIn}\n className=\"inline-flex h-9 w-full items-center justify-center gap-2 whitespace-nowrap rounded-md bg-imp-foreground px-4 py-2 text-sm font-medium text-imp-background shadow transition-colors hover:bg-imp-foreground/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-imp-ring disabled:pointer-events-none disabled:opacity-50\"\n >\n {isSigningIn ? 'Signing in…' : 'Login'}\n </button>\n </div>\n </form>\n );\n}\n","/**\n * SignInPage — Full-page sign-in component with layout.\n *\n * Renders the SignInForm centered on the page with a logo, title,\n * and grouped fields matching the Invect design system.\n * Sign-up is not offered — new users are created by admins.\n */\n\nimport { SignInForm } from './SignInForm';\n\nexport interface SignInPageProps {\n /** Called after successful sign-in */\n onSuccess?: () => void;\n /** Called when user clicks \"Sign Up\" link (optional — hidden if omitted) */\n onNavigateToSignUp?: () => void;\n /** Page title */\n title?: string;\n /** Page subtitle */\n subtitle?: string;\n}\n\nexport function SignInPage({\n onSuccess,\n onNavigateToSignUp,\n title = 'Welcome back',\n subtitle = 'Sign in to your account to continue',\n}: SignInPageProps) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-imp-background p-4 text-imp-foreground\">\n <div className=\"flex w-full max-w-sm flex-col gap-6\">\n {/* Header: logo + title + subtitle */}\n <div className=\"flex flex-col items-center gap-2 text-center\">\n <div className=\"flex items-center justify-center\">\n <ImpLogo className=\"h-10 w-auto\" />\n </div>\n <h1 className=\"text-xl font-bold\">{title}</h1>\n <p className=\"text-sm text-imp-muted-foreground\">{subtitle}</p>\n </div>\n\n {/* Form */}\n <SignInForm onSuccess={onSuccess} />\n\n {/* Separator + admin note */}\n <div className=\"relative text-center text-sm\">\n <div className=\"absolute inset-0 top-1/2 border-t border-imp-border\" />\n <span className=\"relative bg-imp-background px-2 text-imp-muted-foreground\">\n Admin-managed accounts\n </span>\n </div>\n\n <p className=\"px-6 text-center text-xs text-imp-muted-foreground\">\n New accounts are created by your administrator.\n {onNavigateToSignUp ? ' Or ' : ' Contact your admin if you need access.'}\n {onNavigateToSignUp && (\n <button\n type=\"button\"\n onClick={onNavigateToSignUp}\n className=\"font-medium underline underline-offset-4 hover:text-imp-foreground\"\n >\n Sign up\n </button>\n )}\n </p>\n </div>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Internal: Imperat logo\n// ─────────────────────────────────────────────────────────────\n\nfunction ImpLogo({ className }: { className?: string }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 109 209\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"9\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n >\n <path d=\"M31.7735 104.5L4.50073 18.7859L54.5007 33.0716L31.7735 104.5Z\" />\n <path d=\"M54.5007 4.5L104.501 18.7857L54.5007 33.0714L4.50073 18.7857L54.5007 4.5Z\" />\n <path d=\"M4.50073 190.214L54.5007 33.0716L104.501 18.7859L4.50073 190.214Z\" />\n <path d=\"M54.5007 204.5L81.4238 104.5L104.501 18.7859L4.50073 190.214L54.5007 204.5Z\" />\n <path d=\"M54.5007 204.5L81.4238 104.5L104.501 190.214L54.5007 204.5Z\" />\n </svg>\n );\n}\n","/**\n * UserButton — Compact user avatar + dropdown for the authenticated user.\n *\n * Shows the user's avatar/initials when signed in, with a dropdown\n * containing their name, email, and sign-out button.\n * Shows a \"Sign In\" button when not authenticated.\n */\n\nimport { useState, useRef, useEffect } from 'react';\nimport { LogOut, User } from 'lucide-react';\nimport { useAuth } from '../providers/AuthProvider';\nimport { formatAuthRoleLabel } from '../../shared/roles';\n\nexport interface UserButtonProps {\n /** Called when the sign-in button is clicked (unauthenticated state) */\n onSignInClick?: () => void;\n /** Additional CSS class names */\n className?: string;\n}\n\nexport function UserButton({ onSignInClick, className }: UserButtonProps) {\n const { user, isAuthenticated, isLoading, signOut } = useAuth();\n const [isOpen, setIsOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n const handleClick = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setIsOpen(false);\n }\n };\n if (isOpen) {\n document.addEventListener('mousedown', handleClick);\n }\n return () => document.removeEventListener('mousedown', handleClick);\n }, [isOpen]);\n\n if (isLoading) {\n return <div className={`h-8 w-8 animate-pulse rounded-full bg-imp-muted ${className ?? ''}`} />;\n }\n\n if (!isAuthenticated || !user) {\n return (\n <button\n onClick={onSignInClick}\n className={`inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium text-imp-foreground hover:bg-imp-muted ${className ?? ''}`}\n >\n <User className=\"h-4 w-4\" />\n Sign In\n </button>\n );\n }\n\n const initials = (user.name ?? user.email ?? user.id)[0]?.toUpperCase() ?? '?';\n\n return (\n <div ref={ref} className={`relative ${className ?? ''}`}>\n {/* Avatar trigger */}\n <button\n onClick={() => setIsOpen(!isOpen)}\n className=\"flex h-8 w-8 items-center justify-center rounded-full bg-imp-primary/10 text-sm font-medium text-imp-primary hover:bg-imp-primary/20 transition-colors\"\n title={user.name ?? user.email ?? user.id}\n >\n {user.image ? (\n <img\n src={user.image}\n alt={user.name ?? ''}\n className=\"h-8 w-8 rounded-full object-cover\"\n />\n ) : (\n initials\n )}\n </button>\n\n {/* Dropdown */}\n {isOpen && (\n <div className=\"absolute right-0 top-full z-50 mt-2 w-56 rounded-lg border border-imp-border bg-imp-background shadow-lg\">\n {/* User info */}\n <div className=\"border-b border-imp-border px-4 py-3\">\n <p className=\"truncate text-sm font-medium\">{user.name ?? 'User'}</p>\n {user.email && (\n <p className=\"truncate text-xs text-imp-muted-foreground\">{user.email}</p>\n )}\n {user.role && (\n <span className=\"mt-1 inline-block rounded-full bg-imp-muted px-2 py-0.5 text-xs font-medium\">\n {formatAuthRoleLabel(user.role)}\n </span>\n )}\n </div>\n\n {/* Actions */}\n <div className=\"p-1\">\n <button\n onClick={async () => {\n setIsOpen(false);\n await signOut();\n }}\n className=\"flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm text-imp-foreground hover:bg-imp-muted\"\n >\n <LogOut className=\"h-4 w-4\" />\n Sign Out\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n","/**\n * AuthGate — Conditionally renders children based on auth state.\n *\n * Useful for protecting routes or showing different content for\n * authenticated vs unauthenticated users.\n */\n\nimport type { ReactNode } from 'react';\nimport { useAuth } from '../providers/AuthProvider';\n\nexport interface AuthGateProps {\n /** Content to show when authenticated */\n children: ReactNode;\n /** Content to show when NOT authenticated (defaults to null) */\n fallback?: ReactNode;\n /** Content to show while loading (defaults to null) */\n loading?: ReactNode;\n}\n\nexport function AuthGate({ children, fallback = null, loading = null }: AuthGateProps) {\n const { isAuthenticated, isLoading } = useAuth();\n\n if (isLoading) {\n return <>{loading}</>;\n }\n\n if (!isAuthenticated) {\n return <>{fallback}</>;\n }\n\n return <>{children}</>;\n}\n","/**\n * UserManagement — Admin panel for managing users.\n *\n * Displays a list of users with the ability to:\n * - Create new users (email/password/role)\n * - Change user roles\n * - Delete users\n *\n * Only visible to admin users. Uses the auth plugin's\n * `/plugins/auth/users` endpoints.\n */\n\nimport { useState, useMemo, useCallback, type FormEvent } from 'react';\nimport {\n ArrowDown,\n ArrowUp,\n ChevronsUpDown,\n ChevronDown,\n Search,\n Trash2,\n UserPlus,\n} from 'lucide-react';\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@invect/frontend';\nimport { useAuth } from '../providers/AuthProvider';\nimport {\n AUTH_ADMIN_ROLE,\n AUTH_ASSIGNABLE_ROLES,\n AUTH_DEFAULT_ROLE,\n formatAuthRoleLabel,\n isAuthAssignableRole,\n} from '../../shared/roles';\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\ninterface ManagedUser {\n id: string;\n name?: string;\n email?: string;\n role?: string;\n createdAt?: string;\n}\n\nexport interface UserManagementProps {\n /** Base URL for the Invect API (same as AuthProvider's baseUrl) */\n apiBaseUrl: string;\n /** Additional CSS class names */\n className?: string;\n}\n\nconst ASSIGNABLE_ROLE_OPTIONS: Array<{\n value: (typeof AUTH_ASSIGNABLE_ROLES)[number];\n label: string;\n description: string;\n}> = [\n {\n value: AUTH_DEFAULT_ROLE,\n label: 'None',\n description: 'No global access; flow access can still be granted via RBAC.',\n },\n { value: 'owner', label: 'Owner', description: 'Can edit and manage sharing for all flows.' },\n { value: 'editor', label: 'Editor', description: 'Can inspect, run, and edit flows.' },\n { value: 'operator', label: 'Operator', description: 'Can inspect and run flows.' },\n { value: 'viewer', label: 'Viewer', description: 'Can inspect flows.' },\n];\n\nconst ROLE_BADGE_CLASSES = 'border-imp-border bg-imp-muted/50 text-imp-foreground';\n\nfunction getInitials(user: ManagedUser): string {\n if (user.name) {\n const parts = user.name.trim().split(/\\s+/);\n if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n return parts[0][0].toUpperCase();\n }\n if (user.email) return user.email[0].toUpperCase();\n return '?';\n}\n\nfunction formatDate(iso: string): string {\n try {\n return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(new Date(iso));\n } catch {\n return iso;\n }\n}\n\n// ─────────────────────────────────────────────────────────────\n// Sortable column header\n// ─────────────────────────────────────────────────────────────\n\nfunction SortHeader({\n label,\n field,\n sortField,\n sortDir,\n onSort,\n align = 'left',\n}: {\n label: string;\n field: 'name' | 'createdAt' | 'role';\n sortField: 'name' | 'createdAt' | 'role';\n sortDir: 'asc' | 'desc';\n onSort: (field: 'name' | 'createdAt' | 'role') => void;\n align?: 'left' | 'right';\n}) {\n const active = sortField === field;\n const Icon = active ? (sortDir === 'asc' ? ArrowUp : ArrowDown) : ChevronsUpDown;\n return (\n <th className={`px-4 py-2.5 text-${align} font-medium`}>\n <button\n type=\"button\"\n onClick={() => onSort(field)}\n className={`inline-flex items-center gap-1 rounded transition-colors hover:text-imp-foreground ${active ? 'text-imp-foreground' : ''}`}\n >\n {label}\n <Icon className=\"w-3 h-3 shrink-0\" />\n </button>\n </th>\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Role Dropdown\n// ─────────────────────────────────────────────────────────────\n\nfunction RoleDropdown({\n value,\n userId,\n disabled,\n onChange,\n}: {\n value: string;\n userId: string;\n disabled: boolean;\n onChange: (userId: string, role: string) => void;\n}) {\n const current = isAuthAssignableRole(value) ? value : AUTH_DEFAULT_ROLE;\n const currentLabel = ASSIGNABLE_ROLE_OPTIONS.find((o) => o.value === current)?.label ?? current;\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild disabled={disabled}>\n <button\n type=\"button\"\n className={`inline-flex w-28 items-center justify-between gap-1.5 rounded-full border px-2.5 py-0.5 text-sm font-medium transition-colors hover:bg-imp-muted disabled:cursor-not-allowed disabled:opacity-50 ${ROLE_BADGE_CLASSES}`}\n >\n {currentLabel}\n {!disabled && <ChevronDown className=\"w-3 h-3 shrink-0\" />}\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-56\">\n <DropdownMenuLabel className=\"text-xs\">Set role</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {ASSIGNABLE_ROLE_OPTIONS.map((option) => (\n <DropdownMenuItem\n key={option.value}\n onSelect={() => onChange(userId, option.value)}\n className={`items-start gap-0 px-2 py-2 ${current === option.value ? 'bg-accent text-accent-foreground' : ''}`}\n >\n <div className=\"min-w-0 text-left\">\n <div className=\"text-sm font-medium\">{option.label}</div>\n <div className=\"text-xs text-muted-foreground\">{option.description}</div>\n </div>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────\n\nexport function UserManagement({ apiBaseUrl, className }: UserManagementProps) {\n const { user, isAuthenticated } = useAuth();\n const [users, setUsers] = useState<ManagedUser[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [showCreateDialog, setShowCreateDialog] = useState(false);\n const [pendingDeleteUser, setPendingDeleteUser] = useState<ManagedUser | null>(null);\n const [hasFetched, setHasFetched] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n const [sortField, setSortField] = useState<'name' | 'createdAt' | 'role'>('name');\n const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');\n\n const PAGE_SIZE = 10;\n\n const handleSort = useCallback((field: 'name' | 'createdAt' | 'role') => {\n setSortField((prev) => {\n if (prev === field) {\n setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));\n return prev;\n }\n setSortDir('asc');\n return field;\n });\n setCurrentPage(1);\n }, []);\n\n const filteredUsers = useMemo(() => {\n let result = users;\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (u) =>\n u.name?.toLowerCase().includes(q) ||\n u.email?.toLowerCase().includes(q) ||\n u.role?.toLowerCase().includes(q),\n );\n }\n return [...result].sort((a, b) => {\n let aVal = '';\n let bVal = '';\n if (sortField === 'name') {\n aVal = (a.name || a.email || '').toLowerCase();\n bVal = (b.name || b.email || '').toLowerCase();\n } else if (sortField === 'createdAt') {\n aVal = a.createdAt ?? '';\n bVal = b.createdAt ?? '';\n } else if (sortField === 'role') {\n aVal = (a.role ?? '').toLowerCase();\n bVal = (b.role ?? '').toLowerCase();\n }\n const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;\n return sortDir === 'asc' ? cmp : -cmp;\n });\n }, [users, searchQuery, sortField, sortDir]);\n\n const totalPages = Math.max(1, Math.ceil(filteredUsers.length / PAGE_SIZE));\n const paginatedUsers = filteredUsers.slice(\n (currentPage - 1) * PAGE_SIZE,\n currentPage * PAGE_SIZE,\n );\n\n const authApiBase = `${apiBaseUrl}/plugins/auth`;\n\n // ── Fetch Users ────────────────────────────────────────────\n\n const fetchUsers = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const res = await fetch(`${authApiBase}/users`, { credentials: 'include' });\n if (!res.ok) {\n const data = await res.json().catch(() => ({ error: 'Failed to fetch users' }));\n throw new Error(data.error || data.message || `HTTP ${res.status}`);\n }\n const data = await res.json();\n setUsers(data.users ?? []);\n setHasFetched(true);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to fetch users');\n } finally {\n setIsLoading(false);\n }\n }, [authApiBase]);\n\n // ── Delete User ────────────────────────────────────────────\n\n const deleteUser = useCallback(\n async (userId: string) => {\n try {\n const res = await fetch(`${authApiBase}/users/${userId}`, {\n method: 'DELETE',\n credentials: 'include',\n });\n if (!res.ok) {\n const data = await res.json().catch(() => ({}));\n throw new Error(data.error || `HTTP ${res.status}`);\n }\n setUsers((prev) => prev.filter((u) => u.id !== userId));\n setPendingDeleteUser(null);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to delete user');\n setPendingDeleteUser(null);\n }\n },\n [authApiBase],\n );\n\n // ── Update Role ────────────────────────────────────────────\n\n const updateRole = useCallback(\n async (userId: string, role: string) => {\n try {\n const res = await fetch(`${authApiBase}/users/${userId}/role`, {\n method: 'PATCH',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ role }),\n });\n if (!res.ok) {\n const data = await res.json().catch(() => ({}));\n throw new Error(data.error || `HTTP ${res.status}`);\n }\n setUsers((prev) => prev.map((u) => (u.id === userId ? { ...u, role } : u)));\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update role');\n }\n },\n [authApiBase],\n );\n\n // Guard: only admins see this\n if (!isAuthenticated || user?.role !== 'admin') {\n return null;\n }\n\n // Auto-fetch on first render\n if (!hasFetched && !isLoading) {\n fetchUsers();\n }\n\n return (\n <div className={`space-y-4 ${className ?? ''}`}>\n {/* Search + Create User */}\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1 max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 pointer-events-none text-imp-muted-foreground\" />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setCurrentPage(1);\n }}\n placeholder=\"Search users…\"\n className=\"w-full py-2 pr-3 text-sm border rounded-lg outline-none border-imp-border bg-transparent pl-9 placeholder:text-imp-muted-foreground focus:border-imp-primary/50\"\n />\n </div>\n <button\n type=\"button\"\n onClick={() => setShowCreateDialog(true)}\n className=\"flex items-center gap-1.5 rounded-lg border border-imp-border px-3 py-2 text-sm font-medium text-imp-muted-foreground transition-colors hover:border-imp-primary/50 hover:text-imp-foreground\"\n >\n <UserPlus className=\"w-4 h-4\" /> Create User\n </button>\n </div>\n\n {error && (\n <div className=\"p-3 text-sm text-red-600 rounded-md bg-red-50 dark:bg-red-950/20 dark:text-red-400\">\n {error}\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"ml-2 underline hover:no-underline\"\n >\n Dismiss\n </button>\n </div>\n )}\n\n {/* Users Table */}\n <div className=\"overflow-hidden border rounded-xl border-imp-border bg-imp-background/40\">\n <table className=\"w-full text-sm table-fixed\">\n <colgroup>\n <col className=\"w-[40%]\" />\n <col className=\"w-[20%]\" />\n <col className=\"w-[20%]\" />\n <col className=\"w-[20%]\" />\n </colgroup>\n <thead>\n <tr className=\"text-xs font-medium border-b border-imp-border bg-imp-muted/20 text-imp-muted-foreground\">\n <SortHeader\n label=\"User\"\n field=\"name\"\n sortField={sortField}\n sortDir={sortDir}\n onSort={handleSort}\n />\n <SortHeader\n label=\"Global Role\"\n field=\"role\"\n sortField={sortField}\n sortDir={sortDir}\n onSort={handleSort}\n />\n <SortHeader\n label=\"Created\"\n field=\"createdAt\"\n sortField={sortField}\n sortDir={sortDir}\n onSort={handleSort}\n align=\"right\"\n />\n <th className=\"px-4 py-2.5 text-right font-medium\"></th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-imp-border\">\n {paginatedUsers.length === 0 && (\n <tr>\n <td colSpan={4} className=\"px-4 py-8 text-sm text-center text-imp-muted-foreground\">\n {!hasFetched || isLoading\n ? 'Loading…'\n : searchQuery\n ? 'No users match your search.'\n : 'No users found.'}\n </td>\n </tr>\n )}\n {paginatedUsers.map((u) => (\n <tr key={u.id} className=\"group hover:bg-imp-muted/20\">\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center w-8 h-8 text-xs font-semibold rounded-full shrink-0 bg-imp-primary/10 text-imp-primary\">\n {getInitials(u)}\n </div>\n <div className=\"min-w-0\">\n <div className=\"font-medium truncate text-imp-foreground\">\n {u.name || 'Unnamed'}\n </div>\n <div className=\"text-xs truncate text-imp-muted-foreground\">{u.email}</div>\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n {u.role === AUTH_ADMIN_ROLE ? (\n <span\n className={`inline-flex rounded-full border px-2.5 py-0.5 text-sm font-medium ${ROLE_BADGE_CLASSES}`}\n >\n {formatAuthRoleLabel(u.role)}\n </span>\n ) : (\n <RoleDropdown\n value={u.role ?? AUTH_DEFAULT_ROLE}\n userId={u.id}\n disabled={u.id === user?.id}\n onChange={updateRole}\n />\n )}\n </td>\n <td className=\"px-4 py-3 text-xs text-right text-imp-muted-foreground\">\n {u.createdAt ? formatDate(u.createdAt) : '—'}\n </td>\n <td className=\"px-4 py-3 text-right\">\n {u.role === AUTH_ADMIN_ROLE ? (\n <span className=\"text-xs text-imp-muted-foreground\">Config managed</span>\n ) : u.id !== user?.id ? (\n <button\n type=\"button\"\n onClick={() => setPendingDeleteUser(u)}\n className=\"rounded-md p-1.5 text-imp-muted-foreground opacity-0 transition-opacity hover:bg-red-50 hover:text-red-600 group-hover:opacity-100 dark:hover:bg-red-950/20 dark:hover:text-red-400\"\n >\n <Trash2 className=\"w-4 h-4\" />\n </button>\n ) : null}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n {/* Pagination */}\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-xs text-imp-muted-foreground\">\n {filteredUsers.length} user{filteredUsers.length !== 1 ? 's' : ''}\n {searchQuery && ` matching \"${searchQuery}\"`}\n </span>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}\n disabled={currentPage === 1}\n className=\"rounded-md border border-imp-border px-2.5 py-1 text-xs hover:bg-imp-muted disabled:opacity-50\"\n >\n Previous\n </button>\n <span className=\"text-xs text-imp-muted-foreground\">\n {currentPage} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}\n disabled={currentPage === totalPages}\n className=\"rounded-md border border-imp-border px-2.5 py-1 text-xs hover:bg-imp-muted disabled:opacity-50\"\n >\n Next\n </button>\n </div>\n </div>\n\n {/* Create User Dialog */}\n <Dialog open={showCreateDialog} onOpenChange={(open) => !open && setShowCreateDialog(false)}>\n <DialogContent className=\"max-w-md border-imp-border bg-imp-background text-imp-foreground sm:max-w-md\">\n <DialogHeader>\n <DialogTitle className=\"text-sm font-semibold\">Create New User</DialogTitle>\n </DialogHeader>\n <CreateUserForm\n apiBaseUrl={authApiBase}\n onCreated={(newUser) => {\n setUsers((prev) => [...prev, newUser]);\n setShowCreateDialog(false);\n }}\n onCancel={() => setShowCreateDialog(false)}\n />\n </DialogContent>\n </Dialog>\n\n {/* Delete Confirmation Dialog */}\n <Dialog\n open={pendingDeleteUser !== null}\n onOpenChange={(open) => !open && setPendingDeleteUser(null)}\n >\n <DialogContent className=\"max-w-sm border-imp-border bg-imp-background text-imp-foreground sm:max-w-sm\">\n <DialogHeader>\n <DialogTitle className=\"text-sm font-semibold\">Delete user</DialogTitle>\n </DialogHeader>\n <p className=\"text-sm text-imp-muted-foreground\">\n Are you sure you want to delete{' '}\n <span className=\"font-medium text-imp-foreground\">\n {pendingDeleteUser?.name || pendingDeleteUser?.email || 'this user'}\n </span>\n ? This action cannot be undone.\n </p>\n <DialogFooter className=\"gap-2\">\n <button\n type=\"button\"\n onClick={() => setPendingDeleteUser(null)}\n className=\"rounded-md border border-imp-border px-3 py-1.5 text-sm hover:bg-imp-muted\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => pendingDeleteUser && deleteUser(pendingDeleteUser.id)}\n className=\"rounded-md bg-red-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-red-700\"\n >\n Delete\n </button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Internal: Create User Form\n// ─────────────────────────────────────────────────────────────\n\nfunction CreateUserForm({\n apiBaseUrl,\n onCreated,\n onCancel,\n}: {\n apiBaseUrl: string;\n onCreated: (user: ManagedUser) => void;\n onCancel: () => void;\n}) {\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [name, setName] = useState('');\n const [role, setRole] = useState<string>(AUTH_DEFAULT_ROLE);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!email.trim() || !password.trim()) {\n setError('Email and password are required');\n return;\n }\n if (password.length < 8) {\n setError('Password must be at least 8 characters');\n return;\n }\n\n setIsSubmitting(true);\n try {\n const res = await fetch(`${apiBaseUrl}/users`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ email, password, name: name.trim() || undefined, role }),\n });\n const data = await res.json();\n if (!res.ok) {\n throw new Error(data.error || data.message || `HTTP ${res.status}`);\n }\n onCreated(data.user);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create user');\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block mb-1 text-xs font-medium text-imp-foreground\">Name</label>\n <input\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"User name\"\n className=\"w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm placeholder:text-imp-muted-foreground focus:outline-none focus:border-imp-primary/50\"\n />\n </div>\n <div>\n <label className=\"block mb-1 text-xs font-medium text-imp-foreground\">Role</label>\n <select\n value={role}\n onChange={(e) => setRole(e.target.value)}\n className=\"w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm focus:outline-none focus:border-imp-primary/50\"\n >\n {ASSIGNABLE_ROLE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-imp-muted-foreground\">\n Flow access can still be granted via RBAC.\n </p>\n </div>\n </div>\n\n <div>\n <label className=\"block mb-1 text-xs font-medium text-imp-foreground\">\n Email <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"user@example.com\"\n required\n autoComplete=\"off\"\n className=\"w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm placeholder:text-imp-muted-foreground focus:outline-none focus:border-imp-primary/50\"\n />\n </div>\n\n <div>\n <label className=\"block mb-1 text-xs font-medium text-imp-foreground\">\n Password <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Min 8 characters\"\n required\n minLength={8}\n autoComplete=\"new-password\"\n className=\"w-full rounded-md border border-imp-border bg-imp-background px-3 py-1.5 text-sm placeholder:text-imp-muted-foreground focus:outline-none focus:border-imp-primary/50\"\n />\n </div>\n\n {error && (\n <div className=\"p-2 text-xs text-red-600 rounded-md bg-red-50 dark:bg-red-950/20 dark:text-red-400\">\n {error}\n </div>\n )}\n\n <div className=\"flex justify-end gap-2 pt-1\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"rounded-md border border-imp-border px-3 py-1.5 text-sm hover:bg-imp-muted\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-4 py-2 text-sm font-semibold rounded-md bg-imp-primary text-imp-primary-foreground hover:bg-imp-primary/90 disabled:opacity-50\"\n >\n {isSubmitting ? 'Creating…' : 'Create User'}\n </button>\n </div>\n </form>\n );\n}\n","/**\n * AuthenticatedInvect — Wraps the Invect component with auth gating.\n *\n * Renders InvectShell → AuthProvider → AuthGate around Invect.\n * The shell establishes the `.invect` CSS scope so all theme tokens\n * work for both the sign-in page and the Invect editor.\n *\n * When the user is not authenticated, shows the sign-in page.\n * When authenticated, renders the full Invect UI.\n *\n * Sign-up is disabled — initial admin users are configured explicitly via\n * `userAuth({ globalAdmins: [...] })`, and subsequent users are\n * created by the admin through the User Management panel.\n *\n * @example\n * ```tsx\n * import { AuthenticatedInvect } from '@invect/user-auth/ui';\n * import { Invect, InvectShell } from '@invect/frontend';\n * import '@invect/frontend/styles';\n *\n * export default function Page() {\n * return (\n * <AuthenticatedInvect\n * apiBaseUrl=\"/api/invect\"\n * basePath=\"/invect\"\n * InvectComponent={Invect}\n * ShellComponent={InvectShell}\n * />\n * );\n * }\n * ```\n */\n\nimport { type ReactNode, type ComponentType, type MemoExoticComponent } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { AuthProvider } from '../providers/AuthProvider';\nimport { AuthGate } from './AuthGate';\nimport { SignInPage } from './SignInPage';\n\n/**\n * Accepts both a plain component and a React.memo-wrapped component.\n * React.memo returns MemoExoticComponent which isn't directly assignable\n * to ComponentType in TypeScript, but is valid in JSX.\n */\ntype ComponentOrMemo<P> = ComponentType<P> | MemoExoticComponent<ComponentType<P>>;\n\n// ─────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Generic over TPlugin so that passing a typed InvectComponent (e.g. one that\n * expects `plugins?: InvectFrontendPlugin[]`) causes TypeScript to infer the\n * correct element type for the `plugins` prop on AuthenticatedInvect itself.\n * Defaults to `unknown` for the no-plugins case.\n */\nexport interface AuthenticatedInvectProps<TPlugin = unknown> {\n /**\n * Base URL for the Invect API.\n * Used for both auth endpoints and the Invect component.\n * @example '/api/invect' or 'http://localhost:3000/invect'\n */\n apiBaseUrl?: string;\n /**\n * Base path where Invect is mounted in the browser.\n * @default '/invect'\n */\n basePath?: string;\n /**\n * The Invect component to render when authenticated.\n * Pass this to avoid a direct dependency on @invect/frontend.\n * Accepts both plain and React.memo-wrapped components.\n *\n * @example\n * ```tsx\n * import { Invect } from '@invect/frontend';\n * <AuthenticatedInvect InvectComponent={Invect} />\n * ```\n */\n InvectComponent: ComponentOrMemo<{\n apiBaseUrl?: string;\n basePath?: string;\n reactQueryClient?: QueryClient;\n plugins?: TPlugin[];\n }>;\n /**\n * The InvectShell component that provides the `.invect` CSS scope.\n * This ensures theme tokens work for both the sign-in page and the\n * Invect editor. Import from `@invect/frontend`.\n *\n * `children` is typed as `unknown` rather than `ReactNode` to avoid a\n * structural incompatibility between `@types/react@18` (used here) and\n * `@types/react@19` (used by `@invect/frontend`) where `ReactPortal`\n * changed between versions.\n *\n * If not provided, the auth UI renders without the Invect CSS scope\n * and must rely on the host app's styling.\n *\n * @example\n * ```tsx\n * import { InvectShell } from '@invect/frontend';\n * <AuthenticatedInvect ShellComponent={InvectShell} />\n * ```\n */\n ShellComponent?: ComponentOrMemo<{\n children: ReactNode;\n theme?: 'light' | 'dark' | 'system';\n className?: string;\n }>;\n /**\n * Optional React Query client. If provided, it's shared between\n * the auth provider and the Invect component.\n */\n reactQueryClient?: QueryClient;\n /**\n * Content to display while checking session status.\n */\n loading?: ReactNode;\n /**\n * Theme for the shell wrapper.\n * @default 'system'\n */\n theme?: 'light' | 'dark' | 'system';\n /**\n * Frontend plugins forwarded to InvectComponent.\n * The element type is inferred from InvectComponent's `plugins` prop type,\n * so this stays consistent with whatever component you pass.\n *\n * @example\n * ```tsx\n * import { rbacFrontendPlugin } from '@invect/rbac/ui';\n * <AuthenticatedInvect plugins={[rbacFrontendPlugin]} />\n * ```\n */\n plugins?: TPlugin[];\n}\n\n// ─────────────────────────────────────────────────────────────\n// Component\n// ─────────────────────────────────────────────────────────────\n\nconst defaultQueryClient = new QueryClient({\n defaultOptions: {\n queries: { staleTime: 5 * 60 * 1000, retry: 1 },\n },\n});\n\nexport function AuthenticatedInvect<TPlugin = unknown>({\n apiBaseUrl = 'http://localhost:3000/invect',\n basePath = '/invect',\n InvectComponent,\n ShellComponent,\n reactQueryClient,\n loading,\n theme = 'light',\n plugins,\n}: AuthenticatedInvectProps<TPlugin>) {\n const client = reactQueryClient ?? defaultQueryClient;\n const Invect = InvectComponent as ComponentType<{\n apiBaseUrl?: string;\n basePath?: string;\n reactQueryClient?: QueryClient;\n plugins?: TPlugin[];\n }>;\n const Shell = ShellComponent as\n | ComponentType<{\n children?: ReactNode;\n theme?: 'light' | 'dark' | 'system';\n className?: string;\n }>\n | undefined;\n\n const content = (\n <QueryClientProvider client={client}>\n <AuthProvider baseUrl={apiBaseUrl}>\n <AuthGate loading={loading ?? <LoadingSpinner />} fallback={<SignInOnly />}>\n <Invect\n apiBaseUrl={apiBaseUrl}\n basePath={basePath}\n reactQueryClient={client}\n plugins={plugins}\n />\n </AuthGate>\n </AuthProvider>\n </QueryClientProvider>\n );\n\n // Wrap in the shell if provided — gives us the .invect CSS scope\n if (Shell) {\n return (\n <Shell theme={theme} className=\"h-full\">\n {content}\n </Shell>\n );\n }\n\n return content;\n}\n\n// ─────────────────────────────────────────────────────────────\n// Internal: Sign-in only view (no sign-up)\n// ─────────────────────────────────────────────────────────────\n\nfunction SignInOnly() {\n return (\n <SignInPage\n onSuccess={() => {\n // Auth state change will cause AuthGate to re-render with children\n }}\n subtitle=\"Sign in to access Invect\"\n />\n );\n}\n\n// ─────────────────────────────────────────────────────────────\n// Internal: Loading spinner\n// ─────────────────────────────────────────────────────────────\n\nfunction LoadingSpinner() {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-imp-background\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-imp-muted border-t-imp-primary\" />\n </div>\n );\n}\n","/**\n * UserManagementPage — Standalone page for user management.\n *\n * Wraps the existing UserManagement component in a page layout\n * consistent with the Access Control page style. Registered as a\n * plugin route contribution at '/users'.\n */\n\nimport { Users } from 'lucide-react';\nimport { useApiClient, PageLayout } from '@invect/frontend';\nimport { UserManagement } from './UserManagement';\nimport { useAuth } from '../providers/AuthProvider';\n\nexport function UserManagementPage() {\n const api = useApiClient();\n const { user, isAuthenticated } = useAuth();\n const apiBaseUrl = api.getBaseURL();\n\n if (!isAuthenticated) {\n return (\n <div className=\"imp-page w-full h-full min-h-0 overflow-y-auto bg-imp-background text-imp-foreground flex items-center justify-center\">\n <p className=\"text-sm text-imp-muted-foreground\">Please sign in to access this page.</p>\n </div>\n );\n }\n\n if (user?.role !== 'admin') {\n return (\n <PageLayout\n title=\"User Management\"\n subtitle=\"Manage users for your Invect instance.\"\n icon={Users}\n >\n <div className=\"rounded-md bg-yellow-50 p-3 text-sm text-yellow-800 dark:bg-yellow-950/20 dark:text-yellow-400\">\n Only administrators can manage users. Contact an admin for access.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"User Management\"\n subtitle=\"Create, manage, and remove users for your Invect instance.\"\n icon={Users}\n >\n <UserManagement apiBaseUrl={apiBaseUrl} />\n </PageLayout>\n );\n}\n","/**\n * ProfilePage — Standalone page for the current authenticated user.\n *\n * Shows basic account information and provides a sign-out action.\n */\n\nimport { LogOut, Mail, Shield, User as UserIcon } from 'lucide-react';\nimport { PageLayout } from '@invect/frontend';\nimport { useAuth } from '../providers/AuthProvider';\nimport { formatAuthRoleLabel } from '../../shared/roles';\n\nexport interface ProfilePageProps {\n basePath: string;\n}\n\nexport function ProfilePage({ basePath }: ProfilePageProps) {\n void basePath;\n const { user, isAuthenticated, isLoading, signOut } = useAuth();\n\n if (isLoading) {\n return (\n <div className=\"imp-page flex h-full min-h-0 w-full items-center justify-center overflow-y-auto bg-imp-background text-imp-foreground\">\n <p className=\"text-sm text-imp-muted-foreground\">Loading profile…</p>\n </div>\n );\n }\n\n if (!isAuthenticated || !user) {\n return (\n <div className=\"imp-page flex h-full min-h-0 w-full items-center justify-center overflow-y-auto bg-imp-background text-imp-foreground\">\n <p className=\"text-sm text-imp-muted-foreground\">Please sign in to view your profile.</p>\n </div>\n );\n }\n\n const displayName = user.name ?? user.email ?? user.id;\n const initials = displayName[0]?.toUpperCase() ?? '?';\n\n return (\n <PageLayout\n title=\"Profile\"\n subtitle=\"View your account details and manage your current session.\"\n icon={UserIcon}\n >\n <div className=\"max-w-2xl rounded-xl border border-imp-border bg-imp-card p-6 shadow-sm\">\n <div className=\"mb-6 flex items-center gap-4\">\n <div className=\"flex h-16 w-16 shrink-0 items-center justify-center overflow-hidden rounded-full bg-imp-primary/10 text-lg font-semibold text-imp-primary\">\n {user.image ? (\n <img src={user.image} alt={displayName} className=\"h-16 w-16 object-cover\" />\n ) : (\n initials\n )}\n </div>\n\n <div className=\"min-w-0\">\n <p className=\"truncate text-lg font-semibold text-imp-foreground\">{displayName}</p>\n <p className=\"truncate text-sm text-imp-muted-foreground\">{user.email ?? user.id}</p>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"rounded-lg border border-imp-border bg-imp-background p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-imp-foreground\">\n <UserIcon className=\"h-4 w-4 text-imp-muted-foreground\" />\n Name\n </div>\n <p className=\"text-sm text-imp-muted-foreground\">{user.name ?? 'Not set'}</p>\n </div>\n\n <div className=\"rounded-lg border border-imp-border bg-imp-background p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-imp-foreground\">\n <Mail className=\"h-4 w-4 text-imp-muted-foreground\" />\n Email\n </div>\n <p className=\"text-sm text-imp-muted-foreground\">{user.email ?? 'Not available'}</p>\n </div>\n\n <div className=\"rounded-lg border border-imp-border bg-imp-background p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-imp-foreground\">\n <Shield className=\"h-4 w-4 text-imp-muted-foreground\" />\n Role\n </div>\n <p className=\"text-sm text-imp-muted-foreground\">{formatAuthRoleLabel(user.role)}</p>\n </div>\n\n <div className=\"rounded-lg border border-imp-border bg-imp-background p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-imp-foreground\">\n <UserIcon className=\"h-4 w-4 text-imp-muted-foreground\" />\n User ID\n </div>\n <p className=\"break-all text-sm text-imp-muted-foreground\">{user.id}</p>\n </div>\n </div>\n\n <div className=\"mt-6 flex justify-end border-t border-imp-border pt-4\">\n <button\n onClick={async () => {\n await signOut();\n }}\n className=\"inline-flex items-center gap-2 rounded-md border border-imp-border px-4 py-2 text-sm font-medium text-imp-foreground transition-colors hover:bg-imp-muted\"\n >\n <LogOut className=\"h-4 w-4\" />\n Sign Out\n </button>\n </div>\n </div>\n </PageLayout>\n );\n}\n","/**\n * SidebarUserMenu — User avatar link in the sidebar footer.\n *\n * Clicking navigates directly to the profile page.\n * Sign-out is available on the profile page itself.\n */\n\nimport { Link, useLocation } from 'react-router';\nimport { useAuth } from '../providers/AuthProvider';\n\nexport interface SidebarUserMenuProps {\n collapsed?: boolean;\n basePath?: string;\n}\n\nexport function SidebarUserMenu({ collapsed = false, basePath = '' }: SidebarUserMenuProps) {\n const { user, isAuthenticated, isLoading } = useAuth();\n const location = useLocation();\n\n if (isLoading || !isAuthenticated || !user) {\n return null;\n }\n\n const initials = (user.name ?? user.email ?? user.id)[0]?.toUpperCase() ?? '?';\n const displayName = user.name ?? user.email ?? 'User';\n const profilePath = `${basePath}/profile`;\n const isActive = location.pathname === profilePath;\n\n return (\n <Link\n to={profilePath}\n title={`${displayName}${user.role ? ` — ${user.role}` : ''}`}\n className={[\n 'flex w-full items-center gap-3 rounded-md px-2 py-2 transition-colors',\n 'hover:bg-imp-muted/60',\n isActive ? 'bg-imp-muted/60' : '',\n collapsed ? 'justify-center' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {/* Avatar */}\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-imp-primary/10 text-sm font-medium text-imp-primary\">\n {user.image ? (\n <img src={user.image} alt={displayName} className=\"h-8 w-8 rounded-full object-cover\" />\n ) : (\n initials\n )}\n </div>\n\n {/* Name + role (only when expanded) */}\n {!collapsed && (\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\">{displayName}</p>\n {user.role && (\n <p className=\"truncate text-xs capitalize text-imp-muted-foreground\">{user.role}</p>\n )}\n </div>\n )}\n </Link>\n );\n}\n","/**\n * @invect/user-auth — Auth Frontend Plugin Definition\n *\n * Registers the auth plugin's frontend contributions:\n * - Sidebar item: \"Users\" (admin-only)\n * - Route: /users → UserManagementPage\n *\n * Note: AuthProvider is NOT included as a plugin provider because\n * AuthenticatedInvect already wraps the tree with it. This plugin\n * only adds the user management UI.\n */\n\nimport { Users } from 'lucide-react';\nimport { ProfilePage } from '../components/ProfilePage';\nimport { UserManagementPage } from '../components/UserManagementPage';\nimport { SidebarUserMenu } from '../components/SidebarUserMenu';\nimport type { InvectFrontendPlugin } from '@invect/frontend';\n\nexport const authFrontendPlugin: InvectFrontendPlugin = {\n id: 'user-auth',\n name: 'User Authentication',\n\n // ─── Sidebar ───\n sidebar: [\n {\n label: 'Users',\n icon: Users,\n path: '/users',\n position: 'top',\n permission: 'admin:*',\n },\n ],\n\n // ─── Sidebar Footer (user avatar + sign-out menu) ───\n sidebarFooter: SidebarUserMenu,\n\n // ─── Routes ───\n routes: [\n {\n path: '/profile',\n component: ProfilePage,\n },\n {\n path: '/users',\n component: UserManagementPage,\n },\n ],\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@invect/user-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Better Auth plugin for Invect — adds user authentication, session management, identity resolution, and auth UI components",
|
|
5
5
|
"main": "dist/backend/index.cjs",
|
|
6
6
|
"module": "dist/backend/index.mjs",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"kysely": "^0.28.0",
|
|
24
|
-
"@invect/core": "0.1.
|
|
24
|
+
"@invect/core": "0.1.3"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"better-auth": ">=1.0.0",
|
|
28
28
|
"better-sqlite3": ">=9.0.0",
|
|
29
|
-
"@invect/frontend": ">=0.1.
|
|
29
|
+
"@invect/frontend": ">=0.1.2",
|
|
30
30
|
"react": ">=18.0.0",
|
|
31
31
|
"react-dom": ">=18.0.0",
|
|
32
32
|
"react-router": ">=7.0.0",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"tsdown": "^0.21.1",
|
|
70
70
|
"typescript": "^5.3.3",
|
|
71
71
|
"vitest": "^1.2.2",
|
|
72
|
-
"@invect/frontend": "0.1.
|
|
72
|
+
"@invect/frontend": "0.1.2"
|
|
73
73
|
},
|
|
74
74
|
"exports": {
|
|
75
75
|
".": {
|