diff --git a/listify-client/src/app/lists/confirm-delete-list-dialog/confirm-delete-list-dialog.component.html b/listify-client/src/app/lists/confirm-delete-list-dialog/confirm-delete-list-dialog.component.html new file mode 100644 index 0000000..72953ba --- /dev/null +++ b/listify-client/src/app/lists/confirm-delete-list-dialog/confirm-delete-list-dialog.component.html @@ -0,0 +1,20 @@ +
+ +
+ +

Liste loeschen?

+ + +

+ {{ data.listName }} wird geloescht und ist danach nicht + mehr in deinen Listen sichtbar. +

+
+ + + + + diff --git a/listify-client/src/app/lists/confirm-delete-list-dialog/confirm-delete-list-dialog.component.ts b/listify-client/src/app/lists/confirm-delete-list-dialog/confirm-delete-list-dialog.component.ts new file mode 100644 index 0000000..32c3bc5 --- /dev/null +++ b/listify-client/src/app/lists/confirm-delete-list-dialog/confirm-delete-list-dialog.component.ts @@ -0,0 +1,17 @@ +import { Component, inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; + +export interface ConfirmDeleteListDialogData { + listName: string; +} + +@Component({ + selector: 'app-confirm-delete-list-dialog', + imports: [MatButtonModule, MatDialogModule, MatIconModule], + templateUrl: './confirm-delete-list-dialog.component.html', +}) +export class ConfirmDeleteListDialogComponent { + protected readonly data = inject(MAT_DIALOG_DATA); +} diff --git a/listify-client/src/app/lists/list-detail/list-detail.component.html b/listify-client/src/app/lists/list-detail/list-detail.component.html index d4756c3..2041350 100644 --- a/listify-client/src/app/lists/list-detail/list-detail.component.html +++ b/listify-client/src/app/lists/list-detail/list-detail.component.html @@ -61,6 +61,23 @@ {{ showEditor() ? 'Abbrechen' : 'Bearbeiten' }} + + @if (canDeleteList()) { + + } } diff --git a/listify-client/src/app/lists/list-detail/list-detail.component.ts b/listify-client/src/app/lists/list-detail/list-detail.component.ts index 4d5488c..cf98ba5 100644 --- a/listify-client/src/app/lists/list-detail/list-detail.component.ts +++ b/listify-client/src/app/lists/list-detail/list-detail.component.ts @@ -7,6 +7,7 @@ import { finalize } from 'rxjs'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; @@ -16,6 +17,7 @@ import { AuthService } from '../../auth/auth.service'; import { getAuthErrorMessage } from '../../auth/error-message'; import { PublicUserSearchResult } from '../../auth/auth.models'; import { OnboardingService } from '../../onboarding/onboarding.service'; +import { ConfirmDeleteListDialogComponent } from '../confirm-delete-list-dialog/confirm-delete-list-dialog.component'; import { ListItemSuggestion, ListRealtimeEvent, @@ -34,6 +36,7 @@ import { ListsService } from '../lists.service'; MatButtonModule, MatCardModule, MatCheckboxModule, + MatDialogModule, MatFormFieldModule, MatIconModule, MatInputModule, @@ -47,6 +50,7 @@ export class ListDetailComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); private readonly formBuilder = inject(NonNullableFormBuilder); private readonly authService = inject(AuthService); + private readonly dialog = inject(MatDialog); private readonly listsService = inject(ListsService); private readonly listsRealtimeService = inject(ListsRealtimeService); private readonly route = inject(ActivatedRoute); @@ -59,6 +63,7 @@ export class ListDetailComponent implements OnInit { protected readonly loading = signal(true); protected readonly saving = signal(false); protected readonly creatingTemplate = signal(false); + protected readonly deletingList = signal(false); protected readonly editing = signal(false); protected readonly addingItem = signal(false); protected readonly loadingSuggestions = signal(false); @@ -76,6 +81,9 @@ export class ListDetailComponent implements OnInit { protected readonly canManageShares = computed( () => this.list()?.accessRole === 'owner' && !this.isCreateMode(), ); + protected readonly canDeleteList = computed( + () => this.list()?.accessRole === 'owner' && !this.isCreateMode(), + ); protected readonly showShareControls = computed( () => this.canManageShares() && this.showEditor(), ); @@ -233,6 +241,48 @@ export class ListDetailComponent implements OnInit { }); } + protected deleteList(): void { + const listId = this.listId(); + const list = this.list(); + + if (!listId || !list || !this.canDeleteList() || this.deletingList()) { + return; + } + + this.dialog + .open( + ConfirmDeleteListDialogComponent, + { + data: { listName: list.name }, + maxWidth: '420px', + width: 'calc(100vw - 32px)', + }, + ) + .afterClosed() + .subscribe((confirmed) => { + if (!confirmed) { + return; + } + + this.deletingList.set(true); + + this.listsService + .deleteList(listId) + .pipe(finalize(() => this.deletingList.set(false))) + .subscribe({ + next: () => { + this.snackBar.open('Liste geloescht.', 'OK', { duration: 3000 }); + void this.router.navigateByUrl('/lists'); + }, + error: (error: unknown) => { + this.snackBar.open(getAuthErrorMessage(error), 'OK', { + duration: 5000, + }); + }, + }); + }); + } + protected loadSuggestions(): void { const listId = this.listId(); diff --git a/listify-client/src/app/lists/lists.component.html b/listify-client/src/app/lists/lists.component.html index 1c00f4d..f53dfc7 100644 --- a/listify-client/src/app/lists/lists.component.html +++ b/listify-client/src/app/lists/lists.component.html @@ -167,6 +167,23 @@ + @if (list.accessRole === 'owner') { + + } + ([]); protected readonly loading = signal(true); + protected readonly deletingListId = signal(null); protected readonly errorMessage = signal(null); protected readonly searchTerm = signal(''); protected readonly kindFilter = signal('all'); @@ -171,6 +180,47 @@ export class ListsComponent implements OnInit { this.sortOption.set('updated-desc'); } + protected deleteList(list: UserList): void { + if (list.accessRole !== 'owner' || this.deletingListId()) { + return; + } + + this.dialog + .open( + ConfirmDeleteListDialogComponent, + { + data: { listName: list.name }, + maxWidth: '420px', + width: 'calc(100vw - 32px)', + }, + ) + .afterClosed() + .subscribe((confirmed) => { + if (!confirmed) { + return; + } + + this.deletingListId.set(list.id); + + this.listsService + .deleteList(list.id) + .pipe(finalize(() => this.deletingListId.set(null))) + .subscribe({ + next: () => { + this.lists.update((lists) => + lists.filter((existingList) => existingList.id !== list.id), + ); + this.snackBar.open('Liste geloescht.', 'OK', { duration: 3000 }); + }, + error: (error: unknown) => { + this.snackBar.open(getAuthErrorMessage(error), 'OK', { + duration: 5000, + }); + }, + }); + }); + } + private subscribeToRealtime(): void { this.listsRealtimeService .events() diff --git a/listify-client/src/app/lists/lists.service.ts b/listify-client/src/app/lists/lists.service.ts index b2d4b15..fe4ae7a 100644 --- a/listify-client/src/app/lists/lists.service.ts +++ b/listify-client/src/app/lists/lists.service.ts @@ -32,6 +32,10 @@ export class ListsService { return this.http.patch(`${this.apiUrl}/${listId}`, data); } + deleteList(listId: string): Observable<{ message: string }> { + return this.http.delete<{ message: string }>(`${this.apiUrl}/${listId}`); + } + shareList(listId: string, userId: string): Observable { return this.http.post(`${this.apiUrl}/${listId}/shares`, { userId }); }