More webapp changes+fixes
This commit is contained in:
@@ -39,6 +39,12 @@ import {
|
|||||||
InfoCircleOutline,
|
InfoCircleOutline,
|
||||||
ExclamationCircleOutline,
|
ExclamationCircleOutline,
|
||||||
CheckCircleOutline,
|
CheckCircleOutline,
|
||||||
|
UserAddOutline,
|
||||||
|
UserDeleteOutline,
|
||||||
|
PauseCircleOutline,
|
||||||
|
PlayCircleOutline,
|
||||||
|
StopOutline,
|
||||||
|
ArrowLeftOutline,
|
||||||
} from '@ant-design/icons-angular/icons';
|
} from '@ant-design/icons-angular/icons';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
@@ -79,6 +85,12 @@ const icons: IconDefinition[] = [
|
|||||||
InfoCircleOutline,
|
InfoCircleOutline,
|
||||||
ExclamationCircleOutline,
|
ExclamationCircleOutline,
|
||||||
CheckCircleOutline,
|
CheckCircleOutline,
|
||||||
|
UserAddOutline,
|
||||||
|
UserDeleteOutline,
|
||||||
|
PauseCircleOutline,
|
||||||
|
PlayCircleOutline,
|
||||||
|
StopOutline,
|
||||||
|
ArrowLeftOutline,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface MessageListParams {
|
|||||||
search?: string;
|
search?: string;
|
||||||
sender?: string[];
|
sender?: string[];
|
||||||
subscription_status?: 'all' | 'confirmed' | 'unconfirmed';
|
subscription_status?: 'all' | 'confirmed' | 'unconfirmed';
|
||||||
|
used_key?: string;
|
||||||
trimmed?: boolean;
|
trimmed?: boolean;
|
||||||
page_size?: number;
|
page_size?: number;
|
||||||
next_page_token?: string;
|
next_page_token?: string;
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ export class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (params.subscription_status) httpParams = httpParams.set('subscription_status', params.subscription_status);
|
if (params.subscription_status) httpParams = httpParams.set('subscription_status', params.subscription_status);
|
||||||
|
if (params.used_key) httpParams = httpParams.set('used_key', params.used_key);
|
||||||
if (params.trimmed !== undefined) httpParams = httpParams.set('trimmed', params.trimmed);
|
if (params.trimmed !== undefined) httpParams = httpParams.set('trimmed', params.trimmed);
|
||||||
if (params.page_size) httpParams = httpParams.set('page_size', params.page_size);
|
if (params.page_size) httpParams = httpParams.set('page_size', params.page_size);
|
||||||
if (params.next_page_token) httpParams = httpParams.set('next_page_token', params.next_page_token);
|
if (params.next_page_token) httpParams = httpParams.set('next_page_token', params.next_page_token);
|
||||||
|
|||||||
@@ -150,7 +150,21 @@
|
|||||||
</nz-card>
|
</nz-card>
|
||||||
|
|
||||||
@if (isOwner()) {
|
@if (isOwner()) {
|
||||||
<nz-card nzTitle="Subscriptions" class="mt-16">
|
<nz-card nzTitle="Subscriptions" [nzExtra]="subscriptionsCardExtra" class="mt-16">
|
||||||
|
<ng-template #subscriptionsCardExtra>
|
||||||
|
@if (expertMode()) {
|
||||||
|
<button
|
||||||
|
nz-button
|
||||||
|
nzSize="small"
|
||||||
|
[nzType]="isUserSubscribed() ? 'default' : 'primary'"
|
||||||
|
nz-tooltip
|
||||||
|
[nzTooltipTitle]="isUserSubscribed() ? 'Unsubscribe' : 'Subscribe'"
|
||||||
|
(click)="toggleSelfSubscription()"
|
||||||
|
>
|
||||||
|
<span nz-icon [nzType]="isUserSubscribed() ? 'user-delete' : 'user-add'"></span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
<nz-table
|
<nz-table
|
||||||
#subscriptionTable
|
#subscriptionTable
|
||||||
[nzData]="subscriptions()"
|
[nzData]="subscriptions()"
|
||||||
@@ -164,6 +178,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Subscriber</th>
|
<th>Subscriber</th>
|
||||||
<th nzWidth="0">Status</th>
|
<th nzWidth="0">Status</th>
|
||||||
|
<th nzWidth="0">Active</th>
|
||||||
<th nzWidth="0">Created</th>
|
<th nzWidth="0">Created</th>
|
||||||
<th nzWidth="0">Actions</th>
|
<th nzWidth="0">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -184,6 +199,13 @@
|
|||||||
</nz-tag>
|
</nz-tag>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="cell-link" [routerLink]="['/subscriptions', sub.subscription_id]">
|
||||||
|
<nz-tag [nzColor]="sub.active ? 'green' : 'default'">
|
||||||
|
{{ sub.active ? 'Active' : 'Inactive' }}
|
||||||
|
</nz-tag>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="cell-link" [routerLink]="['/subscriptions', sub.subscription_id]">
|
<a class="cell-link" [routerLink]="['/subscriptions', sub.subscription_id]">
|
||||||
<div class="timestamp-absolute">{{ sub.timestamp_created | date:'yyyy-MM-dd HH:mm:ss' }}</div>
|
<div class="timestamp-absolute">{{ sub.timestamp_created | date:'yyyy-MM-dd HH:mm:ss' }}</div>
|
||||||
@@ -234,7 +256,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">
|
<td colspan="5">
|
||||||
<nz-empty nzNotFoundContent="No subscriptions"></nz-empty>
|
<nz-empty nzNotFoundContent="No subscriptions"></nz-empty>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -104,6 +104,9 @@
|
|||||||
.message-content {
|
.message-content {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
white-space: pre;
|
||||||
|
max-height: 2lh;
|
||||||
|
overflow-y: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-muted {
|
.text-muted {
|
||||||
|
|||||||
@@ -390,4 +390,33 @@ export class ChannelDetailComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUserSubscribed(): boolean {
|
||||||
|
return this.channel()?.subscription !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSelfSubscription(): void {
|
||||||
|
const channel = this.channel();
|
||||||
|
const userId = this.authService.getUserId();
|
||||||
|
if (!channel || !userId) return;
|
||||||
|
|
||||||
|
if (this.isUserSubscribed()) {
|
||||||
|
// Unsubscribe
|
||||||
|
const subscriptionId = channel.subscription!.subscription_id;
|
||||||
|
this.apiService.deleteSubscription(userId, subscriptionId).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notification.success('Unsubscribed from channel');
|
||||||
|
this.loadChannel(channel.channel_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Subscribe
|
||||||
|
this.apiService.createSubscription(userId, { channel_id: channel.channel_id }).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notification.success('Subscribed to channel');
|
||||||
|
this.loadChannel(channel.channel_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<nz-tabset (nzSelectedIndexChange)="onTabChange($event)">
|
||||||
|
<nz-tab nzTitle="All"></nz-tab>
|
||||||
|
<nz-tab nzTitle="Owned"></nz-tab>
|
||||||
|
<nz-tab nzTitle="Foreign"></nz-tab>
|
||||||
|
</nz-tabset>
|
||||||
|
|
||||||
|
@if (getTabDescription()) {
|
||||||
|
<nz-alert
|
||||||
|
nzType="info"
|
||||||
|
[nzMessage]="getTabDescription()!"
|
||||||
|
nzShowIcon
|
||||||
|
style="margin-bottom: 16px;"
|
||||||
|
></nz-alert>
|
||||||
|
}
|
||||||
|
|
||||||
<nz-card>
|
<nz-card>
|
||||||
<nz-table
|
<nz-table
|
||||||
#channelTable
|
#channelTable
|
||||||
@@ -28,6 +43,9 @@
|
|||||||
<th style="width: 400px">Subscribers</th>
|
<th style="width: 400px">Subscribers</th>
|
||||||
<th style="width: 0">Messages</th>
|
<th style="width: 0">Messages</th>
|
||||||
<th style="width: 0">Last Sent</th>
|
<th style="width: 0">Last Sent</th>
|
||||||
|
@if (expertMode()) {
|
||||||
|
<th style="width: 0">Actions</th>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -56,10 +74,12 @@
|
|||||||
<td>
|
<td>
|
||||||
@if (isOwned(channel)) {
|
@if (isOwned(channel)) {
|
||||||
<a class="cell-link" [routerLink]="['/channels', channel.channel_id]">
|
<a class="cell-link" [routerLink]="['/channels', channel.channel_id]">
|
||||||
{{ getOwnerDisplayName(channel.owner_user_id) }}
|
<div class="channel-name">{{ getOwnerDisplayName(channel.owner_user_id) }}</div>
|
||||||
|
<div class="channel-id mono">{{ channel.owner_user_id }}</div>
|
||||||
</a>
|
</a>
|
||||||
} @else {
|
} @else {
|
||||||
{{ getOwnerDisplayName(channel.owner_user_id) }}
|
<div class="channel-name">{{ getOwnerDisplayName(channel.owner_user_id) }}</div>
|
||||||
|
<div class="channel-id mono">{{ channel.owner_user_id }}</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -112,10 +132,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
@if (expertMode()) {
|
||||||
|
<td>
|
||||||
|
@if (isOwned(channel)) {
|
||||||
|
<button
|
||||||
|
nz-button
|
||||||
|
nzSize="small"
|
||||||
|
[nzType]="channel.subscription ? 'default' : 'primary'"
|
||||||
|
nz-tooltip
|
||||||
|
[nzTooltipTitle]="channel.subscription ? 'Unsubscribe' : 'Subscribe'"
|
||||||
|
(click)="toggleSelfSubscription(channel, $event)"
|
||||||
|
>
|
||||||
|
<span nz-icon [nzType]="channel.subscription ? 'user-delete' : 'user-add'"></span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7">
|
<td [attr.colspan]="expertMode() ? 8 : 7">
|
||||||
<nz-empty nzNotFoundContent="No channels found"></nz-empty>
|
<nz-empty nzNotFoundContent="No channels found"></nz-empty>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, inject, signal, OnInit } from '@angular/core';
|
import { Component, inject, signal, computed, OnInit } from '@angular/core';
|
||||||
import { CommonModule, DatePipe } from '@angular/common';
|
import { CommonModule, DatePipe } from '@angular/common';
|
||||||
import { Router, RouterLink } from '@angular/router';
|
import { Router, RouterLink } from '@angular/router';
|
||||||
import { NzTableModule } from 'ng-zorro-antd/table';
|
import { NzTableModule } from 'ng-zorro-antd/table';
|
||||||
@@ -9,13 +9,19 @@ import { NzBadgeModule } from 'ng-zorro-antd/badge';
|
|||||||
import { NzEmptyModule } from 'ng-zorro-antd/empty';
|
import { NzEmptyModule } from 'ng-zorro-antd/empty';
|
||||||
import { NzCardModule } from 'ng-zorro-antd/card';
|
import { NzCardModule } from 'ng-zorro-antd/card';
|
||||||
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
|
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
|
||||||
|
import { NzTabsModule } from 'ng-zorro-antd/tabs';
|
||||||
|
import { NzAlertModule } from 'ng-zorro-antd/alert';
|
||||||
import { ApiService } from '../../../core/services/api.service';
|
import { ApiService } from '../../../core/services/api.service';
|
||||||
import { AuthService } from '../../../core/services/auth.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 { UserCacheService, ResolvedUser } from '../../../core/services/user-cache.service';
|
||||||
import { ChannelWithSubscription } from '../../../core/models';
|
import { ChannelWithSubscription } from '../../../core/models';
|
||||||
import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe';
|
import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe';
|
||||||
import { ChannelSubscribersComponent } from '../channel-subscribers/channel-subscribers.component';
|
import { ChannelSubscribersComponent } from '../channel-subscribers/channel-subscribers.component';
|
||||||
|
|
||||||
|
type ChannelTab = 'all' | 'owned' | 'foreign';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-channel-list',
|
selector: 'app-channel-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -31,6 +37,8 @@ import { ChannelSubscribersComponent } from '../channel-subscribers/channel-subs
|
|||||||
NzEmptyModule,
|
NzEmptyModule,
|
||||||
NzCardModule,
|
NzCardModule,
|
||||||
NzToolTipModule,
|
NzToolTipModule,
|
||||||
|
NzTabsModule,
|
||||||
|
NzAlertModule,
|
||||||
RelativeTimePipe,
|
RelativeTimePipe,
|
||||||
ChannelSubscribersComponent,
|
ChannelSubscribersComponent,
|
||||||
],
|
],
|
||||||
@@ -40,12 +48,31 @@ import { ChannelSubscribersComponent } from '../channel-subscribers/channel-subs
|
|||||||
export class ChannelListComponent implements OnInit {
|
export class ChannelListComponent implements OnInit {
|
||||||
private apiService = inject(ApiService);
|
private apiService = inject(ApiService);
|
||||||
private authService = inject(AuthService);
|
private authService = inject(AuthService);
|
||||||
|
private notification = inject(NotificationService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
private userCacheService = inject(UserCacheService);
|
private userCacheService = inject(UserCacheService);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
|
|
||||||
channels = signal<ChannelWithSubscription[]>([]);
|
allChannels = signal<ChannelWithSubscription[]>([]);
|
||||||
ownerNames = signal<Map<string, ResolvedUser>>(new Map());
|
ownerNames = signal<Map<string, ResolvedUser>>(new Map());
|
||||||
loading = signal(false);
|
loading = signal(false);
|
||||||
|
expertMode = this.settingsService.expertMode;
|
||||||
|
activeTab = signal<ChannelTab>('all');
|
||||||
|
|
||||||
|
channels = computed(() => {
|
||||||
|
const userId = this.authService.getUserId();
|
||||||
|
const all = this.allChannels();
|
||||||
|
const tab = this.activeTab();
|
||||||
|
|
||||||
|
switch (tab) {
|
||||||
|
case 'owned':
|
||||||
|
return all.filter(c => c.owner_user_id === userId);
|
||||||
|
case 'foreign':
|
||||||
|
return all.filter(c => c.owner_user_id !== userId);
|
||||||
|
default:
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.loadChannels();
|
this.loadChannels();
|
||||||
@@ -58,7 +85,7 @@ export class ChannelListComponent implements OnInit {
|
|||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
this.apiService.getChannels(userId, 'all_any').subscribe({
|
this.apiService.getChannels(userId, 'all_any').subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.channels.set(response.channels);
|
this.allChannels.set(response.channels);
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
this.resolveOwnerNames(response.channels);
|
this.resolveOwnerNames(response.channels);
|
||||||
},
|
},
|
||||||
@@ -68,6 +95,22 @@ export class ChannelListComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTabChange(index: number): void {
|
||||||
|
const tabs: ChannelTab[] = ['all', 'owned', 'foreign'];
|
||||||
|
this.activeTab.set(tabs[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTabDescription(): string | null {
|
||||||
|
switch (this.activeTab()) {
|
||||||
|
case 'owned':
|
||||||
|
return 'Channels that you own and can configure.';
|
||||||
|
case 'foreign':
|
||||||
|
return 'Channels owned by other users that you are subscribed to.';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private resolveOwnerNames(channels: ChannelWithSubscription[]): void {
|
private resolveOwnerNames(channels: ChannelWithSubscription[]): void {
|
||||||
const uniqueOwnerIds = [...new Set(channels.map(c => c.owner_user_id))];
|
const uniqueOwnerIds = [...new Set(channels.map(c => c.owner_user_id))];
|
||||||
for (const ownerId of uniqueOwnerIds) {
|
for (const ownerId of uniqueOwnerIds) {
|
||||||
@@ -109,4 +152,28 @@ export class ChannelListComponent implements OnInit {
|
|||||||
|
|
||||||
return { label: 'Not Subscribed', color: 'default' };
|
return { label: 'Not Subscribed', color: 'default' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleSelfSubscription(channel: ChannelWithSubscription, event: Event): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
const userId = this.authService.getUserId();
|
||||||
|
if (!userId) return;
|
||||||
|
|
||||||
|
if (channel.subscription) {
|
||||||
|
// Unsubscribe
|
||||||
|
this.apiService.deleteSubscription(userId, channel.subscription.subscription_id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notification.success('Unsubscribed from channel');
|
||||||
|
this.loadChannels();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Subscribe
|
||||||
|
this.apiService.createSubscription(userId, { channel_id: channel.channel_id }).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notification.success('Subscribed to channel');
|
||||||
|
this.loadChannels();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,9 @@
|
|||||||
.message-content {
|
.message-content {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
white-space: pre;
|
||||||
|
max-height: 2lh;
|
||||||
|
overflow-y: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-name {
|
.cell-name {
|
||||||
|
|||||||
@@ -131,17 +131,16 @@ export class KeyDetailComponent implements OnInit {
|
|||||||
|
|
||||||
loadMessages(keyId: string, nextPageToken?: string): void {
|
loadMessages(keyId: string, nextPageToken?: string): void {
|
||||||
this.loadingMessages.set(true);
|
this.loadingMessages.set(true);
|
||||||
// Load more messages than page size to ensure we get enough after filtering
|
|
||||||
this.apiService.getMessages({
|
this.apiService.getMessages({
|
||||||
page_size: 64,
|
subscription_status: 'all',
|
||||||
|
used_key: keyId,
|
||||||
|
page_size: this.messagesPageSize,
|
||||||
next_page_token: nextPageToken,
|
next_page_token: nextPageToken,
|
||||||
trimmed: true
|
trimmed: true
|
||||||
}).subscribe({
|
}).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
// Filter messages by the key that was used to send them
|
this.messages.set(response.messages);
|
||||||
const filtered = response.messages.filter(m => m.used_key_id === keyId);
|
this.messagesTotalCount.set(response.total_count);
|
||||||
this.messages.set(filtered.slice(0, this.messagesPageSize));
|
|
||||||
this.messagesTotalCount.set(filtered.length);
|
|
||||||
this.messagesNextPageToken.set(response.next_page_token || null);
|
this.messagesNextPageToken.set(response.next_page_token || null);
|
||||||
this.loadingMessages.set(false);
|
this.loadingMessages.set(false);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -101,9 +101,7 @@
|
|||||||
@for (delivery of deliveriesTable.data; track delivery.delivery_id) {
|
@for (delivery of deliveriesTable.data; track delivery.delivery_id) {
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a [routerLink]="['/clients', delivery.receiver_client_id]" class="mono">
|
<span class="mono">{{ delivery.receiver_client_id }}</span>
|
||||||
{{ delivery.receiver_client_id }}
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<nz-tag [nzColor]="getStatusColor(delivery.status)">
|
<nz-tag [nzColor]="getStatusColor(delivery.status)">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, inject, signal, OnInit, computed } from '@angular/core';
|
import { Component, inject, signal, OnInit, computed, effect } from '@angular/core';
|
||||||
import { CommonModule, DatePipe } from '@angular/common';
|
import { CommonModule, DatePipe } from '@angular/common';
|
||||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
import { NzCardModule } from 'ng-zorro-antd/card';
|
import { NzCardModule } from 'ng-zorro-antd/card';
|
||||||
@@ -66,6 +66,15 @@ export class MessageDetailComponent implements OnInit {
|
|||||||
|
|
||||||
showDeliveries = computed(() => this.expertMode() && this.isChannelOwner());
|
showDeliveries = computed(() => this.expertMode() && this.isChannelOwner());
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Watch for expert mode changes and load deliveries when it becomes visible
|
||||||
|
effect(() => {
|
||||||
|
if (this.showDeliveries() && this.message() && this.deliveries().length === 0 && !this.loadingDeliveries()) {
|
||||||
|
this.loadDeliveries(this.message()!.message_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const messageId = this.route.snapshot.paramMap.get('id');
|
const messageId = this.route.snapshot.paramMap.get('id');
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
|
|||||||
@@ -50,6 +50,9 @@
|
|||||||
.message-content {
|
.message-content {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
white-space: pre;
|
||||||
|
max-height: 2lh;
|
||||||
|
overflow-y: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-muted {
|
.text-muted {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
Accept
|
Accept
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@if (subscription()!.confirmed && isOwner()) {
|
@if (subscription()!.confirmed && isOwner() && !isOwnSubscription()) {
|
||||||
<button
|
<button
|
||||||
nz-button
|
nz-button
|
||||||
nz-popconfirm
|
nz-popconfirm
|
||||||
@@ -27,7 +27,25 @@
|
|||||||
Deactivate
|
Deactivate
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@if (expertMode() && isOutgoing() && subscription()!.confirmed && subscription()!.active) {
|
@if (isOwnSubscription()) {
|
||||||
|
@if (subscription()!.active) {
|
||||||
|
<button
|
||||||
|
nz-button
|
||||||
|
nz-popconfirm
|
||||||
|
nzPopconfirmTitle="Deactivate this subscription?"
|
||||||
|
(nzOnConfirm)="setInactive()"
|
||||||
|
>
|
||||||
|
<span nz-icon nzType="pause-circle"></span>
|
||||||
|
Deactivate
|
||||||
|
</button>
|
||||||
|
} @else {
|
||||||
|
<button nz-button nzType="primary" (click)="activateSubscription()">
|
||||||
|
<span nz-icon nzType="play-circle"></span>
|
||||||
|
Activate
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if (expertMode() && isOutgoing() && subscription()!.confirmed && subscription()!.active && !isOwnSubscription()) {
|
||||||
<button
|
<button
|
||||||
nz-button
|
nz-button
|
||||||
nz-popconfirm
|
nz-popconfirm
|
||||||
|
|||||||
@@ -118,6 +118,13 @@ export class SubscriptionDetailComponent implements OnInit {
|
|||||||
return sub.channel_owner_user_id === userId;
|
return sub.channel_owner_user_id === userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isOwnSubscription(): boolean {
|
||||||
|
const sub = this.subscription();
|
||||||
|
if (!sub) return false;
|
||||||
|
const userId = this.authService.getUserId();
|
||||||
|
return sub.subscriber_user_id === userId && sub.channel_owner_user_id === userId;
|
||||||
|
}
|
||||||
|
|
||||||
getStatusInfo(): { label: string; color: string } {
|
getStatusInfo(): { label: string; color: string } {
|
||||||
const sub = this.subscription();
|
const sub = this.subscription();
|
||||||
if (!sub) return { label: 'Unknown', color: 'default' };
|
if (!sub) return { label: 'Unknown', color: 'default' };
|
||||||
@@ -153,6 +160,19 @@ export class SubscriptionDetailComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activateSubscription(): void {
|
||||||
|
const sub = this.subscription();
|
||||||
|
const userId = this.authService.getUserId();
|
||||||
|
if (!sub || !userId) return;
|
||||||
|
|
||||||
|
this.apiService.confirmSubscription(userId, sub.subscription_id, { active: true }).subscribe({
|
||||||
|
next: (updated) => {
|
||||||
|
this.subscription.set(updated);
|
||||||
|
this.notification.success('Subscription activated');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
deactivateSubscription(): void {
|
deactivateSubscription(): void {
|
||||||
const sub = this.subscription();
|
const sub = this.subscription();
|
||||||
const userId = this.authService.getUserId();
|
const userId = this.authService.getUserId();
|
||||||
|
|||||||
@@ -133,6 +133,33 @@
|
|||||||
<span nz-icon nzType="close"></span>
|
<span nz-icon nzType="close"></span>
|
||||||
</button>
|
</button>
|
||||||
} @else {
|
} @else {
|
||||||
|
<!-- Own subscriptions: can activate/deactivate -->
|
||||||
|
@if (isOwnSubscription(sub)) {
|
||||||
|
@if (sub.active) {
|
||||||
|
<button
|
||||||
|
nz-button
|
||||||
|
nzSize="small"
|
||||||
|
nz-tooltip
|
||||||
|
nzTooltipTitle="Deactivate"
|
||||||
|
nz-popconfirm
|
||||||
|
nzPopconfirmTitle="Deactivate this subscription?"
|
||||||
|
(nzOnConfirm)="deactivateSubscription(sub)"
|
||||||
|
>
|
||||||
|
<span nz-icon nzType="pause-circle"></span>
|
||||||
|
</button>
|
||||||
|
} @else {
|
||||||
|
<button
|
||||||
|
nz-button
|
||||||
|
nzSize="small"
|
||||||
|
nzType="primary"
|
||||||
|
nz-tooltip
|
||||||
|
nzTooltipTitle="Activate"
|
||||||
|
(click)="activateSubscription(sub)"
|
||||||
|
>
|
||||||
|
<span nz-icon nzType="play-circle"></span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
<!-- Confirmed or outgoing: can revoke -->
|
<!-- Confirmed or outgoing: can revoke -->
|
||||||
<button
|
<button
|
||||||
nz-button
|
nz-button
|
||||||
|
|||||||
@@ -159,6 +159,11 @@ export class SubscriptionListComponent implements OnInit {
|
|||||||
return sub.channel_owner_user_id === userId;
|
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 {
|
viewSubscription(sub: Subscription): void {
|
||||||
this.router.navigate(['/subscriptions', sub.subscription_id]);
|
this.router.navigate(['/subscriptions', sub.subscription_id]);
|
||||||
}
|
}
|
||||||
@@ -200,6 +205,30 @@ export class SubscriptionListComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Create subscription
|
||||||
openCreateModal(): void {
|
openCreateModal(): void {
|
||||||
this.newChannelOwner = '';
|
this.newChannelOwner = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user