Sistemato home e keycloak
This commit is contained in:
@@ -59,7 +59,7 @@
|
||||
"serve": {
|
||||
"builder": "@angular/build:dev-server",
|
||||
"options": {
|
||||
"port": 4201
|
||||
"port": 7000
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
.app-shell__brand-name {
|
||||
font-family: var(--mat-sys-headline-small-font, Caprasimo, sans-serif);
|
||||
font-size: 19px;
|
||||
line-height: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.app-shell__spacer {
|
||||
@@ -47,7 +49,7 @@
|
||||
border-radius: 999px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.app-shell__avatar {
|
||||
@@ -56,7 +58,8 @@
|
||||
border-radius: 50%;
|
||||
background: var(--mat-sys-secondary);
|
||||
color: var(--mat-sys-on-secondary);
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
|
||||
@@ -18,13 +18,6 @@ export const routes: Routes = [
|
||||
path: 'profilo',
|
||||
loadChildren: () => import('./profilo/profilo.routes').then((m) => m.PROFILO_ROUTES)
|
||||
},
|
||||
{
|
||||
path: 'richieste-creazione-gruppo',
|
||||
loadChildren: () =>
|
||||
import('./richieste-creazione-gruppo/richieste-creazione-gruppo.routes').then(
|
||||
(m) => m.RICHIESTE_CREAZIONE_GRUPPO_ROUTES
|
||||
)
|
||||
},
|
||||
{
|
||||
// Pubblica: un invito deve poter essere visualizzato anche da chi non ha ancora un account.
|
||||
path: 'inviti',
|
||||
|
||||
@@ -49,3 +49,8 @@
|
||||
.gruppi-list__link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
app-richieste-creazione-gruppo {
|
||||
display: block;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
@@ -32,4 +32,6 @@
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
<app-richieste-creazione-gruppo />
|
||||
</section>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import Keycloak from 'keycloak-js';
|
||||
import { of, throwError } from 'rxjs';
|
||||
|
||||
import { RichiesteCreazioneGruppoApiService } from '../../profilo/richieste-creazione-gruppo-api.service';
|
||||
import { GruppiApiService, GruppoListItem } from '../gruppi-api.service';
|
||||
import { GruppiList } from './gruppi-list';
|
||||
|
||||
@@ -13,7 +17,16 @@ describe('GruppiList', () => {
|
||||
async function setup(): Promise<void> {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GruppiList],
|
||||
providers: [provideRouter([]), { provide: GruppiApiService, useValue: gruppiApi }]
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
{ provide: GruppiApiService, useValue: gruppiApi },
|
||||
// GruppiList incorpora <app-richieste-creazione-gruppo>, che richiede le sue
|
||||
// stesse dipendenze (vedi richieste-creazione-gruppo.spec.ts).
|
||||
{ provide: RichiesteCreazioneGruppoApiService, useValue: { getRichieste: vi.fn().mockReturnValue(of([])) } },
|
||||
{ provide: Keycloak, useValue: { tokenParsed: { realm_access: { roles: ['admin'] } } } },
|
||||
{ provide: MatDialog, useValue: { open: vi.fn() } },
|
||||
{ provide: MatSnackBar, useValue: { open: vi.fn() } }
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GruppiList);
|
||||
|
||||
@@ -4,11 +4,12 @@ import { MatTableModule } from '@angular/material/table';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
import { RichiesteCreazioneGruppo } from '../../richieste-creazione-gruppo/richieste-creazione-gruppo';
|
||||
import { GruppiApiService, GruppoListItem } from '../gruppi-api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-gruppi-list',
|
||||
imports: [MatButtonModule, MatTableModule, RouterLink],
|
||||
imports: [MatButtonModule, MatTableModule, RouterLink, RichiesteCreazioneGruppo],
|
||||
templateUrl: './gruppi-list.html',
|
||||
styleUrl: './gruppi-list.css'
|
||||
})
|
||||
|
||||
@@ -86,32 +86,3 @@
|
||||
font-size: 12px;
|
||||
color: var(--mat-sys-on-primary-container);
|
||||
}
|
||||
|
||||
.amministrazione {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.amministrazione__title {
|
||||
font-family: var(--mat-sys-title-medium-font, Caprasimo, sans-serif);
|
||||
font-size: 19px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.amministrazione__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.amministrazione__link {
|
||||
font-size: 13px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--mat-sys-outline-variant);
|
||||
text-decoration: none;
|
||||
color: var(--mat-sys-on-surface);
|
||||
}
|
||||
|
||||
.amministrazione__link:hover {
|
||||
background: var(--mat-sys-surface-container-high);
|
||||
}
|
||||
|
||||
@@ -31,14 +31,4 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (isAdmin) {
|
||||
<section class="amministrazione">
|
||||
<h2 class="amministrazione__title">Amministrazione</h2>
|
||||
<nav class="amministrazione__links">
|
||||
<a class="amministrazione__link" routerLink="/gruppi">Gruppi scout</a>
|
||||
<a class="amministrazione__link" routerLink="/richieste-creazione-gruppo">Richieste di creazione gruppo</a>
|
||||
</nav>
|
||||
</section>
|
||||
}
|
||||
</section>
|
||||
|
||||
@@ -67,32 +67,4 @@ describe('Home', () => {
|
||||
expect(compiled.querySelector('.servizio-card__avviso')).toBeNull();
|
||||
expect(compiled.querySelector('.servizio-card button')).toBeNull();
|
||||
});
|
||||
|
||||
describe('sezione amministrazione', () => {
|
||||
it('è visibile con i link a /gruppi e /richieste-creazione-gruppo per admin', async () => {
|
||||
keycloak = {
|
||||
authenticated: true,
|
||||
login: vi.fn(),
|
||||
tokenParsed: { realm_access: { roles: ['admin'] } }
|
||||
};
|
||||
await setup();
|
||||
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.textContent).toContain('Amministrazione');
|
||||
const links = Array.from(compiled.querySelectorAll('.amministrazione__links a')) as HTMLAnchorElement[];
|
||||
expect(links.map((a) => a.getAttribute('href'))).toEqual(['/gruppi', '/richieste-creazione-gruppo']);
|
||||
});
|
||||
|
||||
it('non è visibile per un utente senza ruolo admin', async () => {
|
||||
keycloak = {
|
||||
authenticated: true,
|
||||
login: vi.fn(),
|
||||
tokenParsed: { realm_access: { roles: ['censito'] } }
|
||||
};
|
||||
await setup();
|
||||
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.textContent).not.toContain('Amministrazione');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import Keycloak from 'keycloak-js';
|
||||
|
||||
import { environment } from '../../environments/environment';
|
||||
import { extractRealmRoles } from '../core/auth/roles';
|
||||
|
||||
export interface ServizioCard {
|
||||
id: string;
|
||||
@@ -41,7 +39,7 @@ const SERVIZI: ServizioCard[] = [
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
imports: [MatButtonModule, MatCardModule, RouterLink],
|
||||
imports: [MatButtonModule, MatCardModule],
|
||||
templateUrl: './home.html',
|
||||
styleUrl: './home.css'
|
||||
})
|
||||
@@ -54,10 +52,6 @@ export class Home {
|
||||
return this.keycloak.authenticated ?? false;
|
||||
}
|
||||
|
||||
get isAdmin(): boolean {
|
||||
return extractRealmRoles(this.keycloak.tokenParsed).includes('admin');
|
||||
}
|
||||
|
||||
login(): void {
|
||||
this.keycloak.login({ redirectUri: window.location.href });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
.modifica-profilo {
|
||||
max-width: 520px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.modifica-profilo__title {
|
||||
font-family: var(--mat-sys-headline-medium-font, Caprasimo, sans-serif);
|
||||
font-size: 28px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modifica-profilo__card {
|
||||
background: var(--mat-sys-surface-container);
|
||||
border-radius: 32px;
|
||||
padding: 26px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.modifica-profilo__section-title {
|
||||
font-family: var(--mat-sys-title-medium-font, Caprasimo, sans-serif);
|
||||
font-size: 17px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modifica-profilo__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.modifica-profilo__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.modifica-profilo__submit {
|
||||
border-radius: 999px !important;
|
||||
background: var(--mat-sys-primary) !important;
|
||||
color: var(--mat-sys-on-primary) !important;
|
||||
}
|
||||
|
||||
.modifica-profilo__submit--secondary {
|
||||
background: var(--mat-sys-secondary) !important;
|
||||
color: var(--mat-sys-on-secondary) !important;
|
||||
}
|
||||
|
||||
.modifica-profilo__error {
|
||||
color: var(--mat-sys-error);
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.modifica-profilo__conferma {
|
||||
color: var(--mat-sys-primary);
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.modifica-profilo__hint {
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.modifica-profilo__back {
|
||||
align-self: flex-start;
|
||||
border-radius: 999px !important;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<section class="modifica-profilo">
|
||||
<h1 class="modifica-profilo__title">Modifica dati di registrazione</h1>
|
||||
|
||||
@if (caricamento()) {
|
||||
<p class="modifica-profilo__hint">Caricamento…</p>
|
||||
} @else if (caricamentoError(); as message) {
|
||||
<p class="modifica-profilo__error" role="alert">{{ message }}</p>
|
||||
} @else {
|
||||
<div class="modifica-profilo__card">
|
||||
<h2 class="modifica-profilo__section-title">Dati personali</h2>
|
||||
|
||||
<form class="modifica-profilo__form" (submit)="$event.preventDefault(); salvaProfilo()" novalidate>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Nome</mat-label>
|
||||
<input matInput [formControl]="nome" />
|
||||
@if (nome.hasError('required')) {
|
||||
<mat-error>Il nome è obbligatorio.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Cognome</mat-label>
|
||||
<input matInput [formControl]="cognome" />
|
||||
@if (cognome.hasError('required')) {
|
||||
<mat-error>Il cognome è obbligatorio.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Email</mat-label>
|
||||
<input matInput type="email" [formControl]="email" />
|
||||
@if (email.hasError('required')) {
|
||||
<mat-error>L'email è obbligatoria.</mat-error>
|
||||
} @else if (email.hasError('email')) {
|
||||
<mat-error>Inserisci un'email valida.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
@if (salvataggioError(); as message) {
|
||||
<p class="modifica-profilo__error" role="alert">{{ message }}</p>
|
||||
}
|
||||
|
||||
@if (salvataggioOk()) {
|
||||
<p class="modifica-profilo__conferma" role="status">Dati aggiornati con successo.</p>
|
||||
}
|
||||
|
||||
<div class="modifica-profilo__actions">
|
||||
<button class="modifica-profilo__submit" mat-flat-button type="submit" [disabled]="salvataggio()">
|
||||
{{ salvataggio() ? 'Salvataggio…' : 'Salva modifiche' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modifica-profilo__card">
|
||||
<h2 class="modifica-profilo__section-title">Cambia password</h2>
|
||||
|
||||
<form class="modifica-profilo__form" (submit)="$event.preventDefault(); cambiaPassword()" novalidate>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Nuova password</mat-label>
|
||||
<input matInput type="password" [formControl]="nuovaPassword" />
|
||||
@if (nuovaPassword.hasError('required')) {
|
||||
<mat-error>La nuova password è obbligatoria.</mat-error>
|
||||
} @else if (nuovaPassword.hasError('minlength')) {
|
||||
<mat-error>La password deve avere almeno 8 caratteri.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Conferma password</mat-label>
|
||||
<input matInput type="password" [formControl]="confermaPassword" />
|
||||
@if (confermaPassword.hasError('required')) {
|
||||
<mat-error>Conferma la nuova password.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
@if (cambioPasswordError(); as message) {
|
||||
<p class="modifica-profilo__error" role="alert">{{ message }}</p>
|
||||
}
|
||||
|
||||
@if (cambioPasswordOk()) {
|
||||
<p class="modifica-profilo__conferma" role="status">Password aggiornata con successo.</p>
|
||||
}
|
||||
|
||||
<div class="modifica-profilo__actions">
|
||||
<button class="modifica-profilo__submit modifica-profilo__submit--secondary" mat-flat-button type="submit" [disabled]="cambioPassword()">
|
||||
{{ cambioPassword() ? 'Aggiornamento…' : 'Aggiorna password' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
<button class="modifica-profilo__back" mat-stroked-button type="button" (click)="tornaAlProfilo()">
|
||||
Torna al profilo
|
||||
</button>
|
||||
</section>
|
||||
@@ -0,0 +1,132 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { Router } from '@angular/router';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
import { UtenteApiService } from '../utente-api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-modifica-profilo',
|
||||
imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatProgressSpinnerModule],
|
||||
templateUrl: './modifica-profilo.html',
|
||||
styleUrl: './modifica-profilo.css'
|
||||
})
|
||||
export class ModificaProfilo {
|
||||
private readonly utenteApi = inject(UtenteApiService);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
readonly caricamento = signal(true);
|
||||
readonly caricamentoError = signal<string | null>(null);
|
||||
|
||||
readonly email = new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] });
|
||||
readonly nome = new FormControl('', { nonNullable: true, validators: [Validators.required] });
|
||||
readonly cognome = new FormControl('', { nonNullable: true, validators: [Validators.required] });
|
||||
|
||||
readonly salvataggio = signal(false);
|
||||
readonly salvataggioError = signal<string | null>(null);
|
||||
readonly salvataggioOk = signal(false);
|
||||
|
||||
readonly nuovaPassword = new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.minLength(8)] });
|
||||
readonly confermaPassword = new FormControl('', { nonNullable: true, validators: [Validators.required] });
|
||||
readonly cambioPassword = signal(false);
|
||||
readonly cambioPasswordError = signal<string | null>(null);
|
||||
readonly cambioPasswordOk = signal(false);
|
||||
|
||||
constructor() {
|
||||
this.caricaProfilo();
|
||||
}
|
||||
|
||||
private async caricaProfilo(): Promise<void> {
|
||||
try {
|
||||
const profilo = await firstValueFrom(this.utenteApi.getMe());
|
||||
this.email.setValue(profilo.email);
|
||||
this.nome.setValue(profilo.nome);
|
||||
this.cognome.setValue(profilo.cognome);
|
||||
} catch {
|
||||
this.caricamentoError.set('Impossibile caricare i dati del profilo. Riprova più tardi.');
|
||||
} finally {
|
||||
this.caricamento.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
async salvaProfilo(): Promise<void> {
|
||||
if (this.salvataggio()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.email.invalid || this.nome.invalid || this.cognome.invalid) {
|
||||
this.email.markAsTouched();
|
||||
this.nome.markAsTouched();
|
||||
this.cognome.markAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
this.salvataggio.set(true);
|
||||
this.salvataggioError.set(null);
|
||||
this.salvataggioOk.set(false);
|
||||
|
||||
try {
|
||||
await firstValueFrom(
|
||||
this.utenteApi.updateMe({
|
||||
email: this.email.value.trim(),
|
||||
nome: this.nome.value.trim(),
|
||||
cognome: this.cognome.value.trim()
|
||||
})
|
||||
);
|
||||
this.salvataggioOk.set(true);
|
||||
} catch (error) {
|
||||
this.salvataggioError.set(this.messageForProfiloError(error as HttpErrorResponse));
|
||||
} finally {
|
||||
this.salvataggio.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private messageForProfiloError(error: HttpErrorResponse): string {
|
||||
if (error.status === 409) {
|
||||
return 'Esiste già un utente registrato con questa email.';
|
||||
}
|
||||
|
||||
return 'Impossibile salvare le modifiche. Riprova più tardi.';
|
||||
}
|
||||
|
||||
async cambiaPassword(): Promise<void> {
|
||||
if (this.cambioPassword()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.nuovaPassword.invalid || this.confermaPassword.invalid) {
|
||||
this.nuovaPassword.markAsTouched();
|
||||
this.confermaPassword.markAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.nuovaPassword.value !== this.confermaPassword.value) {
|
||||
this.cambioPasswordError.set('Le due password non coincidono.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.cambioPassword.set(true);
|
||||
this.cambioPasswordError.set(null);
|
||||
this.cambioPasswordOk.set(false);
|
||||
|
||||
try {
|
||||
await firstValueFrom(this.utenteApi.updatePassword(this.nuovaPassword.value));
|
||||
this.cambioPasswordOk.set(true);
|
||||
this.nuovaPassword.reset('');
|
||||
this.confermaPassword.reset('');
|
||||
} catch {
|
||||
this.cambioPasswordError.set('Impossibile aggiornare la password. Riprova più tardi.');
|
||||
} finally {
|
||||
this.cambioPassword.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
tornaAlProfilo(): void {
|
||||
this.router.navigateByUrl('/profilo');
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.profilo__account-btn {
|
||||
border-radius: 999px !important;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.profilo-card {
|
||||
max-width: 420px;
|
||||
background: var(--mat-sys-surface-container);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<section class="profilo">
|
||||
<h1 class="profilo__title">Il tuo profilo</h1>
|
||||
|
||||
<button class="profilo__account-btn" mat-stroked-button type="button" routerLink="/profilo/modifica">
|
||||
Modifica dati di registrazione
|
||||
</button>
|
||||
|
||||
@if (hasOrganization()) {
|
||||
<div class="profilo-card">
|
||||
<div class="profilo-card__avatar" [class.profilo-card__avatar--admin]="!ruoloCorrente">
|
||||
@@ -16,6 +20,13 @@
|
||||
{{ ruoloCorrente ?? 'Ruolo non disponibile' }}
|
||||
</span>
|
||||
</div>
|
||||
} @else if (isAdmin) {
|
||||
<div class="profilo-card">
|
||||
<div class="profilo-card__avatar profilo-card__avatar--admin">A</div>
|
||||
<div class="profilo-card__group-label">Ruolo</div>
|
||||
<div class="profilo-card__group-name">Amministratore centrale</div>
|
||||
<span class="profilo-card__badge">admin</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="profilo-paths">
|
||||
<section class="profilo-path">
|
||||
|
||||
@@ -7,5 +7,10 @@ export const PROFILO_ROUTES: Routes = [
|
||||
path: '',
|
||||
loadComponent: () => import('./profilo').then((m) => m.Profilo),
|
||||
canActivate: [requireAuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'modifica',
|
||||
loadComponent: () => import('./modifica-profilo/modifica-profilo').then((m) => m.ModificaProfilo),
|
||||
canActivate: [requireAuthGuard]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import Keycloak from 'keycloak-js';
|
||||
import { Observable, of, throwError } from 'rxjs';
|
||||
|
||||
@@ -33,6 +34,7 @@ describe('Profilo', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Profilo],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
{ provide: Keycloak, useValue: { tokenParsed: { realm_access: { roles: options.roles } } } },
|
||||
{
|
||||
provide: OrganizationContextService,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import Keycloak from 'keycloak-js';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@@ -20,7 +21,7 @@ type GruppoOption = GruppoPubblicoListItem;
|
||||
|
||||
@Component({
|
||||
selector: 'app-profilo',
|
||||
imports: [ReactiveFormsModule, MatButtonModule, MatCardModule, MatFormFieldModule, MatInputModule, MatSelectModule],
|
||||
imports: [ReactiveFormsModule, MatButtonModule, MatCardModule, MatFormFieldModule, MatInputModule, MatSelectModule, RouterLink],
|
||||
templateUrl: './profilo.html',
|
||||
styleUrl: './profilo.css'
|
||||
})
|
||||
@@ -39,6 +40,10 @@ export class Profilo {
|
||||
return RUOLI_ORGANIZZAZIONE.find((ruolo) => roles.includes(ruolo)) ?? null;
|
||||
}
|
||||
|
||||
get isAdmin(): boolean {
|
||||
return extractRealmRoles(this.keycloak.tokenParsed).includes('admin');
|
||||
}
|
||||
|
||||
readonly gruppiDisponibili = signal<GruppoOption[]>([]);
|
||||
readonly caricandoGruppi = signal(true);
|
||||
|
||||
@@ -48,7 +53,9 @@ export class Profilo {
|
||||
readonly richiestaIngressoInviata = signal(false);
|
||||
|
||||
constructor() {
|
||||
this.caricaGruppiDisponibili();
|
||||
if (!this.isAdmin) {
|
||||
this.caricaGruppiDisponibili();
|
||||
}
|
||||
}
|
||||
|
||||
private async caricaGruppiDisponibili(): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
export interface ProfiloUtente {
|
||||
email: string;
|
||||
nome: string;
|
||||
cognome: string;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class UtenteApiService {
|
||||
private readonly http = inject(HttpClient);
|
||||
|
||||
getMe(): Observable<ProfiloUtente> {
|
||||
return this.http.get<ProfiloUtente>(`${environment.orgServiceApiBaseUrl}/me`);
|
||||
}
|
||||
|
||||
updateMe(profilo: ProfiloUtente): Observable<ProfiloUtente> {
|
||||
return this.http.put<ProfiloUtente>(`${environment.orgServiceApiBaseUrl}/me`, profilo);
|
||||
}
|
||||
|
||||
updatePassword(password: string): Observable<void> {
|
||||
return this.http.put<void>(`${environment.orgServiceApiBaseUrl}/me/password`, { password });
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
import { requireAuthGuard } from '../core/require-auth.guard';
|
||||
|
||||
export const RICHIESTE_CREAZIONE_GRUPPO_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () => import('./richieste-creazione-gruppo').then((m) => m.RichiesteCreazioneGruppo),
|
||||
canActivate: [requireAuthGuard]
|
||||
}
|
||||
];
|
||||
Reference in New Issue
Block a user