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 Layout from './components/layout/Layout';
|
||||
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 { useAnalyticsCounters } from './hooks/useAnalyticsCounters';
|
||||
// Auth pages - load immediately (small)
|
||||
@@ -122,6 +126,10 @@ function BlockingOverlay() {
|
||||
return <ChannelSubscriptionScreen />;
|
||||
}
|
||||
|
||||
if (blockingType === 'blacklisted') {
|
||||
return <BlacklistedScreen />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,11 @@ export interface ChannelSubscriptionError {
|
||||
channel_link?: string;
|
||||
}
|
||||
|
||||
export interface BlacklistedError {
|
||||
code: 'blacklisted';
|
||||
message: string;
|
||||
}
|
||||
|
||||
export function isMaintenanceError(
|
||||
error: unknown,
|
||||
): 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)
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
@@ -156,6 +169,15 @@ apiClient.interceptors.response.use(
|
||||
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 не сработала)
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
// Не обрабатываем 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 ChannelSubscriptionScreen } from './ChannelSubscriptionScreen';
|
||||
export { default as BlacklistedScreen } from './BlacklistedScreen';
|
||||
|
||||
@@ -2627,6 +2627,12 @@
|
||||
"hint": "Click check button after subscribing",
|
||||
"notSubscribed": "You haven't subscribed to the channel yet",
|
||||
"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": "پس از عضویت دکمه بررسی را بزنید",
|
||||
"notSubscribed": "شما هنوز در کانال عضو نشدهاید",
|
||||
"checkError": "بررسی ناموفق بود. لطفاً بعداً دوباره امتحان کنید."
|
||||
},
|
||||
"blacklisted": {
|
||||
"title": "دسترسی ممنوع",
|
||||
"defaultMessage": "حساب شما مسدود شده است.",
|
||||
"reason": "دلیل",
|
||||
"contactSupport": "اگر فکر میکنید این اشتباه است، با پشتیبانی تماس بگیرید."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3340,6 +3340,12 @@
|
||||
"hint": "После подписки нажмите кнопку проверки",
|
||||
"notSubscribed": "Вы ещё не подписались на канал",
|
||||
"checkError": "Ошибка проверки. Попробуйте позже."
|
||||
},
|
||||
"blacklisted": {
|
||||
"title": "Доступ запрещён",
|
||||
"defaultMessage": "Ваш аккаунт заблокирован.",
|
||||
"reason": "Причина",
|
||||
"contactSupport": "Если вы считаете, что это ошибка, обратитесь в поддержку."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2152,6 +2152,12 @@
|
||||
"hint": "订阅后点击检查按钮",
|
||||
"notSubscribed": "您还没有订阅该频道",
|
||||
"checkError": "检查失败。请稍后重试。"
|
||||
},
|
||||
"blacklisted": {
|
||||
"title": "访问被拒绝",
|
||||
"defaultMessage": "您的帐户已被封锁。",
|
||||
"reason": "原因",
|
||||
"contactSupport": "如果您认为这是错误,请联系客服。"
|
||||
}
|
||||
},
|
||||
"banSystem": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export type BlockingType = 'maintenance' | 'channel_subscription' | null;
|
||||
export type BlockingType = 'maintenance' | 'channel_subscription' | 'blacklisted' | null;
|
||||
|
||||
interface MaintenanceInfo {
|
||||
message: string;
|
||||
@@ -12,13 +12,19 @@ interface ChannelSubscriptionInfo {
|
||||
channel_link?: string;
|
||||
}
|
||||
|
||||
interface BlacklistedInfo {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface BlockingState {
|
||||
blockingType: BlockingType;
|
||||
maintenanceInfo: MaintenanceInfo | null;
|
||||
channelInfo: ChannelSubscriptionInfo | null;
|
||||
blacklistedInfo: BlacklistedInfo | null;
|
||||
|
||||
setMaintenance: (info: MaintenanceInfo) => void;
|
||||
setChannelSubscription: (info: ChannelSubscriptionInfo) => void;
|
||||
setBlacklisted: (info: BlacklistedInfo) => void;
|
||||
clearBlocking: () => void;
|
||||
}
|
||||
|
||||
@@ -26,12 +32,14 @@ export const useBlockingStore = create<BlockingState>((set) => ({
|
||||
blockingType: null,
|
||||
maintenanceInfo: null,
|
||||
channelInfo: null,
|
||||
blacklistedInfo: null,
|
||||
|
||||
setMaintenance: (info) =>
|
||||
set({
|
||||
blockingType: 'maintenance',
|
||||
maintenanceInfo: info,
|
||||
channelInfo: null,
|
||||
blacklistedInfo: null,
|
||||
}),
|
||||
|
||||
setChannelSubscription: (info) =>
|
||||
@@ -39,6 +47,15 @@ export const useBlockingStore = create<BlockingState>((set) => ({
|
||||
blockingType: 'channel_subscription',
|
||||
channelInfo: info,
|
||||
maintenanceInfo: null,
|
||||
blacklistedInfo: null,
|
||||
}),
|
||||
|
||||
setBlacklisted: (info) =>
|
||||
set({
|
||||
blockingType: 'blacklisted',
|
||||
blacklistedInfo: info,
|
||||
maintenanceInfo: null,
|
||||
channelInfo: null,
|
||||
}),
|
||||
|
||||
clearBlocking: () =>
|
||||
@@ -46,5 +63,6 @@ export const useBlockingStore = create<BlockingState>((set) => ({
|
||||
blockingType: null,
|
||||
maintenanceInfo: null,
|
||||
channelInfo: null,
|
||||
blacklistedInfo: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user