UDT-008: Gestión completa de usuarios #11
@@ -6,8 +6,10 @@ import {
|
|||||||
Zap,
|
Zap,
|
||||||
Settings,
|
Settings,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
|
Users,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
|
Lock,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
@@ -86,6 +88,25 @@ export function SidebarNav() {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* Profile / account section — visible for all authenticated users */}
|
||||||
|
<div className="pt-2 pb-1 px-3">
|
||||||
|
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground/60">
|
||||||
|
Mi cuenta
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
to="/perfil/contrasena"
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors hover:bg-accent hover:text-accent-foreground',
|
||||||
|
pathname === '/perfil/contrasena'
|
||||||
|
? 'bg-accent text-accent-foreground font-medium'
|
||||||
|
: 'text-muted-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Lock className="h-4 w-4 shrink-0" />
|
||||||
|
<span>Cambiar contraseña</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
{/* Admin-only section */}
|
{/* Admin-only section */}
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<>
|
<>
|
||||||
@@ -94,6 +115,18 @@ export function SidebarNav() {
|
|||||||
Administración
|
Administración
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<Link
|
||||||
|
to="/usuarios"
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors hover:bg-accent hover:text-accent-foreground',
|
||||||
|
pathname.startsWith('/usuarios') && pathname !== '/usuarios/nuevo'
|
||||||
|
? 'bg-accent text-accent-foreground font-medium'
|
||||||
|
: 'text-muted-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Users className="h-4 w-4 shrink-0" />
|
||||||
|
<span>Usuarios</span>
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/usuarios/nuevo"
|
to="/usuarios/nuevo"
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
import { useAuthStore } from './stores/authStore'
|
import { useAuthStore } from './stores/authStore'
|
||||||
import { ProtectedRoute } from './components/routing/ProtectedRoute'
|
import { ProtectedRoute } from './components/routing/ProtectedRoute'
|
||||||
|
import { MustChangePasswordGate } from './components/routing/MustChangePasswordGate'
|
||||||
import { LoginPage } from './features/auth/pages/LoginPage'
|
import { LoginPage } from './features/auth/pages/LoginPage'
|
||||||
import { CreateUserPage } from './features/users/pages/CreateUserPage'
|
import { CreateUserPage } from './features/users/pages/CreateUserPage'
|
||||||
|
import { UsersListPage } from './features/users/pages/UsersListPage'
|
||||||
|
import { UserDetailPage } from './features/users/pages/UserDetailPage'
|
||||||
|
import { UserEditPage } from './features/users/pages/UserEditPage'
|
||||||
|
import { ChangeMyPasswordPage } from './features/profile/pages/ChangeMyPasswordPage'
|
||||||
import { RolesPage } from './features/roles/pages/RolesPage'
|
import { RolesPage } from './features/roles/pages/RolesPage'
|
||||||
import { NewRolPage } from './features/roles/pages/NewRolPage'
|
import { NewRolPage } from './features/roles/pages/NewRolPage'
|
||||||
import { EditRolPage } from './features/roles/pages/EditRolPage'
|
import { EditRolPage } from './features/roles/pages/EditRolPage'
|
||||||
@@ -19,9 +24,30 @@ function PublicRoute({ children }: { children: React.ReactNode }) {
|
|||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a protected route with ProtectedLayout + MustChangePasswordGate.
|
||||||
|
* The gate forces users with mustChangePassword=true to /perfil/contrasena.
|
||||||
|
*/
|
||||||
|
function ProtectedPage({
|
||||||
|
children,
|
||||||
|
requiredPermissions,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
requiredPermissions?: string[]
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ProtectedRoute requiredPermissions={requiredPermissions}>
|
||||||
|
<MustChangePasswordGate>
|
||||||
|
<ProtectedLayout>{children}</ProtectedLayout>
|
||||||
|
</MustChangePasswordGate>
|
||||||
|
</ProtectedRoute>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function AppRoutes() {
|
export function AppRoutes() {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
|
{/* Public routes */}
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
path="/login"
|
||||||
element={
|
element={
|
||||||
@@ -32,71 +58,102 @@ export function AppRoutes() {
|
|||||||
</PublicRoute>
|
</PublicRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Change password — protected but NO MustChangePasswordGate (avoids redirect loop) */}
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/perfil/contrasena"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<ProtectedLayout>
|
<ProtectedLayout>
|
||||||
<HomePage />
|
<ChangeMyPasswordPage />
|
||||||
</ProtectedLayout>
|
</ProtectedLayout>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Protected routes — all wrapped with MustChangePasswordGate */}
|
||||||
|
<Route
|
||||||
|
path="/"
|
||||||
|
element={<ProtectedPage><HomePage /></ProtectedPage>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/usuarios"
|
||||||
|
element={
|
||||||
|
<ProtectedPage requiredPermissions={['administracion:usuarios:gestionar']}>
|
||||||
|
<UsersListPage />
|
||||||
|
</ProtectedPage>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/usuarios/nuevo"
|
path="/usuarios/nuevo"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute requiredPermissions={['administracion:usuarios:gestionar']}>
|
<ProtectedPage requiredPermissions={['administracion:usuarios:gestionar']}>
|
||||||
<ProtectedLayout>
|
|
||||||
<CreateUserPage />
|
<CreateUserPage />
|
||||||
</ProtectedLayout>
|
</ProtectedPage>
|
||||||
</ProtectedRoute>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/usuarios/:id"
|
||||||
|
element={
|
||||||
|
<ProtectedPage requiredPermissions={['administracion:usuarios:gestionar']}>
|
||||||
|
<UserDetailPage />
|
||||||
|
</ProtectedPage>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/usuarios/:id/editar"
|
||||||
|
element={
|
||||||
|
<ProtectedPage requiredPermissions={['administracion:usuarios:gestionar']}>
|
||||||
|
<UserEditPage />
|
||||||
|
</ProtectedPage>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/admin/roles"
|
path="/admin/roles"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute requiredPermissions={['administracion:roles:gestionar']}>
|
<ProtectedPage requiredPermissions={['administracion:roles:gestionar']}>
|
||||||
<ProtectedLayout>
|
|
||||||
<RolesPage />
|
<RolesPage />
|
||||||
</ProtectedLayout>
|
</ProtectedPage>
|
||||||
</ProtectedRoute>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/admin/roles/nuevo"
|
path="/admin/roles/nuevo"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute requiredPermissions={['administracion:roles:gestionar']}>
|
<ProtectedPage requiredPermissions={['administracion:roles:gestionar']}>
|
||||||
<ProtectedLayout>
|
|
||||||
<NewRolPage />
|
<NewRolPage />
|
||||||
</ProtectedLayout>
|
</ProtectedPage>
|
||||||
</ProtectedRoute>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/admin/roles/:codigo/editar"
|
path="/admin/roles/:codigo/editar"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute requiredPermissions={['administracion:roles:gestionar']}>
|
<ProtectedPage requiredPermissions={['administracion:roles:gestionar']}>
|
||||||
<ProtectedLayout>
|
|
||||||
<EditRolPage />
|
<EditRolPage />
|
||||||
</ProtectedLayout>
|
</ProtectedPage>
|
||||||
</ProtectedRoute>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/admin/permisos"
|
path="/admin/permisos"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute
|
<ProtectedPage
|
||||||
requiredPermissions={[
|
requiredPermissions={[
|
||||||
'administracion:roles_permisos:gestionar',
|
'administracion:roles_permisos:gestionar',
|
||||||
'administracion:permisos:ver',
|
'administracion:permisos:ver',
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<ProtectedLayout>
|
|
||||||
<RolPermisosPage />
|
<RolPermisosPage />
|
||||||
</ProtectedLayout>
|
</ProtectedPage>
|
||||||
</ProtectedRoute>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user