aggiunti link per navigare quadno loggati e la apgian dei producers

This commit is contained in:
Lucio Lelii 2026-02-09 16:16:28 +01:00
parent 74c9984994
commit f63bac4457
31 changed files with 536 additions and 129 deletions

View File

@ -5,6 +5,11 @@
"packageManager": "npm" "packageManager": "npm"
}, },
"newProjectRoot": "projects", "newProjectRoot": "projects",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"projects": { "projects": {
"validator-gui": { "validator-gui": {
"projectType": "application", "projectType": "application",

View File

@ -1,8 +1 @@
<header> <app-main-layout></app-main-layout>
<app-header></app-header>
</header>
<main class="content">
<router-outlet></router-outlet>
</main>

View File

@ -1,6 +1,7 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { HomePage } from './pages/home/home'; import { HomePage } from './pages/home/home';
import { LoginPage } from './pages/login/login'; import { LoginPage } from './pages/login/login';
import { authGuard } from './guards/auth-guard';
export const routes: Routes = [ export const routes: Routes = [
{ {
@ -12,6 +13,14 @@ export const routes: Routes = [
path: 'login', path: 'login',
component: LoginPage component: LoginPage
}, },
{
path: '',
canActivate: [authGuard],
children: [
{ path: '', redirectTo: 'producers', pathMatch: 'full' },
{ path: 'producers', loadComponent: () => import('./pages/producers/producers').then(m => m.ProducersComponent) },
]
},
{ {
path: '**', path: '**',
redirectTo: '' redirectTo: ''

View File

@ -1,19 +1,13 @@
import { Component, inject, signal } from '@angular/core'; import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { MainLayout } from "./layouts/main-layout/main-layout";
import { Header } from "./components/header/header";
import { AuthenticationService } from './services/authentication';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
imports: [ RouterOutlet, Header], imports: [MainLayout],
templateUrl: './app.html', templateUrl: './app.html',
styleUrl: './app.css' styleUrl: './app.css'
}) })
export class App { export class App {
protected readonly title = signal('validator-gui'); protected readonly title = signal('validator-gui');
protected authService = inject(AuthenticationService);
loggedInUser = this.authService.loggedInUser;
} }

View File

@ -1,25 +1,23 @@
<it-header slimTitle="ISTI CNR - Istituto di Scienza e Tecnologie dell'Informazione" <it-header slimTitle="ISTI CNR - Istituto di Scienza e Tecnologie dell'Informazione"
slimTitleLink="https://www.isti.cnr.it" [loginStyle]="'none'" [light]="false" [showSearch]="false" [sticky]="true" slimTitleLink="https://www.isti.cnr.it" [loginStyle]="getLoginStyle()" [smallHeader]="false" [light]="false"
[megamenu]="true"> [showSearch]="false" [sticky]="true" [megamenu]="false" (loginClick)="login()">
<ng-container slimRightZone>
@if(loggedInUser()) { @if(loggedInUser()) {
<a itAvatar size="md" href="#" color="blue" <ng-container slimRightZone>
<div class="d-flex align-items-center gap-2">
<a itAvatar size="md" color="blue"
itTooltip="<strong>{{loggedInUser()!.username}}</strong><br/><em>{{loggedInUser()!.email}}</em>" itTooltip="<strong>{{loggedInUser()!.username}}</strong><br/><em>{{loggedInUser()!.email}}</em>"
tooltipPlacement="top" tooltipHtml="true"> tooltipPlacement="top" tooltipHtml="true">
<p aria-hidden="true">{{ loggedInUser()!.username | initials }}</p> <p aria-hidden="true">{{ loggedInUser()!.username | initials }}</p>
<span class="visually-hidden">Mario Rossi</span> <span class="visually-hidden">{{ loggedInUser()!.username }}</span>
</a> </a>
} @else {
<a class="btn btn-primary btn-sm mx-auto text-decoration-none" routerLink="/login"> <it-icon class="logout-icon" color="white" name="logout" role="button" tabindex="0" (click)="logout()">
<div class="d-flex text-decoration-none gap-1 align-items-center"> </it-icon>
<it-icon name="user"></it-icon>
<span>Accedi all'area riservata</span>
</div> </div>
</a>
}
</ng-container> </ng-container>
}
<ng-container brand> <ng-container brand>
<a href="#"> <a routerLink="home">
<it-icon name="pa"></it-icon> <it-icon name="pa"></it-icon>
<div class="it-brand-text"> <div class="it-brand-text">
<div class="it-brand-title">Validatore Sistemi Fiscali</div> <div class="it-brand-title">Validatore Sistemi Fiscali</div>
@ -27,9 +25,5 @@
</div> </div>
</a> </a>
</ng-container> </ng-container>
<ng-container navItems>
<it-navbar-item>
<a class="nav-link active" routerLink="/" aria-current="page"><span>Home</span></a>
</it-navbar-item>
</ng-container>
</it-header> </it-header>

View File

@ -0,0 +1,7 @@
.logout-icon {
cursor: pointer;
color: white;
&:hover {
opacity: 0.85;
}
}

View File

@ -1,18 +1,36 @@
import { Component, inject } from '@angular/core'; import { Component, computed, inject } from '@angular/core';
import { DesignAngularKitModule, ItTooltipDirective } from 'design-angular-kit'; import { DesignAngularKitModule, ItTooltipDirective } from 'design-angular-kit';
import { AuthenticationService } from '../../services/authentication'; import { AuthenticationService } from '../../services/authentication';
import { InitialsPipe } from '../../pipes/initials-pipe-pipe'; import { InitialsPipe } from '../../pipes/initials-pipe-pipe';
import { RouterLink } from "@angular/router"; import { Router } from "@angular/router";
@Component({ @Component({
selector: 'app-header', selector: 'app-header',
imports: [DesignAngularKitModule, InitialsPipe, RouterLink], imports: [DesignAngularKitModule, InitialsPipe],
templateUrl: './header.html', templateUrl: './header.html',
styleUrl: './header.css', styleUrl: './header.scss',
}) })
export class Header { export class Header {
private router = inject(Router);
protected authService = inject(AuthenticationService); protected authService = inject(AuthenticationService);
loggedInUser = this.authService.loggedInUser; getLoginStyle() {
return this.authService.loggedInUser() ? 'none' : 'full';
}
login() {
this.router.navigate(['/login']);
}
logout() {
this.authService.logout();
this.router.navigate(['/home']);
}
loggedInUser() {
return this.authService.loggedInUser();
}
} }

View File

@ -0,0 +1,19 @@
<it-list>
<it-list-item iconLeft="true" href="#" externalLink="true">
<div>
<h4 class="text m-0">Link lista 1</h4>
<p class="small m-0">Lorem ipsum dolor sit amet.</p>
</div>
<ng-container multiple>
<a href="#" aria-label="Testo - Azione 1">
<it-icon name="code-circle" color="primary"></it-icon>
</a>
<a href="#" aria-label="Testo - Azione 2">
<it-icon name="code-circle" color="primary"></it-icon>
</a>
<a href="#" aria-label="Testo - Azione 3">
<it-icon name="code-circle" color="primary"></it-icon>
</a>
</ng-container>
</it-list-item>
</it-list>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProducersList } from './producers-list';
describe('ProducersList', () => {
let component: ProducersList;
let fixture: ComponentFixture<ProducersList>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProducersList]
})
.compileComponents();
fixture = TestBed.createComponent(ProducersList);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { DesignAngularKitModule } from 'design-angular-kit';
@Component({
selector: 'app-producers-list',
imports: [DesignAngularKitModule],
templateUrl: './producers-list.html',
styleUrl: './producers-list.scss',
})
export class ProducersListComponent {
}

View File

@ -9,7 +9,7 @@ export const authGuard: CanActivateFn = (route, state) => {
if (authService.isLoggedIn()) { if (authService.isLoggedIn()) {
return true; // Allow access return true; // Allow access
} else { } else {
router.navigate(['/']); router.navigate(['/login']);
return false; // Deny access return false; // Deny access
} }
}; };

View File

@ -0,0 +1,26 @@
<header>
<app-header></app-header>
</header>
<nav class="pa-nav" aria-label="Navigazione principale">
<ul class="pa-nav__list">
<li class="pa-nav__item"
routerLinkActive="is-active"
[routerLinkActiveOptions]="{ exact: true }">
<a class="pa-nav__link" [routerLink]="['/']">
Home
</a>
</li>
@if(loggedInUser()) {
<li class="pa-nav__item"
routerLinkActive="is-active"
[routerLinkActiveOptions]="{ exact: true }">
<a class="pa-nav__link" [routerLink]="['/producers']">
Produttori
</a>
</li>
}
</ul>
</nav>
<main class="content">
<router-outlet></router-outlet>
</main>

View File

@ -0,0 +1,42 @@
/* wrapper nav (opzionale se già su header) */
.pa-nav {
background-color: #0066cc; // azzurro PA
display: flex;
padding: 0 50px;
}
/* lista */
.pa-nav__list {
display: flex;
gap: 0.25rem;
margin: 0;
padding: 0 0.5rem;
list-style: none;
}
/* link base */
.pa-nav__link {
display: inline-flex;
align-items: center;
padding: 0.55rem 0.9rem;
text-decoration: none;
font-weight: 500;
color: #ffffff;
opacity: 0.92;
transition: background-color 0.15s ease, box-shadow 0.15s ease;
&:hover {
cursor: pointer;
}
&:focus {
opacity: 1;
background-color: rgba(255, 255, 255, 0.15);
text-decoration: none;
}
}
.pa-nav__item.is-active > .pa-nav__link {
color: beige;
background-color: rgba(255, 255, 255, 0.25);
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MainLayout } from './main-layout';
describe('MainLayout', () => {
let component: MainLayout;
let fixture: ComponentFixture<MainLayout>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MainLayout]
})
.compileComponents();
fixture = TestBed.createComponent(MainLayout);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
import { Component, inject } from '@angular/core';
import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
import { DesignAngularKitModule } from 'design-angular-kit';
import { Header } from '../../components/header/header';
import { AuthenticationService } from '../../services/authentication';
@Component({
selector: 'app-main-layout',
imports: [RouterOutlet, Header, DesignAngularKitModule, RouterLink, RouterLinkActive],
templateUrl: './main-layout.html',
styleUrl: './main-layout.scss',
})
export class MainLayout {
protected authService = inject(AuthenticationService);
loggedInUser() {
return this.authService.loggedInUser();
}
}

View File

@ -0,0 +1,4 @@
export interface LoginData {
email: string;
password: string;
}

View File

@ -1,4 +1,7 @@
export type User = {
username: string; export type Role = 'admin' | 'user';
email: string;
export class User {
constructor(public username: string, public email: string, public role: Role = "user") {}
}; };

View File

@ -15,7 +15,9 @@
attraverso regole configurabili e controlli avanzati. attraverso regole configurabili e controlli avanzati.
</p> </p>
@if(!isLoggedIn()) {
<p class="home-content__text"> <p class="home-content__text">
Laccesso alle funzionalità complete è riservato agli utenti autenticati. Laccesso alle funzionalità complete è riservato agli utenti autenticati. <a routerLink="/login">Accedi</a> per iniziare a utilizzare il sistema.
</p> </p>
}
</section> </section>

View File

@ -0,0 +1,65 @@
// Variabili Bootstrap Italia già disponibili se importi bootstrap-italia
$primary: var(--bs-primary);
$gray-700: var(--bs-gray-700);
$gray-600: var(--bs-gray-600);
$gray-300: var(--bs-gray-300);
.home-content {
max-width: 960px;
margin: 0 auto;
padding: 3rem 1.5rem;
// Titolo principale
&__title {
font-family: var(--bs-font-sans-serif);
font-size: 2.25rem;
font-weight: 600;
line-height: 1.2;
color: $primary;
margin-bottom: 1.5rem;
}
// Sottotitolo / testo introduttivo
&__lead {
font-size: 1.125rem;
line-height: 1.6;
color: $gray-700;
margin-bottom: 2rem;
}
// Testo standard
&__text {
font-size: 1rem;
line-height: 1.6;
color: $gray-600;
margin-bottom: 1rem;
}
// Separatore soft
&__divider {
margin: 2.5rem 0;
border-top: 1px solid $gray-300;
}
// Link istituzionale
a {
color: $primary;
font-weight: 500;
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
}
/* Responsive */
@media (max-width: 768px) {
.home-content {
padding: 2rem 1rem;
&__title {
font-size: 1.75rem;
}
}
}

View File

@ -1,11 +1,19 @@
import { Component } from '@angular/core'; import { Component, computed, inject } from '@angular/core';
import { AuthenticationService } from '../../services/authentication';
import { RouterLink } from '@angular/router';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
imports: [], imports: [RouterLink],
templateUrl: './home.html', templateUrl: './home.html',
styleUrl: './home.css', styleUrl: './home.scss',
}) })
export class HomePage { export class HomePage {
authService = inject(AuthenticationService);
isLoggedIn(){
return this.authService.isLoggedIn();
}
} }

