auth tests
This commit is contained in:
@@ -9,7 +9,15 @@ type IconName =
|
||||
| 'log-out'
|
||||
| 'user'
|
||||
| 'settings'
|
||||
| 'layout-dashboard';
|
||||
| 'layout-dashboard'
|
||||
| 'pickaxe'
|
||||
| 'box'
|
||||
| 'landmark'
|
||||
| 'book-user'
|
||||
| 'history'
|
||||
| 'list'
|
||||
| 'mail-plus'
|
||||
| 'images';
|
||||
|
||||
type Visibility =
|
||||
| 'always'
|
||||
@@ -41,7 +49,7 @@ const menu: MenuItem[] = [
|
||||
{ id: 'legalLink', href: '/policy', ico: 'shield', label: 'Legal', visibility: 'always' },
|
||||
{ id: 'docsLink', href: 'https://lunddarklab.github.io/adc/', ico: 'book-open', label: 'Docs', visibility: 'always' },
|
||||
{ id: 'loginLink', href: '/login', ico: 'log-in', label: 'Login', visibility: 'guest-only' },
|
||||
{ id: 'userMenuToggle', href: '#', ico: 'menu', visibility: 'auth-only' },
|
||||
{ id: 'user-menu-toggle', href: '#', ico: 'menu', visibility: 'auth-only' },
|
||||
];
|
||||
|
||||
|
||||
@@ -55,9 +63,15 @@ export function getVisibleItems(isLoggedIn: boolean) {
|
||||
}
|
||||
|
||||
// --- Menu utente (pannello laterale) -------------------------------------
|
||||
// Mostrato solo agli utenti autenticati. Le voci con `roles` sono visibili
|
||||
// solo ai ruoli elencati; senza `roles` sono visibili a tutti i loggati.
|
||||
// Per spostare/aggiungere una voce basta intervenire qui, senza toccare il DOM.
|
||||
// Mostrato solo agli utenti autenticati. Le voci sono raggruppate in sezioni
|
||||
// con titolo (come in dyncoll.v1/assets/menu.php). Sia i gruppi sia le singole
|
||||
// voci possono dichiarare `roles`: se presente, sono visibili solo ai ruoli
|
||||
// elencati; se assente, a tutti i loggati. Per spostare/aggiungere una voce
|
||||
// basta intervenire qui, senza toccare il DOM.
|
||||
//
|
||||
// NB: le "main pages" di v1 (home/map/credits/legal/db model) non sono replicate
|
||||
// qui: in v2 l'header-menu è sempre visibile, quindi quei link non servono nel
|
||||
// pannello. Gli href sono rotte placeholder, da allineare al routing reale.
|
||||
|
||||
type PanelAction = 'logout';
|
||||
|
||||
@@ -70,35 +84,55 @@ export type UserPanelItem = {
|
||||
roles?: RoleId[]; // assente = tutti gli autenticati
|
||||
};
|
||||
|
||||
const userPanel: UserPanelItem[] = [
|
||||
{ id: 'dashboardLink', label: 'Dashboard', ico: 'layout-dashboard', href: '/dashboard' },
|
||||
{ id: 'profileLink', label: 'Profile', ico: 'user', href: '/profile' },
|
||||
{ id: 'adminLink', label: 'Admin', ico: 'settings', href: '/admin', roles: [ROLE.ADMIN, ROLE.SUPERVISOR] },
|
||||
{ id: 'logoutBtn', label: 'Logout', ico: 'log-out', action: 'logout' },
|
||||
export type UserPanelGroup = {
|
||||
title?: string; // titolo di sezione; assente nel primo gruppo
|
||||
roles?: RoleId[]; // assente = tutti gli autenticati
|
||||
items: UserPanelItem[];
|
||||
};
|
||||
|
||||
const userPanel: UserPanelGroup[] = [
|
||||
{
|
||||
items: [
|
||||
{ id: 'dashboardLink', label: 'Dashboard', ico: 'layout-dashboard', href: '/dashboard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Add resource',
|
||||
items: [
|
||||
{ id: 'artifactAddLink', label: 'Artifact', ico: 'pickaxe', href: '/artifacts/add' },
|
||||
{ id: 'modelAddLink', label: 'Model', ico: 'box', href: '/models/add' },
|
||||
{ id: 'institutionAddLink', label: 'Institution', ico: 'landmark', href: '/institutions/add' },
|
||||
{ id: 'personAddLink', label: 'Person', ico: 'book-user', href: '/persons/add' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Admin',
|
||||
roles: [ROLE.ADMIN, ROLE.SUPERVISOR],
|
||||
items: [
|
||||
{ id: 'timelineLink', label: 'Timeline', ico: 'history', href: '/timeline' },
|
||||
{ id: 'vocabulariesLink', label: 'Vocabularies', ico: 'list', href: '/vocabularies' },
|
||||
{ id: 'mailComposerLink', label: 'Compose email', ico: 'mail-plus', href: '/mail/compose' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'My account',
|
||||
items: [
|
||||
{ id: 'settingsLink', label: 'Settings', ico: 'settings', href: '/settings' },
|
||||
{ id: 'collectionsLink', label: 'My collections', ico: 'images', href: '#' },
|
||||
{ id: 'logoutBtn', label: 'Logout', ico: 'log-out', action: 'logout' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function getUserPanelItems(roleId: number): UserPanelItem[] {
|
||||
return userPanel.filter(item => !item.roles || item.roles.includes(roleId as RoleId));
|
||||
}
|
||||
|
||||
// Tipi per sidebar
|
||||
// type SidebarAction = 'logout';
|
||||
// type SidebarItem = {
|
||||
// id?: string;
|
||||
// label: string;
|
||||
// ico: IconName;
|
||||
// href?: string; // se presente, la voce è un link
|
||||
// action?: SidebarAction; // se presente, la voce è un pulsante con comportamento
|
||||
// };
|
||||
|
||||
// Gruppo di voci del menu laterale. `roles` elenca i ruoli che vedono il gruppo:
|
||||
// per spostare/aggiungere un link basta intervenire qui, senza toccare il DOM.
|
||||
// type SidebarGroup = {
|
||||
// title?: string; // titolo di sezione (header colorato); assente nel primo gruppo
|
||||
// roles: RoleId[];
|
||||
// items: SidebarItem[];
|
||||
// };
|
||||
|
||||
// Menu laterale (utenti autenticati). I gruppi sono filtrati per ruolo:
|
||||
// sposta una voce in un altro gruppo per cambiarne la visibilità.
|
||||
// const ALL_AUTH: RoleId[] = [ROLE.ADMIN, ROLE.SUPERVISOR, ROLE.USER];
|
||||
// Ritorna i gruppi visibili al ruolo: filtra prima i gruppi, poi le singole voci,
|
||||
// scartando infine i gruppi rimasti vuoti.
|
||||
export function getUserPanelGroups(roleId: number): UserPanelGroup[] {
|
||||
const role = roleId as RoleId;
|
||||
return userPanel
|
||||
.filter(group => !group.roles || group.roles.includes(role))
|
||||
.map(group => ({
|
||||
...group,
|
||||
items: group.items.filter(item => !item.roles || item.roles.includes(role)),
|
||||
}))
|
||||
.filter(group => group.items.length > 0);
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { AuthUser } from "@/shared/auth";
|
||||
import { logoutUser } from "@/shared/auth";
|
||||
import { getVisibleItems, getUserPanelItems, type UserPanelItem } from "./menuTypes";
|
||||
import { getVisibleItems, getUserPanelGroups, type UserPanelItem } from "./menuTypes";
|
||||
import { getCurrentDate } from "@/shared/utils";
|
||||
import {
|
||||
createIcons,
|
||||
House, MapPin, Award, Shield, BookOpen, LogIn, Menu,
|
||||
LogOut, User, Settings, LayoutDashboard,
|
||||
Pickaxe, Box, Landmark, BookUser, History, List, MailPlus, Images,
|
||||
} from 'lucide';
|
||||
|
||||
type Img = {
|
||||
@@ -27,7 +28,11 @@ const footerImg: Img[] = [
|
||||
];
|
||||
|
||||
// Set unico di icone Lucide usate nelle isole (header, pannello, footer).
|
||||
const icons = { House, MapPin, Award, Shield, BookOpen, LogIn, Menu, LogOut, User, Settings, LayoutDashboard };
|
||||
const icons = {
|
||||
House, MapPin, Award, Shield, BookOpen, LogIn, Menu,
|
||||
LogOut, User, Settings, LayoutDashboard,
|
||||
Pickaxe, Box, Landmark, BookUser, History, List, MailPlus, Images,
|
||||
};
|
||||
|
||||
export function renderUI(user: AuthUser | null): void {
|
||||
setLogoHeader();
|
||||
@@ -92,7 +97,7 @@ function setHeaderMenu(user: AuthUser | null): void {
|
||||
function setFooterContent(user: AuthUser | null): void{
|
||||
const footerMenu = document.getElementById('footer-menu');
|
||||
if(footerMenu){
|
||||
buildMenu(footerMenu, getVisibleItems(user !== null), { exclude: ['userMenuToggle'] });
|
||||
buildMenu(footerMenu, getVisibleItems(user !== null), { exclude: ['user-menu-toggle'] });
|
||||
}
|
||||
addFooterLogo();
|
||||
}
|
||||
@@ -127,7 +132,7 @@ function buildMenu(dom: HTMLElement, links: MenuLink[], opts: { exclude?: string
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pannello utente (solo loggati): pannello laterale da destra, sotto l'header,
|
||||
// con backdrop leggero. L'hamburger (#userMenuToggle) resta sempre visibile e
|
||||
// con backdrop leggero. L'hamburger (#user-menu-toggle) resta sempre visibile e
|
||||
// fa da toggle. Replica il comportamento di v1 senza coprire l'header.
|
||||
const PANEL_ID = 'user-panel';
|
||||
const BACKDROP_ID = 'user-panel-backdrop';
|
||||
@@ -139,7 +144,7 @@ function setUserPanel(user: AuthUser | null): void {
|
||||
document.getElementById(BACKDROP_ID)?.remove();
|
||||
if (escHandler) { document.removeEventListener('keydown', escHandler); escHandler = null; }
|
||||
|
||||
const toggle = document.getElementById('userMenuToggle') as HTMLAnchorElement | null;
|
||||
const toggle = document.getElementById('user-menu-toggle') as HTMLAnchorElement | null;
|
||||
if (!user || !toggle) return;
|
||||
|
||||
// backdrop (parte sotto l'header, vedi CSS)
|
||||
@@ -156,8 +161,14 @@ function setUserPanel(user: AuthUser | null): void {
|
||||
|
||||
const list = document.createElement('ul');
|
||||
list.className = 'menu w-full p-4';
|
||||
getUserPanelItems(user.role_id).forEach(item => {
|
||||
list.appendChild(buildPanelEntry(item));
|
||||
getUserPanelGroups(user.role_id).forEach(group => {
|
||||
if (group.title) {
|
||||
const title = document.createElement('li');
|
||||
title.className = 'menu-title';
|
||||
title.textContent = group.title;
|
||||
list.appendChild(title);
|
||||
}
|
||||
group.items.forEach(item => list.appendChild(buildPanelEntry(item)));
|
||||
});
|
||||
panel.appendChild(list);
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
@import '@fontsource/rajdhani/500.css';
|
||||
@import '@fontsource/rajdhani/700.css'; /* display/titoli, coerente col logo */
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "daisyui"; /* NOSONAR */
|
||||
|
||||
@theme {
|
||||
--font-sans: "Titillium Web", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-secondary: "Rajdhani", ui-sans-serif, system-ui, sans-serif;
|
||||
@@ -17,6 +19,7 @@
|
||||
--dc-secondary-dark: rgb(241, 146, 4);
|
||||
--dc-white: rgb(255, 255, 255);
|
||||
--dc-dark-gray: rgb(52, 58, 64);
|
||||
--dc-light-gray: rgb(77,76,68,1);
|
||||
}
|
||||
|
||||
body{
|
||||
@@ -48,6 +51,12 @@ header {
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.user-panel li a,
|
||||
.user-panel li button{
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#header-menu a{
|
||||
width:70px;
|
||||
height:100%;
|
||||
@@ -57,7 +66,7 @@ header {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#header-menu a:not(#userMenuToggle){
|
||||
#header-menu a:not(#user-menu-toggle){
|
||||
border-bottom: 4px solid transparent;
|
||||
transition: all 500ms ease;
|
||||
}
|
||||
@@ -90,22 +99,16 @@ header {
|
||||
width:100%;
|
||||
height:150px;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
#header-menu a:not(#userMenuToggle):hover{
|
||||
border-bottom-color: var(--dc-white);
|
||||
}
|
||||
place-content: center center;
|
||||
}
|
||||
|
||||
/* Sotto md (48rem): voci più strette e label più piccola, così il testo resta
|
||||
sotto le icone senza mandare in overflow l'header su schermi piccoli. */
|
||||
@media (max-width: 47.999rem) {
|
||||
@media (width <= 47.999rem) {
|
||||
#header-menu a{
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
#header-menu a span{
|
||||
font-size: 0.6rem;
|
||||
line-height: 1.1;
|
||||
@@ -121,36 +124,72 @@ header {
|
||||
top: var(--dc-header-h);
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: min(20rem, 85vw);
|
||||
width: min(15rem, 85vw);
|
||||
z-index: 910;
|
||||
background-color: var(--dc-white);
|
||||
color: var(--dc-dark-gray);
|
||||
background-color: var(--dc-light-gray);
|
||||
color: var(--dc-white);
|
||||
box-shadow: -4px 0 16px rgb(0 0 0 / 0.18);
|
||||
overflow-y: auto;
|
||||
transform: translateX(100%);
|
||||
transition: transform 300ms ease;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
.user-panel.is-open{
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
ul.menu{ padding:0 }
|
||||
|
||||
ul.menu > li > *{
|
||||
padding:10px 20px;
|
||||
transition: all 300ms ease;
|
||||
}
|
||||
|
||||
.menu .menu-title{
|
||||
background-color: var(--dc-dark-gray);
|
||||
color: var(--dc-white);
|
||||
font-size: 1.2rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.menu li:not(.menu-title){
|
||||
border-bottom: 1px solid var(--dc-dark-gray);
|
||||
}
|
||||
|
||||
/* Voci del pannello utente: l'icona Lucide (svg, 24px di default) viene
|
||||
dimensionata in em così segue il testo, e allineata verticalmente ad esso. */
|
||||
|
||||
|
||||
.user-panel li :is(a, button) svg{
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-panel-backdrop{
|
||||
position: fixed;
|
||||
top: var(--dc-header-h);
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
inset: var(--dc-header-h) 0 0 0;
|
||||
z-index: 900;
|
||||
background-color: rgb(0 0 0 / 0.25);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 300ms ease;
|
||||
}
|
||||
|
||||
.user-panel-backdrop.is-open{
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
#header-menu a:not(#user-menu-toggle):hover{
|
||||
border-bottom-color: var(--dc-white);
|
||||
}
|
||||
|
||||
ul.menu > li > *:hover{ padding-left: 35px;}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.user-panel,
|
||||
.user-panel-backdrop{
|
||||
|
||||
Reference in New Issue
Block a user