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 @@
+
+ delete
+
+
+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() ? 'close' : 'edit' }}
{{ 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 });
}