View File

@ -1 +1,47 @@
<p>login works!</p> <div class="it-login-wrapper">
<div class="it-login-card">
<h1 class="it-login-title">Accedi</h1>
<p class="it-login-subtitle">
Inserisci le tue credenziali
</p>
<form (ngSubmit)="submit()">
<!-- Email -->
<div class="it-form-group">
<label for="email" class="it-label">Email</label>
<input
id="email"
type="email"
[formField] = "loginForm.email"
class="it-input"
placeholder="nome.cognome@email.it"
/>
</div>
<!-- Password -->
<div class="it-form-group">
<label for="password" class="it-label">Password</label>
<input
id="password"
type="password"
[formField] = "loginForm.password"
class="it-input"
placeholder="••••••••"
/>
</div>
<!-- Actions -->
<div class="it-actions">
<button
type="submit"
class="it-btn-primary"
[disabled]="loginForm().invalid()"
>
Accedi
</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,81 @@
.it-login-wrapper {
padding-top: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.it-login-card {
width: 100%;
max-width: 420px;
background: #ffffff;
padding: 2.5rem;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
.it-login-title {
font-size: 1.75rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: #17324d;
}
.it-login-subtitle {
font-size: 0.95rem;
color: #5c6f82;
margin-bottom: 2rem;
}
.it-form-group {
display: flex;
flex-direction: column;
margin-bottom: 1.5rem;
}
.it-label {
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: #17324d;
}
.it-input {
height: 44px;
padding: 0 12px;
border-radius: 4px;
border: 1px solid #cbd5e1;
font-size: 0.95rem;
transition: border-color 0.2s ease;
&:focus {
outline: none;
border-color: #0066cc;
}
}
.it-actions {
margin-top: 2rem;
}
.it-btn-primary {
width: 100%;
height: 46px;
border: none;
border-radius: 4px;
background: #0066cc;
color: #ffffff;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s ease;
&:hover:not(:disabled) {
background: #0052a3;
}
&:disabled {
background: #cbd5e1;
cursor: not-allowed;
}
}

View File

@ -1,13 +1,38 @@
import { Component } from '@angular/core'; import { Component, inject, signal, WritableSignal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {form, FormField} from '@angular/forms/signals';
import { ItFormModule } from "design-angular-kit";
import { AuthenticationService } from '../../services/authentication';
import { LoginData } from '../../model/login-data';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
imports: [], imports: [FormsModule, ItFormModule, FormField],
templateUrl: './login.html', templateUrl: './login.html',
styleUrl: './login.css', styleUrl: './login.scss',
}) })
export class LoginPage { export class LoginPage {
authService = inject(AuthenticationService);
router = inject(Router);
loginModel = signal<LoginData>({
email: '',
password: '',
});
loginForm = form(this.loginModel);
submit() {
if (this.loginForm().valid()) {
console.log('Login data:', this.loginModel());
this.authService.login(this.loginModel()).subscribe(() => {
console.log('Login successful');
this.router.navigate(['/home']);
});
}
}
} }

