feat: add multi-channel subscription blocking UI and admin management

- Channel subscription blocking screen with per-channel status display
- Safe URL handling with protocol validation (no open redirect)
- Race-condition-safe subscription check with useRef guard
- Admin channel management page with create, toggle, delete
- Error handlers on all admin mutations with haptic feedback
- Proper type guard for channel subscription error detection
- Accessibility: aria-labels on icon-only buttons
- Localization: en, ru, zh, fa translations
This commit is contained in:
Fringg
2026-02-24 02:50:52 +03:00
parent 8ea9a1d6a0
commit a767fe96d3
12 changed files with 758 additions and 113 deletions

68
src/api/adminChannels.ts Normal file
View File

@@ -0,0 +1,68 @@
import apiClient from './client';
// Types
export interface RequiredChannel {
id: number;
channel_id: string;
channel_link: string | null;
title: string | null;
is_active: boolean;
sort_order: number;
}
export interface ChannelListResponse {
items: RequiredChannel[];
total: number;
}
export interface CreateChannelRequest {
channel_id: string;
channel_link?: string;
title?: string;
}
export interface UpdateChannelRequest {
channel_id?: string;
channel_link?: string;
title?: string;
is_active?: boolean;
sort_order?: number;
}
export const adminChannelsApi = {
list: async (): Promise<ChannelListResponse> => {
const { data } = await apiClient.get<ChannelListResponse>(
'/cabinet/admin/channel-subscriptions',
);
return data;
},
create: async (req: CreateChannelRequest): Promise<RequiredChannel> => {
const { data } = await apiClient.post<RequiredChannel>(
'/cabinet/admin/channel-subscriptions',
req,
);
return data;
},
update: async (id: number, req: UpdateChannelRequest): Promise<RequiredChannel> => {
const { data } = await apiClient.patch<RequiredChannel>(
`/cabinet/admin/channel-subscriptions/${id}`,
req,
);
return data;
},
toggle: async (id: number): Promise<RequiredChannel> => {
const { data } = await apiClient.post<RequiredChannel>(
`/cabinet/admin/channel-subscriptions/${id}/toggle`,
);
return data;
},
delete: async (id: number): Promise<void> => {
await apiClient.delete(`/cabinet/admin/channel-subscriptions/${id}`);
},
};
export default adminChannelsApi;