feat(web): login, sesión, interceptor, guards y layout con menú por permisos
- Login contra POST /api/auth/login; SessionStore en localStorage con el timeout deslizante que informa el servidor (sessionTimeoutMinutes). - Interceptor: Bearer en /api, renueva actividad y, ante 401, cierra la sesión y vuelve al login recordando la ruta (solo rutas internas). - Guards: sesión, invitado y permiso por pantalla (data.path). Corrige que inject() no puede usarse tras un await en permissionGuard. - Layout con barra superior (empresa, usuario, tema, salir) y menú lateral filtrado por GET /api/user/menu; plegable y con botón en pantallas angostas. - Menú único en libs/sap-types (MENU, filterMenu); el backend verifica por test que las rutas de permiso de sus recursos existan en él. - Una ruta por pantalla del menú; las no construidas muestran "no disponible". - Verificado en Chrome headless contra el backend y SAP reales: redirección sin sesión, menú de 20 pantallas, navegación, plegado, tema oscuro y persistencia, vista móvil, salir, token inválido y credenciales malas. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f1156929c7
commit
4c8c499a72
36 files changed
+1291
-52
No files matched your search
@@ -1,3 +1,33 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import type { Routes } from '@angular/router';
|
||||
import { MENU } from '@basic-erp/sap-types';
|
||||
import { authGuard, guestGuard, menuLoadedGuard, permissionGuard } from './core/auth/guards';
|
||||
|
||||
export const routes: Routes = [];
|
||||
/** Una ruta por pantalla del menú; las que aún no existen muestran "no disponible". */
|
||||
const screens: Routes = MENU.flatMap((m) => m.items).map((item) => ({
|
||||
path: item.path.slice(1),
|
||||
canActivate: [permissionGuard],
|
||||
data: { path: item.path, title: item.label },
|
||||
loadComponent: () => import('./features/shared/coming-soon.page').then((m) => m.ComingSoonPage),
|
||||
}));
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: 'login',
|
||||
canActivate: [guestGuard],
|
||||
loadComponent: () => import('./features/login/login.page').then((m) => m.LoginPage),
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
canActivate: [authGuard, menuLoadedGuard],
|
||||
loadComponent: () => import('./layout/main-layout').then((m) => m.MainLayout),
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', loadComponent: () => import('./features/home/home.page').then((m) => m.HomePage) },
|
||||
{
|
||||
path: 'sin-acceso',
|
||||
loadComponent: () => import('./features/shared/no-access.page').then((m) => m.NoAccessPage),
|
||||
},
|
||||
...screens,
|
||||
],
|
||||
},
|
||||
{ path: '**', redirectTo: '' },
|
||||
];
|
||||
Reference in new issue
Block a user