fix: wrap team creation in a transaction, add more-menu entry point, fix lint
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,15 @@
|
||||
</header>
|
||||
|
||||
<section class="link-grid">
|
||||
<button type="button" class="link-grid-action" (click)="createTeam()">
|
||||
<mat-card
|
||||
><mat-icon>add_circle</mat-icon>
|
||||
<div>
|
||||
<strong>Team erstellen</strong><span>Ein weiteres Team gründen</span>
|
||||
</div>
|
||||
<mat-icon>chevron_right</mat-icon></mat-card
|
||||
>
|
||||
</button>
|
||||
@if (canOpenGuide()) {
|
||||
<a routerLink="guide"
|
||||
><mat-card
|
||||
|
||||
@@ -26,11 +26,23 @@ h1 {
|
||||
gap: 14px;
|
||||
margin: 28px 0;
|
||||
}
|
||||
a {
|
||||
a,
|
||||
.link-grid-action {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
a mat-card {
|
||||
.link-grid-action {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
a mat-card,
|
||||
.link-grid-action mat-card {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
@@ -41,19 +53,23 @@ a mat-card {
|
||||
transform 150ms ease,
|
||||
box-shadow 150ms ease;
|
||||
}
|
||||
a:hover mat-card {
|
||||
a:hover mat-card,
|
||||
.link-grid-action:hover mat-card {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--mat-sys-level2);
|
||||
}
|
||||
a div,
|
||||
.link-grid-action div,
|
||||
.account-card div {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
}
|
||||
a strong {
|
||||
a strong,
|
||||
.link-grid-action strong {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
a span,
|
||||
.link-grid-action span,
|
||||
.account-card span {
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideRouter, Router } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AuthStore } from '../../../core/auth/auth-store';
|
||||
import { HelpAccessService } from '../../../core/help/help-access';
|
||||
import { MyTeamsStore } from '../../../core/team/my-teams-store';
|
||||
import { TeamStore } from '../../../core/team/team-store';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { More } from './more';
|
||||
|
||||
@Component({ template: '' })
|
||||
@@ -15,6 +21,9 @@ describe('More', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [More],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{ provide: MatDialog, useValue: { open: vi.fn() } },
|
||||
provideRouter([{ path: 'auth/login', component: LoginStub }]),
|
||||
{
|
||||
provide: AuthStore,
|
||||
@@ -49,6 +58,9 @@ describe('More', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [More],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{ provide: MatDialog, useValue: { open: vi.fn() } },
|
||||
provideRouter([]),
|
||||
{
|
||||
provide: AuthStore,
|
||||
@@ -88,6 +100,9 @@ describe('More', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [More],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
{ provide: MatDialog, useValue: { open: vi.fn() } },
|
||||
provideRouter([]),
|
||||
{
|
||||
provide: AuthStore,
|
||||
@@ -111,4 +126,45 @@ describe('More', () => {
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('Berechtigungen');
|
||||
});
|
||||
|
||||
it('opens the create-team dialog and navigates into the newly created team on success', async () => {
|
||||
const dialogClosed = new Subject<
|
||||
{ id: number; name: string; alias: string; balance: number } | undefined
|
||||
>();
|
||||
const dialog = { open: vi.fn(() => ({ afterClosed: () => dialogClosed.asObservable() })) };
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [More],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
provideRouter([]),
|
||||
{
|
||||
provide: AuthStore,
|
||||
useValue: {
|
||||
currentUser: signal({ id: 42, firstName: 'Alex', lastName: 'Muster', email: 'a@b.de' }),
|
||||
clearSession: vi.fn(),
|
||||
},
|
||||
},
|
||||
{ provide: HelpAccessService, useValue: { canOpenGuide: signal(false) } },
|
||||
{ provide: TeamStore, useValue: { team: signal(null) } },
|
||||
{ provide: MatDialog, useValue: dialog },
|
||||
],
|
||||
}).compileComponents();
|
||||
const router = TestBed.inject(Router);
|
||||
const navigateSpy = vi.spyOn(router, 'navigate');
|
||||
const myTeamsStore = TestBed.inject(MyTeamsStore);
|
||||
const refreshSpy = vi.spyOn(myTeamsStore, 'refresh');
|
||||
const fixture = TestBed.createComponent(More);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance['createTeam']();
|
||||
expect(dialog.open).toHaveBeenCalled();
|
||||
|
||||
dialogClosed.next({ id: 9, name: '1. Herren', alias: 'a', balance: 0 });
|
||||
|
||||
expect(refreshSpy).toHaveBeenCalledWith(42);
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['/team', 9, 'overview']);
|
||||
|
||||
TestBed.inject(HttpTestingController).expectOne(`${environment.apiUrl}users/42/teams`).flush([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,11 +2,15 @@ import { Component, computed, inject } from '@angular/core';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { AuthStore } from '../../../core/auth/auth-store';
|
||||
import { HelpAccessService } from '../../../core/help/help-access';
|
||||
import { MyTeamsStore } from '../../../core/team/my-teams-store';
|
||||
import { TeamPermissionsService } from '../../../core/team/team-permissions';
|
||||
import { TeamStore } from '../../../core/team/team-store';
|
||||
import { Team } from '../../../models/team.model';
|
||||
import { CreateTeamDialog } from '../../team-select/create-team-dialog/create-team-dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-more',
|
||||
@@ -16,7 +20,9 @@ import { TeamStore } from '../../../core/team/team-store';
|
||||
})
|
||||
export class More {
|
||||
private readonly authStore = inject(AuthStore);
|
||||
private readonly dialog = inject(MatDialog);
|
||||
private readonly helpAccess = inject(HelpAccessService);
|
||||
private readonly myTeamsStore = inject(MyTeamsStore);
|
||||
private readonly permissions = inject(TeamPermissionsService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly teamStore = inject(TeamStore);
|
||||
@@ -30,4 +36,19 @@ export class More {
|
||||
this.authStore.clearSession();
|
||||
void this.router.navigateByUrl('/auth/login');
|
||||
}
|
||||
|
||||
protected createTeam(): void {
|
||||
this.dialog
|
||||
.open(CreateTeamDialog)
|
||||
.afterClosed()
|
||||
.subscribe((team: Team | undefined) => {
|
||||
if (!team) return;
|
||||
|
||||
const userId = this.authStore.currentUser()?.id;
|
||||
if (userId) {
|
||||
this.myTeamsStore.refresh(userId);
|
||||
}
|
||||
void this.router.navigate(['/team', team.id, 'overview']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user