Backend: - Add MembershipResponse and PendingMembershipResponse DTOs - Add GET /memberships/me and GET /admin/memberships/pending endpoints - Add findByUserIdWithDetails and findPendingWithDetails queries - Add getOwnMemberships and getPendingMemberships service methods Frontend: - Add membership service for API calls - Add join-community page for users to browse and join communities - Add my-memberships page for users to view their memberships - Add admin-memberships page for admins to approve/reject requests - Add routes and navigation items Tests: - Add MembershipServiceTest with 13 unit tests - All 20 tests passing (UserServiceTest + MembershipServiceTest)
69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
import {Component, inject, OnInit, signal} from '@angular/core';
|
|
import {MembershipService, PendingMembershipResponse} from '../../services/membership';
|
|
import {ToastService} from '../../services/toast';
|
|
|
|
@Component({
|
|
selector: 'app-admin-memberships',
|
|
standalone: true,
|
|
templateUrl: './admin-memberships.html'
|
|
})
|
|
export class AdminMembershipsComponent implements OnInit {
|
|
private membershipService = inject(MembershipService);
|
|
private toastService = inject(ToastService);
|
|
|
|
pendingMemberships = signal<PendingMembershipResponse[]>([]);
|
|
isLoading = signal(true);
|
|
isProcessing = signal<string | null>(null);
|
|
|
|
ngOnInit() {
|
|
this.loadPendingMemberships();
|
|
}
|
|
|
|
loadPendingMemberships() {
|
|
this.membershipService.getPendingMemberships().subscribe({
|
|
next: memberships => {
|
|
this.pendingMemberships.set(memberships);
|
|
this.isLoading.set(false);
|
|
},
|
|
error: () => {
|
|
this.isLoading.set(false);
|
|
this.toastService.error('Fehler beim Laden der wartenden Anträge.');
|
|
}
|
|
});
|
|
}
|
|
|
|
approveMembership(id: string) {
|
|
if (this.isProcessing()) return;
|
|
this.isProcessing.set(id);
|
|
|
|
this.membershipService.approveMembership(id).subscribe({
|
|
next: () => {
|
|
this.toastService.success('Mitgliedschaft genehmigt.');
|
|
this.loadPendingMemberships();
|
|
this.isProcessing.set(null);
|
|
},
|
|
error: (err) => {
|
|
this.isProcessing.set(null);
|
|
this.toastService.error(err.error?.message || 'Fehler bei der Genehmigung.');
|
|
}
|
|
});
|
|
}
|
|
|
|
rejectMembership(id: string) {
|
|
if (this.isProcessing()) return;
|
|
this.isProcessing.set(id);
|
|
|
|
this.membershipService.rejectMembership(id).subscribe({
|
|
next: () => {
|
|
this.toastService.success('Mitgliedschaft abgelehnt.');
|
|
this.loadPendingMemberships();
|
|
this.isProcessing.set(null);
|
|
},
|
|
error: (err) => {
|
|
this.isProcessing.set(null);
|
|
this.toastService.error(err.error?.message || 'Fehler bei der Ablehnung.');
|
|
}
|
|
});
|
|
}
|
|
}
|