Compare commits
7 Commits
09da67b8ef
...
6531f2553f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6531f2553f | ||
|
|
b03198baa6 | ||
|
|
bb6b045a2a | ||
|
|
a05ffd0c7f | ||
|
|
886e4e6941 | ||
|
|
c1478f07f1 | ||
|
|
92eacbc5bb |
38
myteamwallet_backend/src/teams/teams.controller.spec.ts
Normal file
38
myteamwallet_backend/src/teams/teams.controller.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { GUARDS_METADATA } from '@nestjs/common/constants';
|
||||||
|
import { RoleEnum } from '../roles/roles.enum';
|
||||||
|
import { RolesGuard } from '../roles/roles.guard';
|
||||||
|
import { TeamsController } from './teams.controller';
|
||||||
|
|
||||||
|
describe('TeamsController', () => {
|
||||||
|
const service = { createNewTeam: jest.fn() };
|
||||||
|
const publicAccess = {};
|
||||||
|
const teamMembers = {};
|
||||||
|
const teamPermissions = {};
|
||||||
|
const controller = new TeamsController(
|
||||||
|
service as any,
|
||||||
|
publicAccess as any,
|
||||||
|
teamMembers as any,
|
||||||
|
teamPermissions as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => jest.clearAllMocks());
|
||||||
|
|
||||||
|
it('allows any logged-in user (not just admins) to create a team', () => {
|
||||||
|
expect(Reflect.getMetadata('roles', TeamsController.prototype.create)).toEqual([
|
||||||
|
RoleEnum.user,
|
||||||
|
RoleEnum.admin,
|
||||||
|
]);
|
||||||
|
expect(
|
||||||
|
Reflect.getMetadata(GUARDS_METADATA, TeamsController.prototype.create),
|
||||||
|
).toContain(RolesGuard);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the authenticated user and the DTO to the service', () => {
|
||||||
|
const req = { user: { id: 42 } };
|
||||||
|
const dto = { name: '1. Herren' };
|
||||||
|
|
||||||
|
void controller.create(req as any, dto as any);
|
||||||
|
|
||||||
|
expect(service.createNewTeam).toHaveBeenCalledWith(dto, 42);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -244,7 +244,7 @@ export class TeamsController {
|
|||||||
'Erstellt ein neues Team mit gegebenem Namen und gibt es zurück.',
|
'Erstellt ein neues Team mit gegebenem Namen und gibt es zurück.',
|
||||||
})
|
})
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@Roles([RoleEnum.admin])
|
@Roles([RoleEnum.user, RoleEnum.admin])
|
||||||
@UseGuards(AuthGuard('jwt'), RolesGuard)
|
@UseGuards(AuthGuard('jwt'), RolesGuard)
|
||||||
@Post()
|
@Post()
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ describe('TeamsService#getOverviewStats theoretical balance', () => {
|
|||||||
{} as any,
|
{} as any,
|
||||||
{ info: jest.fn(), debug: jest.fn(), warn: jest.fn() } as any,
|
{ info: jest.fn(), debug: jest.fn(), warn: jest.fn() } as any,
|
||||||
access as any,
|
access as any,
|
||||||
|
{} as any,
|
||||||
|
{} as any,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -286,6 +288,8 @@ describe('TeamsService#getTeamTransactionsJournal', () => {
|
|||||||
{} as any,
|
{} as any,
|
||||||
{ info: jest.fn(), debug: jest.fn(), warn: jest.fn() } as any,
|
{ info: jest.fn(), debug: jest.fn(), warn: jest.fn() } as any,
|
||||||
access as any,
|
access as any,
|
||||||
|
{} as any,
|
||||||
|
{} as any,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -362,3 +366,101 @@ describe('TeamsService#getTeamTransactionsJournal', () => {
|
|||||||
expect(repository.findOneOrFail).not.toHaveBeenCalled();
|
expect(repository.findOneOrFail).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('TeamsService#createNewTeam', () => {
|
||||||
|
const repository = { create: jest.fn(), save: jest.fn() };
|
||||||
|
const playerRepository = { create: jest.fn(), save: jest.fn() };
|
||||||
|
const rolesRepository = { findOneBy: jest.fn() };
|
||||||
|
const settingsRepository = { create: jest.fn(), save: jest.fn() };
|
||||||
|
const usersRepository = { findOneBy: jest.fn() };
|
||||||
|
const logger = { info: jest.fn(), debug: jest.fn(), warn: jest.fn() };
|
||||||
|
let manager: any;
|
||||||
|
let dataSource: any;
|
||||||
|
let service: TeamsService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
manager = {
|
||||||
|
getRepository: jest.fn((entity: { name: string }) => {
|
||||||
|
switch (entity.name) {
|
||||||
|
case 'Team':
|
||||||
|
return repository;
|
||||||
|
case 'Player':
|
||||||
|
return playerRepository;
|
||||||
|
case 'TeamRole':
|
||||||
|
return rolesRepository;
|
||||||
|
case 'TeamSetting':
|
||||||
|
return settingsRepository;
|
||||||
|
case 'User':
|
||||||
|
return usersRepository;
|
||||||
|
default:
|
||||||
|
throw new Error(`unexpected entity ${entity.name}`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
dataSource = { transaction: jest.fn((work: any) => work(manager)) };
|
||||||
|
service = new TeamsService(
|
||||||
|
{} as any,
|
||||||
|
{} as any,
|
||||||
|
{} as any,
|
||||||
|
{} as any,
|
||||||
|
{} as any,
|
||||||
|
{} as any,
|
||||||
|
logger as any,
|
||||||
|
{} as any,
|
||||||
|
{} as any,
|
||||||
|
dataSource as any,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates the team, its default settings, and a captain membership for the creator, all inside one transaction', async () => {
|
||||||
|
const savedTeam = { id: 7, name: '1. Herren' };
|
||||||
|
repository.create.mockReturnValue({ name: '1. Herren' });
|
||||||
|
repository.save.mockResolvedValue(savedTeam);
|
||||||
|
settingsRepository.create.mockImplementation((s: unknown) => s);
|
||||||
|
settingsRepository.save.mockResolvedValue([]);
|
||||||
|
usersRepository.findOneBy.mockResolvedValue({
|
||||||
|
id: 42,
|
||||||
|
firstName: 'Alex',
|
||||||
|
lastName: 'Muster',
|
||||||
|
});
|
||||||
|
rolesRepository.findOneBy.mockResolvedValue({ id: 3, name: 'captain' });
|
||||||
|
playerRepository.create.mockImplementation((p: unknown) => p);
|
||||||
|
playerRepository.save.mockResolvedValue({});
|
||||||
|
|
||||||
|
const result = await service.createNewTeam({ name: '1. Herren' } as any, '42');
|
||||||
|
|
||||||
|
expect(dataSource.transaction).toHaveBeenCalledTimes(1);
|
||||||
|
expect(result).toBe(savedTeam);
|
||||||
|
expect(rolesRepository.findOneBy).toHaveBeenCalledWith({ id: 3 });
|
||||||
|
expect(usersRepository.findOneBy).toHaveBeenCalledWith({ id: 42 });
|
||||||
|
expect(playerRepository.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
firstName: 'Alex',
|
||||||
|
lastName: 'Muster',
|
||||||
|
team: savedTeam,
|
||||||
|
teamRole: { id: 3, name: 'captain' },
|
||||||
|
user: { id: 42, firstName: 'Alex', lastName: 'Muster' },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(playerRepository.save).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not create the team at all if the captain-membership write fails', async () => {
|
||||||
|
const savedTeam = { id: 7, name: '1. Herren' };
|
||||||
|
repository.create.mockReturnValue({ name: '1. Herren' });
|
||||||
|
repository.save.mockResolvedValue(savedTeam);
|
||||||
|
settingsRepository.create.mockImplementation((s: unknown) => s);
|
||||||
|
settingsRepository.save.mockResolvedValue([]);
|
||||||
|
usersRepository.findOneBy.mockResolvedValue({ id: 42, firstName: 'Alex', lastName: 'Muster' });
|
||||||
|
rolesRepository.findOneBy.mockResolvedValue({ id: 3, name: 'captain' });
|
||||||
|
playerRepository.create.mockImplementation((p: unknown) => p);
|
||||||
|
playerRepository.save.mockRejectedValue(new Error('db down'));
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.createNewTeam({ name: '1. Herren' } as any, '42'),
|
||||||
|
).rejects.toThrow('db down');
|
||||||
|
|
||||||
|
expect(logger.info).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import { TeamSetting } from 'src/team-settings/entities/team-setting.entity';
|
|||||||
import { TEAM_SETTING_DEFAULTS } from 'src/team-settings/team-setting-defaults';
|
import { TEAM_SETTING_DEFAULTS } from 'src/team-settings/team-setting-defaults';
|
||||||
import { TeamWalletTransaction } from 'src/team-wallet-transactions/entities/team-wallet-transaction.entity';
|
import { TeamWalletTransaction } from 'src/team-wallet-transactions/entities/team-wallet-transaction.entity';
|
||||||
import { Transaction } from 'src/transactions/entitites/transaction.entity';
|
import { Transaction } from 'src/transactions/entitites/transaction.entity';
|
||||||
import { Repository } from 'typeorm';
|
import { TeamRolesEnum } from 'src/team-roles/team-roles.enum';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { DataSource, EntityManager, Repository } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
TransactionsQueryDto,
|
TransactionsQueryDto,
|
||||||
TransactionsSortableField,
|
TransactionsSortableField,
|
||||||
@@ -46,6 +48,9 @@ export class TeamsService {
|
|||||||
private teamWalletTransactionRepository: Repository<TeamWalletTransaction>,
|
private teamWalletTransactionRepository: Repository<TeamWalletTransaction>,
|
||||||
private logger: LoggingService,
|
private logger: LoggingService,
|
||||||
private access: TeamAccessService,
|
private access: TeamAccessService,
|
||||||
|
@InjectRepository(User)
|
||||||
|
private usersRepository: Repository<User>,
|
||||||
|
private dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getOverview(teamId: string, actorUserId: string) {
|
async getOverview(teamId: string, actorUserId: string) {
|
||||||
@@ -164,10 +169,16 @@ export class TeamsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createNewTeam(teamdto: CreateTeamDTO, userId: string) {
|
async createNewTeam(teamdto: CreateTeamDTO, userId: string) {
|
||||||
const createTeam = this.repository.create(teamdto);
|
const team = await this.dataSource.transaction(async (manager) => {
|
||||||
|
const teamRepository = manager.getRepository(Team);
|
||||||
|
const createTeam = teamRepository.create(teamdto);
|
||||||
|
const savedTeam = await teamRepository.save(createTeam);
|
||||||
|
|
||||||
const team = await this.repository.save(createTeam);
|
await this.generateBasicTeamSettings(manager, savedTeam);
|
||||||
await this.generateBasicTeamSettings(team);
|
await this.addCreatorAsCaptain(manager, savedTeam, userId);
|
||||||
|
|
||||||
|
return savedTeam;
|
||||||
|
});
|
||||||
|
|
||||||
await this.logger.info({
|
await this.logger.info({
|
||||||
event: 'team_create',
|
event: 'team_create',
|
||||||
@@ -177,7 +188,33 @@ export class TeamsService {
|
|||||||
return team;
|
return team;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateBasicTeamSettings(team: Team): Promise<void> {
|
private async addCreatorAsCaptain(
|
||||||
|
manager: EntityManager,
|
||||||
|
team: Team,
|
||||||
|
userId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const [user, captainRole] = await Promise.all([
|
||||||
|
manager.getRepository(User).findOneBy({ id: Number(userId) }),
|
||||||
|
manager.getRepository(TeamRole).findOneBy({ id: TeamRolesEnum.captain }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const playerRepository = manager.getRepository(Player);
|
||||||
|
const player = playerRepository.create({
|
||||||
|
firstName: user?.firstName ?? '',
|
||||||
|
lastName: user?.lastName ?? '',
|
||||||
|
team,
|
||||||
|
teamRole: captainRole,
|
||||||
|
user: user ?? undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
await playerRepository.save(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async generateBasicTeamSettings(
|
||||||
|
manager: EntityManager,
|
||||||
|
team: Team,
|
||||||
|
): Promise<void> {
|
||||||
|
const settingsRepository = manager.getRepository(TeamSetting);
|
||||||
const settings: CreateTeamSettingDTO[] = Object.entries(
|
const settings: CreateTeamSettingDTO[] = Object.entries(
|
||||||
TEAM_SETTING_DEFAULTS,
|
TEAM_SETTING_DEFAULTS,
|
||||||
).map(([key, value]) => ({
|
).map(([key, value]) => ({
|
||||||
@@ -186,8 +223,8 @@ export class TeamsService {
|
|||||||
team,
|
team,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await this.settingsRepository.save(
|
await settingsRepository.save(
|
||||||
settings.map((s) => this.settingsRepository.create(s)),
|
settings.map((s) => settingsRepository.create(s)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,4 +58,18 @@ describe('MyTeamsStore', () => {
|
|||||||
expect(store.loading()).toBe(false);
|
expect(store.loading()).toBe(false);
|
||||||
expect(store.players()).toEqual([]);
|
expect(store.players()).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('refresh always reloads, even for a user id already loaded', () => {
|
||||||
|
store.ensureLoaded(42);
|
||||||
|
httpMock.expectOne(`${environment.apiUrl}users/42/teams`).flush([player]);
|
||||||
|
|
||||||
|
store.refresh(42);
|
||||||
|
expect(store.loading()).toBe(true);
|
||||||
|
|
||||||
|
const secondPlayer = { ...player, id: 2, team: { id: 6, name: 'Team B' } };
|
||||||
|
httpMock.expectOne(`${environment.apiUrl}users/42/teams`).flush([player, secondPlayer]);
|
||||||
|
|
||||||
|
expect(store.loading()).toBe(false);
|
||||||
|
expect(store.players()).toEqual([player, secondPlayer]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ export class MyTeamsStore {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.fetch(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(userId: number): void {
|
||||||
|
this.fetch(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetch(userId: number): void {
|
||||||
this.loadingSignal.set(true);
|
this.loadingSignal.set(true);
|
||||||
this.teamsApi.loadMyTeams(userId).subscribe({
|
this.teamsApi.loadMyTeams(userId).subscribe({
|
||||||
next: (players) => {
|
next: (players) => {
|
||||||
|
|||||||
@@ -92,4 +92,13 @@ describe('TeamsApi', () => {
|
|||||||
expect(request.request.body).toEqual({ teamRoleId: 4 });
|
expect(request.request.body).toEqual({ teamRoleId: 4 });
|
||||||
request.flush({ id: 7, teamRole: { id: 4 } });
|
request.flush({ id: 7, teamRole: { id: 4 } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('creates a team', () => {
|
||||||
|
const request = { name: '1. Herren' };
|
||||||
|
service.createTeam(request).subscribe();
|
||||||
|
const req = httpMock.expectOne(`${environment.apiUrl}teams`);
|
||||||
|
expect(req.request.method).toBe('POST');
|
||||||
|
expect(req.request.body).toEqual(request);
|
||||||
|
req.flush({ id: 7, name: '1. Herren', alias: 'a', balance: 0 });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ export interface CreatePlayerRequest {
|
|||||||
teamRole: number;
|
teamRole: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateTeamRequest {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TeamsApi {
|
export class TeamsApi {
|
||||||
private readonly http = inject(HttpClient);
|
private readonly http = inject(HttpClient);
|
||||||
@@ -25,6 +29,10 @@ export class TeamsApi {
|
|||||||
return this.http.get<Team>(`${environment.apiUrl}teams/${teamId}/overview`);
|
return this.http.get<Team>(`${environment.apiUrl}teams/${teamId}/overview`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createTeam(request: CreateTeamRequest): Observable<Team> {
|
||||||
|
return this.http.post<Team>(`${environment.apiUrl}teams`, request);
|
||||||
|
}
|
||||||
|
|
||||||
createPlayer(teamId: number, player: CreatePlayerRequest): Observable<Player> {
|
createPlayer(teamId: number, player: CreatePlayerRequest): Observable<Player> {
|
||||||
return this.http.post<Player>(`${environment.apiUrl}teams/${teamId}/players`, player);
|
return this.http.post<Player>(`${environment.apiUrl}teams/${teamId}/players`, player);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||||
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { CreateTeamDialog } from './create-team-dialog';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
|
||||||
|
describe('CreateTeamDialog', () => {
|
||||||
|
let dialogRef: { close: ReturnType<typeof vi.fn> };
|
||||||
|
let httpMock: HttpTestingController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
dialogRef = { close: vi.fn() };
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CreateTeamDialog],
|
||||||
|
providers: [
|
||||||
|
provideHttpClient(),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
{ provide: MatDialogRef, useValue: dialogRef },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
httpMock = TestBed.inject(HttpTestingController);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
httpMock.verify();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the submit button disabled until a team name is entered', () => {
|
||||||
|
const fixture = TestBed.createComponent(CreateTeamDialog);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.componentInstance['form'].invalid).toBe(true);
|
||||||
|
|
||||||
|
fixture.componentInstance['form'].controls.name.setValue('1. Herren');
|
||||||
|
expect(fixture.componentInstance['form'].invalid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates the team and closes the dialog with the created team', () => {
|
||||||
|
const fixture = TestBed.createComponent(CreateTeamDialog);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.componentInstance['form'].controls.name.setValue('1. Herren');
|
||||||
|
fixture.componentInstance['submit']();
|
||||||
|
|
||||||
|
const request = httpMock.expectOne(`${environment.apiUrl}teams`);
|
||||||
|
expect(request.request.body).toEqual({ name: '1. Herren' });
|
||||||
|
request.flush({ id: 9, name: '1. Herren', alias: 'a', balance: 0 });
|
||||||
|
|
||||||
|
expect(dialogRef.close).toHaveBeenCalledWith({
|
||||||
|
id: 9,
|
||||||
|
name: '1. Herren',
|
||||||
|
alias: 'a',
|
||||||
|
balance: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('re-enables the form and keeps the dialog open when the request fails', () => {
|
||||||
|
const fixture = TestBed.createComponent(CreateTeamDialog);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.componentInstance['form'].controls.name.setValue('1. Herren');
|
||||||
|
fixture.componentInstance['submit']();
|
||||||
|
|
||||||
|
httpMock
|
||||||
|
.expectOne(`${environment.apiUrl}teams`)
|
||||||
|
.flush('error', { status: 500, statusText: 'Server Error' });
|
||||||
|
|
||||||
|
expect(dialogRef.close).not.toHaveBeenCalled();
|
||||||
|
expect(fixture.componentInstance['saving']()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Component, inject, signal } from '@angular/core';
|
||||||
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { Team } from '../../../models/team.model';
|
||||||
|
import { TeamsApi } from '../../../core/team/teams-api';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-create-team-dialog',
|
||||||
|
imports: [ReactiveFormsModule, MatButtonModule, MatDialogModule, MatFormFieldModule, MatInputModule],
|
||||||
|
template: `
|
||||||
|
<h2 mat-dialog-title>Team erstellen</h2>
|
||||||
|
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||||
|
<mat-dialog-content>
|
||||||
|
<mat-form-field appearance="outline" class="full-width">
|
||||||
|
<mat-label>Teamname</mat-label>
|
||||||
|
<input matInput formControlName="name" />
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-button type="button" (click)="dialogRef.close()">Abbrechen</button>
|
||||||
|
<button mat-flat-button type="submit" [disabled]="form.invalid || saving()">
|
||||||
|
Erstellen
|
||||||
|
</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
</form>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CreateTeamDialog {
|
||||||
|
protected readonly dialogRef = inject(MatDialogRef<CreateTeamDialog, Team | undefined>);
|
||||||
|
private readonly formBuilder = inject(FormBuilder);
|
||||||
|
private readonly teamsApi = inject(TeamsApi);
|
||||||
|
|
||||||
|
protected readonly saving = signal(false);
|
||||||
|
protected readonly form = this.formBuilder.nonNullable.group({
|
||||||
|
name: ['', Validators.required],
|
||||||
|
});
|
||||||
|
|
||||||
|
protected submit(): void {
|
||||||
|
if (this.form.invalid || this.saving()) return;
|
||||||
|
this.saving.set(true);
|
||||||
|
this.teamsApi.createTeam(this.form.getRawValue()).subscribe({
|
||||||
|
next: (team) => {
|
||||||
|
this.saving.set(false);
|
||||||
|
this.dialogRef.close(team);
|
||||||
|
},
|
||||||
|
error: () => this.saving.set(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
<div class="team-select-toolbar">
|
||||||
|
<button mat-stroked-button type="button" (click)="createTeam()">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
Team erstellen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<div class="team-select-loading">
|
<div class="team-select-loading">
|
||||||
<mat-spinner diameter="32" />
|
<mat-spinner diameter="32" />
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
.team-select-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 1rem 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
.team-select-loading,
|
.team-select-loading,
|
||||||
.team-select-empty {
|
.team-select-empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -2,26 +2,42 @@ import { TestBed } from '@angular/core/testing';
|
|||||||
import { provideHttpClient } from '@angular/common/http';
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||||
import { provideRouter, Router } from '@angular/router';
|
import { provideRouter, Router } from '@angular/router';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { TeamSelect } from './team-select';
|
import { TeamSelect } from './team-select';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { AuthStore } from '../../core/auth/auth-store';
|
import { AuthStore } from '../../core/auth/auth-store';
|
||||||
import { Player } from '../../models/player.model';
|
import { Player } from '../../models/player.model';
|
||||||
|
import { Team } from '../../models/team.model';
|
||||||
|
import { MyTeamsStore } from '../../core/team/my-teams-store';
|
||||||
|
import { CreateTeamDialog } from './create-team-dialog/create-team-dialog';
|
||||||
|
|
||||||
describe('TeamSelect', () => {
|
describe('TeamSelect', () => {
|
||||||
let httpMock: HttpTestingController;
|
let httpMock: HttpTestingController;
|
||||||
let router: Router;
|
let router: Router;
|
||||||
let authStore: AuthStore;
|
let authStore: AuthStore;
|
||||||
|
let myTeamsStore: MyTeamsStore;
|
||||||
|
let dialogClosed: Subject<Team | undefined>;
|
||||||
|
let dialog: { open: ReturnType<typeof vi.fn> };
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
dialogClosed = new Subject<Team | undefined>();
|
||||||
|
dialog = { open: vi.fn(() => ({ afterClosed: () => dialogClosed.asObservable() })) };
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [TeamSelect],
|
imports: [TeamSelect],
|
||||||
providers: [provideHttpClient(), provideHttpClientTesting(), provideRouter([])],
|
providers: [
|
||||||
|
provideHttpClient(),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
provideRouter([]),
|
||||||
|
{ provide: MatDialog, useValue: dialog },
|
||||||
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
httpMock = TestBed.inject(HttpTestingController);
|
httpMock = TestBed.inject(HttpTestingController);
|
||||||
router = TestBed.inject(Router);
|
router = TestBed.inject(Router);
|
||||||
authStore = TestBed.inject(AuthStore);
|
authStore = TestBed.inject(AuthStore);
|
||||||
|
myTeamsStore = TestBed.inject(MyTeamsStore);
|
||||||
authStore.setSession('token', { id: 42, email: 'a@b.de', firstName: 'A', lastName: 'B' });
|
authStore.setSession('token', { id: 42, email: 'a@b.de', firstName: 'A', lastName: 'B' });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -92,4 +108,41 @@ describe('TeamSelect', () => {
|
|||||||
|
|
||||||
expect(fixture.nativeElement.textContent).toContain('Du bist noch keinem Team zugeordnet.');
|
expect(fixture.nativeElement.textContent).toContain('Du bist noch keinem Team zugeordnet.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('opens the create-team dialog and navigates into the newly created team on success', async () => {
|
||||||
|
const navigateSpy = vi.spyOn(router, 'navigate');
|
||||||
|
const refreshSpy = vi.spyOn(myTeamsStore, 'refresh');
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(TeamSelect);
|
||||||
|
fixture.detectChanges();
|
||||||
|
httpMock.expectOne(`${environment.apiUrl}users/42/teams`).flush([]);
|
||||||
|
await fixture.whenStable();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.componentInstance['createTeam']();
|
||||||
|
expect(dialog.open).toHaveBeenCalledWith(CreateTeamDialog);
|
||||||
|
|
||||||
|
const created: Team = { id: 9, name: '1. Herren', alias: 'a', balance: 0 };
|
||||||
|
dialogClosed.next(created);
|
||||||
|
|
||||||
|
expect(refreshSpy).toHaveBeenCalledWith(42);
|
||||||
|
expect(navigateSpy).toHaveBeenCalledWith(['/team', 9, 'overview']);
|
||||||
|
|
||||||
|
httpMock.expectOne(`${environment.apiUrl}users/42/teams`).flush([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not navigate when the create-team dialog is dismissed without a team', async () => {
|
||||||
|
const navigateSpy = vi.spyOn(router, 'navigate');
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(TeamSelect);
|
||||||
|
fixture.detectChanges();
|
||||||
|
httpMock.expectOne(`${environment.apiUrl}users/42/teams`).flush([]);
|
||||||
|
await fixture.whenStable();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.componentInstance['createTeam']();
|
||||||
|
dialogClosed.next(undefined);
|
||||||
|
|
||||||
|
expect(navigateSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { Component, effect, inject } from '@angular/core';
|
import { Component, effect, inject } from '@angular/core';
|
||||||
import { Router, RouterLink } from '@angular/router';
|
import { Router, RouterLink } from '@angular/router';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatListModule } from '@angular/material/list';
|
import { MatListModule } from '@angular/material/list';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { AuthStore } from '../../core/auth/auth-store';
|
import { AuthStore } from '../../core/auth/auth-store';
|
||||||
import { MyTeamsStore } from '../../core/team/my-teams-store';
|
import { MyTeamsStore } from '../../core/team/my-teams-store';
|
||||||
|
import { Team } from '../../models/team.model';
|
||||||
|
import { CreateTeamDialog } from './create-team-dialog/create-team-dialog';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-team-select',
|
selector: 'app-team-select',
|
||||||
imports: [RouterLink, MatListModule, MatProgressSpinnerModule],
|
imports: [RouterLink, MatButtonModule, MatIconModule, MatListModule, MatProgressSpinnerModule],
|
||||||
templateUrl: './team-select.html',
|
templateUrl: './team-select.html',
|
||||||
styleUrl: './team-select.scss',
|
styleUrl: './team-select.scss',
|
||||||
})
|
})
|
||||||
@@ -15,6 +20,7 @@ export class TeamSelect {
|
|||||||
private readonly authStore = inject(AuthStore);
|
private readonly authStore = inject(AuthStore);
|
||||||
private readonly myTeamsStore = inject(MyTeamsStore);
|
private readonly myTeamsStore = inject(MyTeamsStore);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
|
private readonly dialog = inject(MatDialog);
|
||||||
|
|
||||||
protected readonly players = this.myTeamsStore.players;
|
protected readonly players = this.myTeamsStore.players;
|
||||||
protected readonly loading = this.myTeamsStore.loading;
|
protected readonly loading = this.myTeamsStore.loading;
|
||||||
@@ -34,4 +40,19 @@ export class TeamSelect {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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']);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="link-grid">
|
<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()) {
|
@if (canOpenGuide()) {
|
||||||
<a routerLink="guide"
|
<a routerLink="guide"
|
||||||
><mat-card
|
><mat-card
|
||||||
|
|||||||
@@ -26,11 +26,23 @@ h1 {
|
|||||||
gap: 14px;
|
gap: 14px;
|
||||||
margin: 28px 0;
|
margin: 28px 0;
|
||||||
}
|
}
|
||||||
a {
|
a,
|
||||||
|
.link-grid-action {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
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;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -41,19 +53,23 @@ a mat-card {
|
|||||||
transform 150ms ease,
|
transform 150ms ease,
|
||||||
box-shadow 150ms ease;
|
box-shadow 150ms ease;
|
||||||
}
|
}
|
||||||
a:hover mat-card {
|
a:hover mat-card,
|
||||||
|
.link-grid-action:hover mat-card {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: var(--mat-sys-level2);
|
box-shadow: var(--mat-sys-level2);
|
||||||
}
|
}
|
||||||
a div,
|
a div,
|
||||||
|
.link-grid-action div,
|
||||||
.account-card div {
|
.account-card div {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
}
|
}
|
||||||
a strong {
|
a strong,
|
||||||
|
.link-grid-action strong {
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
}
|
}
|
||||||
a span,
|
a span,
|
||||||
|
.link-grid-action span,
|
||||||
.account-card span {
|
.account-card span {
|
||||||
color: var(--mat-sys-on-surface-variant);
|
color: var(--mat-sys-on-surface-variant);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
import { Component, signal } from '@angular/core';
|
||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { provideRouter, Router } from '@angular/router';
|
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 { AuthStore } from '../../../core/auth/auth-store';
|
||||||
import { HelpAccessService } from '../../../core/help/help-access';
|
import { HelpAccessService } from '../../../core/help/help-access';
|
||||||
|
import { MyTeamsStore } from '../../../core/team/my-teams-store';
|
||||||
import { TeamStore } from '../../../core/team/team-store';
|
import { TeamStore } from '../../../core/team/team-store';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
import { More } from './more';
|
import { More } from './more';
|
||||||
|
|
||||||
@Component({ template: '' })
|
@Component({ template: '' })
|
||||||
@@ -15,6 +21,9 @@ describe('More', () => {
|
|||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [More],
|
imports: [More],
|
||||||
providers: [
|
providers: [
|
||||||
|
provideHttpClient(),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
{ provide: MatDialog, useValue: { open: vi.fn() } },
|
||||||
provideRouter([{ path: 'auth/login', component: LoginStub }]),
|
provideRouter([{ path: 'auth/login', component: LoginStub }]),
|
||||||
{
|
{
|
||||||
provide: AuthStore,
|
provide: AuthStore,
|
||||||
@@ -49,6 +58,9 @@ describe('More', () => {
|
|||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [More],
|
imports: [More],
|
||||||
providers: [
|
providers: [
|
||||||
|
provideHttpClient(),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
{ provide: MatDialog, useValue: { open: vi.fn() } },
|
||||||
provideRouter([]),
|
provideRouter([]),
|
||||||
{
|
{
|
||||||
provide: AuthStore,
|
provide: AuthStore,
|
||||||
@@ -88,6 +100,9 @@ describe('More', () => {
|
|||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [More],
|
imports: [More],
|
||||||
providers: [
|
providers: [
|
||||||
|
provideHttpClient(),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
{ provide: MatDialog, useValue: { open: vi.fn() } },
|
||||||
provideRouter([]),
|
provideRouter([]),
|
||||||
{
|
{
|
||||||
provide: AuthStore,
|
provide: AuthStore,
|
||||||
@@ -111,4 +126,45 @@ describe('More', () => {
|
|||||||
|
|
||||||
expect(fixture.nativeElement.textContent).toContain('Berechtigungen');
|
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 { Router, RouterLink } from '@angular/router';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { AuthStore } from '../../../core/auth/auth-store';
|
import { AuthStore } from '../../../core/auth/auth-store';
|
||||||
import { HelpAccessService } from '../../../core/help/help-access';
|
import { HelpAccessService } from '../../../core/help/help-access';
|
||||||
|
import { MyTeamsStore } from '../../../core/team/my-teams-store';
|
||||||
import { TeamPermissionsService } from '../../../core/team/team-permissions';
|
import { TeamPermissionsService } from '../../../core/team/team-permissions';
|
||||||
import { TeamStore } from '../../../core/team/team-store';
|
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({
|
@Component({
|
||||||
selector: 'app-more',
|
selector: 'app-more',
|
||||||
@@ -16,7 +20,9 @@ import { TeamStore } from '../../../core/team/team-store';
|
|||||||
})
|
})
|
||||||
export class More {
|
export class More {
|
||||||
private readonly authStore = inject(AuthStore);
|
private readonly authStore = inject(AuthStore);
|
||||||
|
private readonly dialog = inject(MatDialog);
|
||||||
private readonly helpAccess = inject(HelpAccessService);
|
private readonly helpAccess = inject(HelpAccessService);
|
||||||
|
private readonly myTeamsStore = inject(MyTeamsStore);
|
||||||
private readonly permissions = inject(TeamPermissionsService);
|
private readonly permissions = inject(TeamPermissionsService);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly teamStore = inject(TeamStore);
|
private readonly teamStore = inject(TeamStore);
|
||||||
@@ -30,4 +36,19 @@ export class More {
|
|||||||
this.authStore.clearSession();
|
this.authStore.clearSession();
|
||||||
void this.router.navigateByUrl('/auth/login');
|
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