import { Component, inject, signal, OnInit } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { Router, RouterLink } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { NzTableModule } from 'ng-zorro-antd/table'; import { NzButtonModule } from 'ng-zorro-antd/button'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzTagModule } from 'ng-zorro-antd/tag'; import { NzEmptyModule } from 'ng-zorro-antd/empty'; import { NzTabsModule } from 'ng-zorro-antd/tabs'; import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm'; import { NzModalModule } from 'ng-zorro-antd/modal'; import { NzFormModule } from 'ng-zorro-antd/form'; import { NzInputModule } from 'ng-zorro-antd/input'; import { NzToolTipModule } from 'ng-zorro-antd/tooltip'; import { NzAlertModule } from 'ng-zorro-antd/alert'; import { NzPaginationModule } from 'ng-zorro-antd/pagination'; import { ApiService } from '../../../core/services/api.service'; import { AuthService } from '../../../core/services/auth.service'; import { NotificationService } from '../../../core/services/notification.service'; import { SettingsService } from '../../../core/services/settings.service'; import { UserCacheService, ResolvedUser } from '../../../core/services/user-cache.service'; import { Subscription, SubscriptionFilter } from '../../../core/models'; import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe'; type SubscriptionTab = 'all' | 'own' | 'deactivated' | 'external' | 'incoming'; interface TabConfig { filter: SubscriptionFilter; } const TAB_CONFIGS: Record = { all: { filter: {} }, own: { filter: { direction: 'outgoing', confirmation: 'confirmed', external: 'false' } }, deactivated: { filter: { direction: 'outgoing', confirmation: 'unconfirmed', external: 'false' } }, external: { filter: { direction: 'outgoing', confirmation: 'all', external: 'true' } }, incoming: { filter: { direction: 'incoming', confirmation: 'all', external: 'true' } }, }; @Component({ selector: 'app-subscription-list', standalone: true, imports: [ CommonModule, DatePipe, FormsModule, RouterLink, NzTableModule, NzButtonModule, NzIconModule, NzTagModule, NzEmptyModule, NzTabsModule, NzPopconfirmModule, NzModalModule, NzFormModule, NzInputModule, NzToolTipModule, NzAlertModule, NzPaginationModule, RelativeTimePipe, ], templateUrl: './subscription-list.component.html', styleUrl: './subscription-list.component.scss' }) export class SubscriptionListComponent implements OnInit { private router = inject(Router); private apiService = inject(ApiService); private authService = inject(AuthService); private notification = inject(NotificationService); private settingsService = inject(SettingsService); private userCacheService = inject(UserCacheService); expertMode = this.settingsService.expertMode; subscriptions = signal([]); userNames = signal>(new Map()); loading = signal(false); activeTab: SubscriptionTab = 'all'; // Pagination currentPage = signal(1); pageSize = 50; totalCount = signal(0); // Create subscription modal showCreateModal = signal(false); newChannelOwner = ''; newChannelName = ''; creating = signal(false); ngOnInit(): void { this.loadSubscriptions(); } loadSubscriptions(): void { const userId = this.authService.getUserId(); if (!userId) return; this.loading.set(true); const filter: SubscriptionFilter = { ...TAB_CONFIGS[this.activeTab].filter, page_size: this.pageSize, }; // Use page-index based pagination: $1 = page 1, $2 = page 2, etc. const page = this.currentPage(); if (page > 1) { filter.next_page_token = `$${page}`; } this.apiService.getSubscriptions(userId, filter).subscribe({ next: (response) => { this.subscriptions.set(response.subscriptions); this.totalCount.set(response.total_count); this.loading.set(false); this.resolveUserNames(response.subscriptions); }, error: () => { this.loading.set(false); } }); } private resolveUserNames(subscriptions: Subscription[]): void { const userIds = new Set(); for (const sub of subscriptions) { userIds.add(sub.subscriber_user_id); userIds.add(sub.channel_owner_user_id); } for (const id of userIds) { this.userCacheService.resolveUser(id).subscribe(resolved => { this.userNames.update(map => new Map(map).set(id, resolved)); }); } } getUserDisplayName(userId: string): string { const resolved = this.userNames().get(userId); return resolved?.displayName || userId; } onTabChange(index: number): void { const tabs: SubscriptionTab[] = ['all', 'own', 'deactivated', 'external', 'incoming']; this.activeTab = tabs[index]; this.currentPage.set(1); this.loadSubscriptions(); } goToPage(page: number): void { this.currentPage.set(page); this.loadSubscriptions(); } isOutgoing(sub: Subscription): boolean { const userId = this.authService.getUserId(); return sub.subscriber_user_id === userId; } isOwner(sub: Subscription): boolean { const userId = this.authService.getUserId(); return sub.channel_owner_user_id === userId; } isOwnSubscription(sub: Subscription): boolean { const userId = this.authService.getUserId(); return sub.subscriber_user_id === userId && sub.channel_owner_user_id === userId; } viewSubscription(sub: Subscription): void { this.router.navigate(['/subscriptions', sub.subscription_id]); } // Actions acceptSubscription(sub: Subscription): void { const userId = this.authService.getUserId(); if (!userId) return; this.apiService.confirmSubscription(userId, sub.subscription_id, { confirmed: true }).subscribe({ next: () => { this.notification.success('Subscription accepted'); this.loadSubscriptions(); } }); } denySubscription(sub: Subscription): void { const userId = this.authService.getUserId(); if (!userId) return; this.apiService.deleteSubscription(userId, sub.subscription_id).subscribe({ next: () => { this.notification.success('Subscription denied'); this.loadSubscriptions(); } }); } revokeSubscription(sub: Subscription): void { const userId = this.authService.getUserId(); if (!userId) return; this.apiService.deleteSubscription(userId, sub.subscription_id).subscribe({ next: () => { this.notification.success('Subscription revoked'); this.loadSubscriptions(); } }); } activateSubscription(sub: Subscription): void { const userId = this.authService.getUserId(); if (!userId) return; this.apiService.confirmSubscription(userId, sub.subscription_id, { active: true }).subscribe({ next: () => { this.notification.success('Subscription activated'); this.loadSubscriptions(); } }); } deactivateSubscription(sub: Subscription): void { const userId = this.authService.getUserId(); if (!userId) return; this.apiService.confirmSubscription(userId, sub.subscription_id, { active: false }).subscribe({ next: () => { this.notification.success('Subscription deactivated'); this.loadSubscriptions(); } }); } // Create subscription openCreateModal(): void { this.newChannelOwner = ''; this.newChannelName = ''; this.showCreateModal.set(true); } closeCreateModal(): void { this.showCreateModal.set(false); } createSubscription(): void { const userId = this.authService.getUserId(); if (!userId || !this.newChannelOwner.trim() || !this.newChannelName.trim()) return; this.creating.set(true); this.apiService.createSubscription(userId, { channel_owner_user_id: this.newChannelOwner.trim(), channel_internal_name: this.newChannelName.trim() }).subscribe({ next: () => { this.notification.success('Subscription request sent'); this.closeCreateModal(); this.creating.set(false); this.loadSubscriptions(); }, error: () => { this.creating.set(false); } }); } getConfirmationInfo(sub: Subscription): { label: string; color: string } { if (sub.confirmed) { return { label: 'Confirmed', color: 'green' }; } return { label: 'Pending', color: 'orange' }; } getActiveInfo(sub: Subscription): { label: string; color: string } { if (sub.active) { return { label: 'Active', color: 'green' }; } return { label: 'Inactive', color: 'default' }; } getTypeLabel(sub: Subscription): { label: string; color: string } { const userId = this.authService.getUserId(); if (sub.subscriber_user_id === sub.channel_owner_user_id) { return { label: 'Own', color: 'green' }; } if (sub.subscriber_user_id === userId) { return { label: 'External', color: 'blue' }; } return { label: 'Incoming', color: 'purple' }; } getTabDescription(): string | null { switch (this.activeTab) { case 'own': return 'Active subscriptions to your channels.'; case 'deactivated': return 'Deactivated subscriptions to your channels. These can be reactivated by you.'; case 'external': return 'Your subscriptions to channels owned by other users.'; case 'incoming': return 'Subscription from other users to your channels.'; default: return null; } } }