Fix grafica scouthub-home-fe
This commit is contained in:
@@ -1,7 +1,30 @@
|
||||
.crea-gruppo {
|
||||
max-width: 480px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.crea-gruppo__card {
|
||||
width: min(440px, 100%);
|
||||
background: var(--mat-sys-surface-container);
|
||||
border-radius: 32px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 12px 32px rgba(46, 43, 37, 0.16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 13px;
|
||||
}
|
||||
|
||||
.crea-gruppo__title {
|
||||
font-family: var(--mat-sys-headline-small-font, Caprasimo, sans-serif);
|
||||
font-size: 22px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.crea-gruppo__intro {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.crea-gruppo__form {
|
||||
@@ -15,6 +38,19 @@
|
||||
}
|
||||
|
||||
.crea-gruppo__error {
|
||||
color: var(--mat-sys-error, #b3261e);
|
||||
color: var(--mat-sys-error);
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.crea-gruppo__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.crea-gruppo__submit {
|
||||
border-radius: 999px !important;
|
||||
background: var(--mat-sys-primary) !important;
|
||||
color: var(--mat-sys-on-primary) !important;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
<section class="crea-gruppo">
|
||||
<h1>Crea un nuovo gruppo scout</h1>
|
||||
<p>Il tuo account non è ancora collegato a nessun gruppo scout: creane uno per continuare.</p>
|
||||
<div class="crea-gruppo__card">
|
||||
@if (isAdmin) {
|
||||
<h1 class="crea-gruppo__title">Crea nuovo gruppo</h1>
|
||||
<p class="crea-gruppo__intro">Il tuo account non è ancora collegato a nessun gruppo scout: creane uno per continuare.</p>
|
||||
|
||||
<form class="crea-gruppo__form" (submit)="$event.preventDefault(); submit()" novalidate>
|
||||
<mat-form-field appearance="outline" class="crea-gruppo__field">
|
||||
<mat-label>Nome del gruppo scout</mat-label>
|
||||
<input matInput [formControl]="nome" placeholder="Es. Agesci Milano 1" />
|
||||
@if (nome.hasError('required')) {
|
||||
<mat-error>Il nome del gruppo è obbligatorio.</mat-error>
|
||||
} @else if (nome.hasError('minlength')) {
|
||||
<mat-error>Il nome deve avere almeno 3 caratteri.</mat-error>
|
||||
} @else if (nome.hasError('maxlength')) {
|
||||
<mat-error>Il nome non può superare i 100 caratteri.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<form class="crea-gruppo__form" (submit)="$event.preventDefault(); submit()" novalidate>
|
||||
<mat-form-field appearance="outline" class="crea-gruppo__field">
|
||||
<mat-label>Nome del gruppo scout</mat-label>
|
||||
<input matInput [formControl]="nome" placeholder="Es. Agesci Milano 1" />
|
||||
@if (nome.hasError('required')) {
|
||||
<mat-error>Il nome del gruppo è obbligatorio.</mat-error>
|
||||
} @else if (nome.hasError('minlength')) {
|
||||
<mat-error>Il nome deve avere almeno 3 caratteri.</mat-error>
|
||||
} @else if (nome.hasError('maxlength')) {
|
||||
<mat-error>Il nome non può superare i 100 caratteri.</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
@if (errorMessage(); as message) {
|
||||
<p class="crea-gruppo__error" role="alert">{{ message }}</p>
|
||||
@if (errorMessage(); as message) {
|
||||
<p class="crea-gruppo__error" role="alert">{{ message }}</p>
|
||||
}
|
||||
|
||||
<div class="crea-gruppo__actions">
|
||||
<button class="crea-gruppo__submit" mat-flat-button type="submit" [disabled]="submitting()">
|
||||
{{ submitting() ? 'Creazione in corso…' : 'Crea gruppo' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
} @else {
|
||||
<h1 class="crea-gruppo__title">Accesso non consentito</h1>
|
||||
<p role="alert">Questa pagina è riservata agli utenti con ruolo admin.</p>
|
||||
}
|
||||
|
||||
<button mat-flat-button color="primary" type="submit" [disabled]="submitting()">
|
||||
{{ submitting() ? 'Creazione in corso…' : 'Crea gruppo' }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -11,13 +11,12 @@ describe('CreaGruppo', () => {
|
||||
let component: CreaGruppo;
|
||||
let fixture: ComponentFixture<CreaGruppo>;
|
||||
let gruppiApi: { creaGruppo: ReturnType<typeof vi.fn> };
|
||||
let keycloak: { updateToken: ReturnType<typeof vi.fn> };
|
||||
let router: { navigateByUrl: ReturnType<typeof vi.fn> };
|
||||
|
||||
beforeEach(async () => {
|
||||
async function setup(roles: string[] = ['admin']): Promise<void> {
|
||||
gruppiApi = { creaGruppo: vi.fn() };
|
||||
keycloak = { updateToken: vi.fn().mockResolvedValue(true) };
|
||||
router = { navigateByUrl: vi.fn().mockResolvedValue(true) };
|
||||
const keycloak = { tokenParsed: { realm_access: { roles } } };
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CreaGruppo],
|
||||
@@ -31,13 +30,18 @@ describe('CreaGruppo', () => {
|
||||
fixture = TestBed.createComponent(CreaGruppo);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
}
|
||||
|
||||
it('si crea correttamente', () => {
|
||||
it('si crea correttamente', async () => {
|
||||
await setup();
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('validazione del campo nome', () => {
|
||||
beforeEach(async () => {
|
||||
await setup();
|
||||
});
|
||||
|
||||
it('è invalido se vuoto (required)', () => {
|
||||
component.nome.setValue('');
|
||||
expect(component.nome.hasError('required')).toBe(true);
|
||||
@@ -79,6 +83,10 @@ describe('CreaGruppo', () => {
|
||||
});
|
||||
|
||||
describe('gestione errore 409 (nome già esistente)', () => {
|
||||
beforeEach(async () => {
|
||||
await setup();
|
||||
});
|
||||
|
||||
it('mostra il messaggio del backend e permette di correggere il nome senza ricaricare la pagina', async () => {
|
||||
component.nome.setValue('Agesci Milano 1');
|
||||
gruppiApi.creaGruppo.mockReturnValue(
|
||||
@@ -113,17 +121,40 @@ describe('CreaGruppo', () => {
|
||||
});
|
||||
|
||||
describe('redirect dopo il successo', () => {
|
||||
it('aggiorna forzatamente il token e reindirizza alla home dopo la creazione del gruppo', async () => {
|
||||
beforeEach(async () => {
|
||||
await setup();
|
||||
});
|
||||
|
||||
it('reindirizza alla home dopo la creazione del gruppo', async () => {
|
||||
component.nome.setValue('Agesci Milano 1');
|
||||
gruppiApi.creaGruppo.mockReturnValue(of({ orgId: 'org-1', gruppiCreati: ['Capi'] }));
|
||||
|
||||
await component.submit();
|
||||
|
||||
expect(gruppiApi.creaGruppo).toHaveBeenCalledWith('Agesci Milano 1');
|
||||
expect(keycloak.updateToken).toHaveBeenCalledWith(-1);
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/');
|
||||
expect(component.submitting()).toBe(false);
|
||||
expect(component.errorMessage()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('controllo del ruolo admin', () => {
|
||||
beforeEach(async () => {
|
||||
await setup(['censito']);
|
||||
});
|
||||
|
||||
it('mostra il messaggio di accesso non consentito invece del form', () => {
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('form')).toBeNull();
|
||||
expect(compiled.textContent).toContain('Accesso non consentito');
|
||||
});
|
||||
|
||||
it('non chiama l\'API se l\'utente non ha il ruolo admin', async () => {
|
||||
component.nome.setValue('Agesci Milano 1');
|
||||
|
||||
await component.submit();
|
||||
|
||||
expect(gruppiApi.creaGruppo).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,7 +30,15 @@ export class CreaGruppo {
|
||||
readonly submitting = signal(false);
|
||||
readonly errorMessage = signal<string | null>(null);
|
||||
|
||||
get isAdmin(): boolean {
|
||||
return (this.keycloak.tokenParsed?.realm_access?.roles ?? []).includes('admin');
|
||||
}
|
||||
|
||||
async submit(): Promise<void> {
|
||||
if (!this.isAdmin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.submitting()) {
|
||||
return;
|
||||
}
|
||||
@@ -45,9 +53,6 @@ export class CreaGruppo {
|
||||
|
||||
try {
|
||||
await firstValueFrom(this.gruppiApi.creaGruppo(this.nome.value.trim()));
|
||||
// Il gruppo è appena stato creato su Keycloak: il token corrente non contiene ancora
|
||||
// il claim "organization" aggiornato, va quindi forzato un refresh prima di navigare.
|
||||
await this.keycloak.updateToken(-1).catch(() => undefined);
|
||||
this.submitting.set(false);
|
||||
await this.router.navigateByUrl('/');
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user