View File

@ -0,0 +1 @@
<app-producers-list></app-producers-list>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Producers } from './producers';
describe('Producers', () => {
let component: Producers;
let fixture: ComponentFixture<Producers>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Producers]
})
.compileComponents();
fixture = TestBed.createComponent(Producers);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,12 @@
import { Component } from '@angular/core';
import { ProducersListComponent } from "../../components/producers-list/producers-list";
@Component({
selector: 'app-producers',
imports: [ProducersListComponent],
templateUrl: './producers.html',
styleUrls: ['./producers.scss'],
})
export class ProducersComponent {
}

View File

@ -1,5 +1,7 @@
import { Injectable, signal } from '@angular/core'; import { Injectable, signal } from '@angular/core';
import { User } from '../model/user'; import { User } from '../model/user';
import { LoginData } from '../model/login-data';
import { Observable } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -8,12 +10,21 @@ export class AuthenticationService {
private user = signal<User | null>(null); private user = signal<User | null>(null);
constructor() {
const storedUser = localStorage.getItem('loggedInUser');
if (storedUser) {
this.user.set(JSON.parse(storedUser));
}
}
loggedInUser = this.user.asReadonly(); login(loginData: LoginData): Observable<void> {
const user = new User('John Doe', loginData.email); // Simulate a user based on login data
login(username: string, password: string) { this.user.set(user);
this.user.set({ username, email: 'test@email.it' }); // Simulate a successful login with a dummy user localStorage.setItem('loggedInUser', JSON.stringify(user));
localStorage.setItem('loggedInUser', JSON.stringify({ username, email: '' })); return new Observable((observer) => {
observer.next();
observer.complete();
});
} }
logout() { logout() {
@ -22,8 +33,10 @@ export class AuthenticationService {
} }
isLoggedIn(): boolean { isLoggedIn(): boolean {
const storedUser = localStorage.getItem('loggedInUser'); return this.user() != null;
this.user.set(storedUser != null ? JSON.parse(storedUser) : null); }
return storedUser != null;
loggedInUser(): User | null {
return this.user();
} }
} }

