mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
Merge pull request #153 from BEDOLAGA-DEV/feat/blacklist-blocking-screen
feat: add blacklisted user blocking screen
This commit is contained in:
10
src/App.tsx
10
src/App.tsx
@@ -4,7 +4,11 @@ import { useAuthStore } from './store/auth';
|
|||||||
import { useBlockingStore } from './store/blocking';
|
import { useBlockingStore } from './store/blocking';
|
||||||
import Layout from './components/layout/Layout';
|
import Layout from './components/layout/Layout';
|
||||||
import PageLoader from './components/common/PageLoader';
|
import PageLoader from './components/common/PageLoader';
|
||||||
import { MaintenanceScreen, ChannelSubscriptionScreen } from './components/blocking';
|
import {
|
||||||
|
MaintenanceScreen,
|
||||||
|
ChannelSubscriptionScreen,
|
||||||
|
BlacklistedScreen,
|
||||||
|
} from './components/blocking';
|
||||||
import { saveReturnUrl } from './utils/token';
|
import { saveReturnUrl } from './utils/token';
|
||||||
import { useAnalyticsCounters } from './hooks/useAnalyticsCounters';
|
import { useAnalyticsCounters } from './hooks/useAnalyticsCounters';
|
||||||
// Auth pages - load immediately (small)
|
// Auth pages - load immediately (small)
|
||||||
@@ -122,6 +126,10 @@ function BlockingOverlay() {
|
|||||||
return <ChannelSubscriptionScreen />;
|
return <ChannelSubscriptionScreen />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (blockingType === 'blacklisted') {
|
||||||
|
return <BlacklistedScreen />;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,11 @@ export interface ChannelSubscriptionError {
|
|||||||
channel_link?: string;
|
channel_link?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlacklistedError {
|
||||||
|
code: 'blacklisted';
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
export function isMaintenanceError(
|
export function isMaintenanceError(
|
||||||
error: unknown,
|
error: unknown,
|
||||||
): error is { response: { status: 503; data: { detail: MaintenanceError } } } {
|
): error is { response: { status: 503; data: { detail: MaintenanceError } } } {
|
||||||
@@ -130,6 +135,14 @@ export function isChannelSubscriptionError(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isBlacklistedError(
|
||||||
|
error: unknown,
|
||||||
|
): error is { response: { status: 403; data: { detail: BlacklistedError } } } {
|
||||||
|
if (!error || typeof error !== 'object') return false;
|
||||||
|
const err = error as AxiosError<{ detail: BlacklistedError }>;
|
||||||
|
return err.response?.status === 403 && err.response?.data?.detail?.code === 'blacklisted';
|
||||||
|
}
|
||||||
|
|
||||||
// Response interceptor - handle 401, 503 (maintenance), 403 (channel subscription)
|
// Response interceptor - handle 401, 503 (maintenance), 403 (channel subscription)
|
||||||
apiClient.interceptors.response.use(
|
apiClient.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
@@ -156,6 +169,15 @@ apiClient.interceptors.response.use(
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle blacklisted user (403)
|
||||||
|
if (isBlacklistedError(error)) {
|
||||||
|
const detail = (error.response?.data as { detail: BlacklistedError }).detail;
|
||||||
|
useBlockingStore.getState().setBlacklisted({
|
||||||
|
message: detail.message,
|
||||||
|
});
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
// Если получили 401 и ещё не пробовали refresh (на случай если проверка exp не сработала)
|
// Если получили 401 и ещё не пробовали refresh (на случай если проверка exp не сработала)
|
||||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||||
// Не обрабатываем 401 для авторизационных endpoints - пусть ошибка дойдет до компонента
|
// Не обрабатываем 401 для авторизационных endpoints - пусть ошибка дойдет до компонента
|
||||||
|
|||||||
48
src/components/blocking/BlacklistedScreen.tsx
Normal file
48
src/components/blocking/BlacklistedScreen.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useBlockingStore } from '../../store/blocking';
|
||||||
|
|
||||||
|
export default function BlacklistedScreen() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { blacklistedInfo } = useBlockingStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6">
|
||||||
|
<div className="w-full max-w-md text-center">
|
||||||
|
{/* Icon */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="mx-auto flex h-24 w-24 items-center justify-center rounded-full bg-dark-800">
|
||||||
|
<svg
|
||||||
|
className="h-12 w-12 text-red-500"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h1 className="mb-4 text-2xl font-bold text-white">{t('blocking.blacklisted.title')}</h1>
|
||||||
|
|
||||||
|
{/* Message */}
|
||||||
|
<p className="mb-6 text-lg text-gray-400">{t('blocking.blacklisted.defaultMessage')}</p>
|
||||||
|
|
||||||
|
{/* Reason */}
|
||||||
|
{blacklistedInfo?.message && (
|
||||||
|
<div className="mb-6 rounded-xl bg-dark-800/50 p-4">
|
||||||
|
<p className="mb-1 text-sm text-gray-500">{t('blocking.blacklisted.reason')}:</p>
|
||||||
|
<p className="text-gray-300">{blacklistedInfo.message}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="mt-8 text-sm text-gray-500">{t('blocking.blacklisted.contactSupport')}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export { default as MaintenanceScreen } from './MaintenanceScreen';
|
export { default as MaintenanceScreen } from './MaintenanceScreen';
|
||||||
export { default as ChannelSubscriptionScreen } from './ChannelSubscriptionScreen';
|
export { default as ChannelSubscriptionScreen } from './ChannelSubscriptionScreen';
|
||||||
|
export { default as BlacklistedScreen } from './BlacklistedScreen';
|
||||||
|
|||||||
@@ -2627,6 +2627,12 @@
|
|||||||
"hint": "Click check button after subscribing",
|
"hint": "Click check button after subscribing",
|
||||||
"notSubscribed": "You haven't subscribed to the channel yet",
|
"notSubscribed": "You haven't subscribed to the channel yet",
|
||||||
"checkError": "Check failed. Please try again later."
|
"checkError": "Check failed. Please try again later."
|
||||||
|
},
|
||||||
|
"blacklisted": {
|
||||||
|
"title": "Access Denied",
|
||||||
|
"defaultMessage": "Your account has been blocked.",
|
||||||
|
"reason": "Reason",
|
||||||
|
"contactSupport": "If you believe this is an error, please contact support."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2271,6 +2271,12 @@
|
|||||||
"hint": "پس از عضویت دکمه بررسی را بزنید",
|
"hint": "پس از عضویت دکمه بررسی را بزنید",
|
||||||
"notSubscribed": "شما هنوز در کانال عضو نشدهاید",
|
"notSubscribed": "شما هنوز در کانال عضو نشدهاید",
|
||||||
"checkError": "بررسی ناموفق بود. لطفاً بعداً دوباره امتحان کنید."
|
"checkError": "بررسی ناموفق بود. لطفاً بعداً دوباره امتحان کنید."
|
||||||
|
},
|
||||||
|
"blacklisted": {
|
||||||
|
"title": "دسترسی ممنوع",
|
||||||
|
"defaultMessage": "حساب شما مسدود شده است.",
|
||||||
|
"reason": "دلیل",
|
||||||
|
"contactSupport": "اگر فکر میکنید این اشتباه است، با پشتیبانی تماس بگیرید."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3340,6 +3340,12 @@
|
|||||||
"hint": "После подписки нажмите кнопку проверки",
|
"hint": "После подписки нажмите кнопку проверки",
|
||||||
"notSubscribed": "Вы ещё не подписались на канал",
|
"notSubscribed": "Вы ещё не подписались на канал",
|
||||||
"checkError": "Ошибка проверки. Попробуйте позже."
|
"checkError": "Ошибка проверки. Попробуйте позже."
|
||||||
|
},
|
||||||
|
"blacklisted": {
|
||||||
|
"title": "Доступ запрещён",
|
||||||
|
"defaultMessage": "Ваш аккаунт заблокирован.",
|
||||||
|
"reason": "Причина",
|
||||||
|
"contactSupport": "Если вы считаете, что это ошибка, обратитесь в поддержку."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2152,6 +2152,12 @@
|
|||||||
"hint": "订阅后点击检查按钮",
|
"hint": "订阅后点击检查按钮",
|
||||||
"notSubscribed": "您还没有订阅该频道",
|
"notSubscribed": "您还没有订阅该频道",
|
||||||
"checkError": "检查失败。请稍后重试。"
|
"checkError": "检查失败。请稍后重试。"
|
||||||
|
},
|
||||||
|
"blacklisted": {
|
||||||
|
"title": "访问被拒绝",
|
||||||
|
"defaultMessage": "您的帐户已被封锁。",
|
||||||
|
"reason": "原因",
|
||||||
|
"contactSupport": "如果您认为这是错误,请联系客服。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"banSystem": {
|
"banSystem": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
export type BlockingType = 'maintenance' | 'channel_subscription' | null;
|
export type BlockingType = 'maintenance' | 'channel_subscription' | 'blacklisted' | null;
|
||||||
|
|
||||||
interface MaintenanceInfo {
|
interface MaintenanceInfo {
|
||||||
message: string;
|
message: string;
|
||||||
@@ -12,13 +12,19 @@ interface ChannelSubscriptionInfo {
|
|||||||
channel_link?: string;
|
channel_link?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BlacklistedInfo {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface BlockingState {
|
interface BlockingState {
|
||||||
blockingType: BlockingType;
|
blockingType: BlockingType;
|
||||||
maintenanceInfo: MaintenanceInfo | null;
|
maintenanceInfo: MaintenanceInfo | null;
|
||||||
channelInfo: ChannelSubscriptionInfo | null;
|
channelInfo: ChannelSubscriptionInfo | null;
|
||||||
|
blacklistedInfo: BlacklistedInfo | null;
|
||||||
|
|
||||||
setMaintenance: (info: MaintenanceInfo) => void;
|
setMaintenance: (info: MaintenanceInfo) => void;
|
||||||
setChannelSubscription: (info: ChannelSubscriptionInfo) => void;
|
setChannelSubscription: (info: ChannelSubscriptionInfo) => void;
|
||||||
|
setBlacklisted: (info: BlacklistedInfo) => void;
|
||||||
clearBlocking: () => void;
|
clearBlocking: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,12 +32,14 @@ export const useBlockingStore = create<BlockingState>((set) => ({
|
|||||||
blockingType: null,
|
blockingType: null,
|
||||||
maintenanceInfo: null,
|
maintenanceInfo: null,
|
||||||
channelInfo: null,
|
channelInfo: null,
|
||||||
|
blacklistedInfo: null,
|
||||||
|
|
||||||
setMaintenance: (info) =>
|
setMaintenance: (info) =>
|
||||||
set({
|
set({
|
||||||
blockingType: 'maintenance',
|
blockingType: 'maintenance',
|
||||||
maintenanceInfo: info,
|
maintenanceInfo: info,
|
||||||
channelInfo: null,
|
channelInfo: null,
|
||||||
|
blacklistedInfo: null,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setChannelSubscription: (info) =>
|
setChannelSubscription: (info) =>
|
||||||
@@ -39,6 +47,15 @@ export const useBlockingStore = create<BlockingState>((set) => ({
|
|||||||
blockingType: 'channel_subscription',
|
blockingType: 'channel_subscription',
|
||||||
channelInfo: info,
|
channelInfo: info,
|
||||||
maintenanceInfo: null,
|
maintenanceInfo: null,
|
||||||
|
blacklistedInfo: null,
|
||||||
|
}),
|
||||||
|
|
||||||
|
setBlacklisted: (info) =>
|
||||||
|
set({
|
||||||
|
blockingType: 'blacklisted',
|
||||||
|
blacklistedInfo: info,
|
||||||
|
maintenanceInfo: null,
|
||||||
|
channelInfo: null,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
clearBlocking: () =>
|
clearBlocking: () =>
|
||||||
@@ -46,5 +63,6 @@ export const useBlockingStore = create<BlockingState>((set) => ({
|
|||||||
blockingType: null,
|
blockingType: null,
|
||||||
maintenanceInfo: null,
|
maintenanceInfo: null,
|
||||||
channelInfo: null,
|
channelInfo: null,
|
||||||
|
blacklistedInfo: null,
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user