eeg_portal/eeg_frontend/src/app/pages/join-community/join-community.ts
Bernhard Müller c66c5fddfa feat(community): add complete membership flow with unit tests
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)
2026-07-21 12:42:07 +02:00

98 lines
3.1 KiB
TypeScript

import {Component, inject, OnInit, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MeteringPointService, MeteringPoint} from '../../services/metering-point';
import {MembershipService, EligibleCommunity} from '../../services/membership';
import {ToastService} from '../../services/toast';
@Component({
selector: 'app-join-community',
standalone: true,
imports: [FormsModule],
templateUrl: './join-community.html'
})
export class JoinCommunityComponent implements OnInit {
private meteringPointService = inject(MeteringPointService);
private membershipService = inject(MembershipService);
private toastService = inject(ToastService);
meteringPoints = signal<MeteringPoint[]>([]);
selectedMeteringPoint = signal<MeteringPoint | null>(null);
eligibleCommunities = signal<EligibleCommunity[]>([]);
isLoading = signal(true);
isLoadingCommunities = signal(false);
isSubmitting = signal(false);
selectedPriority = signal(1);
ngOnInit() {
this.loadMeteringPoints();
}
loadMeteringPoints() {
this.meteringPointService.getMyMeteringPoints().subscribe({
next: (points: MeteringPoint[]) => {
// Only show ACTIVE metering points
this.meteringPoints.set(points.filter((p: MeteringPoint) => p.makoState === 'ACTIVE'));
this.isLoading.set(false);
},
error: () => this.isLoading.set(false)
});
}
onMeteringPointChange(event: Event) {
const target = event.target as HTMLSelectElement;
const pointId = target.value;
if (!pointId) {
this.selectedMeteringPoint.set(null);
this.eligibleCommunities.set([]);
return;
}
const point = this.meteringPoints().find(p => p.id === pointId);
this.selectedMeteringPoint.set(point || null);
if (point) {
this.loadEligibleCommunities(point.id);
}
}
loadEligibleCommunities(meteringPointId: string) {
this.isLoadingCommunities.set(true);
this.eligibleCommunities.set([]);
this.membershipService.getEligibleCommunities(meteringPointId).subscribe({
next: communities => {
this.eligibleCommunities.set(communities);
this.isLoadingCommunities.set(false);
},
error: () => {
this.isLoadingCommunities.set(false);
this.toastService.error('Fehler beim Laden der passenden Communities.');
}
});
}
requestMembership(community: EligibleCommunity) {
if (this.isSubmitting() || !this.selectedMeteringPoint()) return;
this.isSubmitting.set(true);
this.membershipService.requestMembership({
meteringPointId: this.selectedMeteringPoint()!.id,
energyCommunityId: community.id,
priorityLevel: this.selectedPriority()
}).subscribe({
next: () => {
this.isSubmitting.set(false);
this.toastService.success(`Beitrittsantrag an "${community.name}" erfolgreich gestellt.`);
this.eligibleCommunities.set([]);
this.selectedMeteringPoint.set(null);
},
error: (err) => {
this.isSubmitting.set(false);
this.toastService.error(err.error?.message || 'Fehler beim Stellen des Beitrittsantrags.');
}
});
}
}