View File

@ -1,68 +1,5 @@
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
@use 'bootstrap-italia/src/scss/bootstrap-italia'; @use 'bootstrap-italia/src/scss/bootstrap-italia';
// Variabili Bootstrap Italia già disponibili se importi bootstrap-italia
$primary: var(--bs-primary);
$gray-700: var(--bs-gray-700);
$gray-600: var(--bs-gray-600);
$gray-300: var(--bs-gray-300);
.home-content {
max-width: 960px;
margin: 0 auto;
padding: 3rem 1.5rem;
// Titolo principale
&__title {
font-family: var(--bs-font-sans-serif);
font-size: 2.25rem;
font-weight: 600;
line-height: 1.2;
color: $primary;
margin-bottom: 1.5rem;
}
// Sottotitolo / testo introduttivo
&__lead {
font-size: 1.125rem;
line-height: 1.6;
color: $gray-700;
margin-bottom: 2rem;
}
// Testo standard
&__text {
font-size: 1rem;
line-height: 1.6;
color: $gray-600;
margin-bottom: 1rem;
}
// Separatore soft
&__divider {
margin: 2.5rem 0;
border-top: 1px solid $gray-300;
}
// Link istituzionale
a {
color: $primary;
font-weight: 500;
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
}
/* Responsive */
@media (max-width: 768px) {
.home-content {
padding: 2rem 1rem;
&__title {
font-size: 1.75rem;
}
}
}