mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
fix: resolve telegram auth token expiration and clean up codebase
- Fix stale initData comparison in clearStaleSessionIfNeeded that destroyed valid refresh tokens on mobile WebView reopens - Restrict X-Telegram-Init-Data header to auth endpoints only - Close Mini App on auth retry to force fresh initData from Telegram - Merge Connection page error/not-configured states for better UX - Remove unnecessary comments across 40+ files (section dividers, restating comments, noise catch block comments) - Configure ESLint allowEmptyCatch to properly handle intentional empty catch blocks (62 warnings resolved)
This commit is contained in:
@@ -23,8 +23,6 @@ import {
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { adminPaymentMethodsApi } from '../api/adminPaymentMethods';
|
||||
import type { PaymentMethodConfig } from '../types';
|
||||
// ============ Icons ============
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
@@ -63,8 +61,6 @@ const SaveIcon = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
// ============ Sortable Card ============
|
||||
|
||||
interface SortableCardProps {
|
||||
config: PaymentMethodConfig;
|
||||
onClick: () => void;
|
||||
@@ -184,10 +180,6 @@ function SortablePaymentCard({ config, onClick }: SortableCardProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// ============ Toast ============
|
||||
|
||||
// ============ Main Page ============
|
||||
|
||||
export default function AdminPaymentMethods() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -6,8 +6,6 @@ import { rbacApi, AccessPolicy, AdminRole } from '@/api/rbac';
|
||||
import { PermissionGate } from '@/components/auth/PermissionGate';
|
||||
import { usePlatform } from '@/platform/hooks/usePlatform';
|
||||
|
||||
// === Icons ===
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
@@ -96,8 +94,6 @@ const CalendarIcon = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
// === Helpers ===
|
||||
|
||||
interface PolicyConditions {
|
||||
time_range?: { start: string; end: string };
|
||||
ip_whitelist?: string[];
|
||||
@@ -130,8 +126,6 @@ function parseConditions(raw: Record<string, unknown>): PolicyConditions {
|
||||
return result;
|
||||
}
|
||||
|
||||
// === Sub-components ===
|
||||
|
||||
interface EffectBadgeProps {
|
||||
effect: 'allow' | 'deny';
|
||||
className?: string;
|
||||
@@ -154,8 +148,6 @@ function EffectBadge({ effect, className }: EffectBadgeProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// === Main Page ===
|
||||
|
||||
export default function AdminPolicies() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -23,8 +23,6 @@ import {
|
||||
RemnawaveIcon,
|
||||
} from '../components/icons';
|
||||
|
||||
// ============ Icons ============
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
@@ -37,8 +35,6 @@ const BackIcon = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
// ============ Helpers ============
|
||||
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
@@ -100,8 +96,6 @@ const getCountryFlag = (code: string | null | undefined): string => {
|
||||
return codeMap[code.toUpperCase()] || code;
|
||||
};
|
||||
|
||||
// ============ Sub-Components ============
|
||||
|
||||
interface StatCardProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
@@ -333,8 +327,6 @@ function SyncCard({ title, description, onAction, isLoading, lastResult }: SyncC
|
||||
);
|
||||
}
|
||||
|
||||
// ============ Tab Components ============
|
||||
|
||||
interface OverviewTabProps {
|
||||
stats: SystemStatsResponse | undefined;
|
||||
isLoading: boolean;
|
||||
@@ -873,8 +865,6 @@ function SyncTab({
|
||||
);
|
||||
}
|
||||
|
||||
// ============ Main Component ============
|
||||
|
||||
type TabType = 'overview' | 'nodes' | 'squads' | 'sync';
|
||||
|
||||
export default function AdminRemnawave() {
|
||||
|
||||
@@ -7,8 +7,6 @@ import { PermissionGate } from '@/components/auth/PermissionGate';
|
||||
import { usePermissionStore } from '@/store/permissions';
|
||||
import { usePlatform } from '@/platform/hooks/usePlatform';
|
||||
|
||||
// === Icons ===
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
@@ -57,8 +55,6 @@ const ShieldIcon = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
// === Main Page ===
|
||||
|
||||
export default function AdminRoles() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -433,7 +433,6 @@ export default function AdminUserDetail() {
|
||||
const data = await adminUsersApi.getReferrals(userId, 0, 50);
|
||||
setReferrals(data.users);
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setReferralsLoading(false);
|
||||
}
|
||||
@@ -446,7 +445,6 @@ export default function AdminUserDetail() {
|
||||
const data = await adminUsersApi.getPanelInfo(userId);
|
||||
setPanelInfo(data);
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setPanelInfoLoading(false);
|
||||
}
|
||||
@@ -457,9 +455,7 @@ export default function AdminUserDetail() {
|
||||
try {
|
||||
const data = await adminUsersApi.getNodeUsage(userId);
|
||||
setNodeUsage(data);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
} catch {}
|
||||
}, [userId]);
|
||||
|
||||
const loadDevices = useCallback(async () => {
|
||||
@@ -471,7 +467,6 @@ export default function AdminUserDetail() {
|
||||
setDevicesTotal(data.total);
|
||||
setDeviceLimit(data.device_limit);
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setDevicesLoading(false);
|
||||
}
|
||||
@@ -488,7 +483,6 @@ export default function AdminUserDetail() {
|
||||
const data = await adminUsersApi.getUserGifts(userId);
|
||||
setGiftsData(data);
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setGiftsLoading(false);
|
||||
}
|
||||
@@ -498,9 +492,7 @@ export default function AdminUserDetail() {
|
||||
try {
|
||||
const data = await promocodesApi.getPromoGroups({ limit: 100 });
|
||||
setPromoGroups(data.items);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
} catch {}
|
||||
}, []);
|
||||
|
||||
const handleTicketReply = async () => {
|
||||
@@ -954,9 +946,7 @@ export default function AdminUserDetail() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
notify.success(t('admin.users.detail.copied'));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
|
||||
@@ -5,8 +5,6 @@ import { useCurrency } from '../hooks/useCurrency';
|
||||
import { adminUsersApi, type UserListItem, type UsersStatsResponse } from '../api/adminUsers';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// ============ Icons ============
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
@@ -57,8 +55,6 @@ const TelegramIcon = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
// ============ Components ============
|
||||
|
||||
interface StatCardProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
@@ -101,8 +97,6 @@ function StatusBadge({ status }: { status: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
// ============ User List Component ============
|
||||
|
||||
interface UserRowProps {
|
||||
user: UserListItem;
|
||||
onClick: () => void;
|
||||
@@ -181,8 +175,6 @@ function UserRow({ user, onClick, formatAmount }: UserRowProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// ============ Main Page ============
|
||||
|
||||
export default function AdminUsers() {
|
||||
const { t } = useTranslation();
|
||||
const { formatWithCurrency } = useCurrency();
|
||||
|
||||
@@ -29,8 +29,6 @@ import { ColorPicker } from '@/components/ColorPicker';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
import { toNumber } from '../utils/inputHelpers';
|
||||
|
||||
// Icons
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
@@ -181,8 +179,6 @@ const PRIZE_TYPE_KEYS = [
|
||||
|
||||
type Tab = 'settings' | 'prizes' | 'statistics';
|
||||
|
||||
// ============ Sortable Prize Card ============
|
||||
|
||||
interface SortablePrizeCardProps {
|
||||
prize: WheelPrizeAdmin;
|
||||
isExpanded: boolean;
|
||||
|
||||
@@ -98,7 +98,6 @@ export default function Connection() {
|
||||
);
|
||||
}, [appConfig?.platforms]);
|
||||
|
||||
// Loading
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center py-20">
|
||||
@@ -107,20 +106,7 @@ export default function Connection() {
|
||||
);
|
||||
}
|
||||
|
||||
// Error
|
||||
if (error || !appConfig) {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center p-8 text-center">
|
||||
<p className="mb-4 text-lg text-dark-300">{t('common.error')}</p>
|
||||
<button onClick={handleGoBack} className="btn-primary px-6 py-2">
|
||||
{t('common.close')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// No apps configured — check before subscription since empty config also has no subscription
|
||||
if (!hasApps) {
|
||||
if (error || !appConfig || !hasApps) {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center p-8 text-center">
|
||||
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-dark-800">
|
||||
|
||||
@@ -21,10 +21,6 @@ import { getApiErrorMessage } from '../utils/api-error';
|
||||
import { formatPrice } from '../utils/format';
|
||||
import { usePlatform, useHaptic } from '@/platform';
|
||||
|
||||
// ============================================================
|
||||
// SVG Icons
|
||||
// ============================================================
|
||||
|
||||
function GiftIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
@@ -131,10 +127,6 @@ function InboxIcon({ className }: { className?: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helpers
|
||||
// ============================================================
|
||||
|
||||
function formatPeriodLabel(
|
||||
days: number,
|
||||
t: (key: string, options?: Record<string, unknown>) => string,
|
||||
@@ -181,16 +173,8 @@ function formatGiftDate(dateStr: string | null): string {
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Tab type
|
||||
// ============================================================
|
||||
|
||||
type TabId = 'buy' | 'activate' | 'myGifts';
|
||||
|
||||
// ============================================================
|
||||
// Sub-components: Shared
|
||||
// ============================================================
|
||||
|
||||
function LoadingSkeleton() {
|
||||
return (
|
||||
<div className="flex min-h-dvh items-center justify-center">
|
||||
@@ -263,10 +247,6 @@ function DisabledState() {
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Sub-components: Buy Tab
|
||||
// ============================================================
|
||||
|
||||
function TariffCard({
|
||||
tariff,
|
||||
isSelected,
|
||||
@@ -904,10 +884,6 @@ function BuyTabContent({
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Sub-components: Activate Tab
|
||||
// ============================================================
|
||||
|
||||
function ActivateTabContent({ initialCode }: { initialCode?: string | null }) {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -1023,10 +999,6 @@ function ActivateTabContent({ initialCode }: { initialCode?: string | null }) {
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Sub-components: My Gifts Tab
|
||||
// ============================================================
|
||||
|
||||
function CopiedToast({ onDismiss }: { onDismiss: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1320,20 +1292,12 @@ function MyGiftsTabContent() {
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Tab animation variants
|
||||
// ============================================================
|
||||
|
||||
const tabContentVariants = {
|
||||
initial: { opacity: 0, x: 20 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
exit: { opacity: 0, x: -20 },
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Main Component
|
||||
// ============================================================
|
||||
|
||||
export default function GiftSubscription() {
|
||||
const { t } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '../api/branding';
|
||||
import { getAndClearReturnUrl } from '../utils/token';
|
||||
import { isInTelegramWebApp, getTelegramInitData, useTelegramSDK } from '../hooks/useTelegramSDK';
|
||||
import { closeMiniApp } from '@telegram-apps/sdk-react';
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher';
|
||||
import TelegramLoginButton from '../components/TelegramLoginButton';
|
||||
import OAuthProviderIcon from '../components/OAuthProviderIcon';
|
||||
@@ -213,30 +214,13 @@ export default function Login() {
|
||||
tryTelegramAuth();
|
||||
}, [isAuthInitializing, loginWithTelegram, navigate, t, getReturnUrl]);
|
||||
|
||||
// Manual retry for Telegram Mini App auth
|
||||
const handleRetryTelegramAuth = async () => {
|
||||
const initData = getTelegramInitData();
|
||||
if (!initData) {
|
||||
setError(t('auth.telegramRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
setError('');
|
||||
setIsLoading(true);
|
||||
const handleRetryTelegramAuth = () => {
|
||||
try {
|
||||
await loginWithTelegram(initData);
|
||||
navigate(getReturnUrl(), { replace: true });
|
||||
} catch (err) {
|
||||
const error = err as { response?: { status?: number; data?: { detail?: string } } };
|
||||
const status = error.response?.status;
|
||||
const detail = error.response?.data?.detail;
|
||||
if (import.meta.env.DEV) console.warn('Telegram auth retry failed:', status, detail);
|
||||
setError(
|
||||
detail ||
|
||||
t('auth.telegramRetryFailed', 'Authorization failed. Close the app and try again.'),
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
sessionStorage.removeItem('tapps/launchParams');
|
||||
sessionStorage.removeItem('telegram_init_data');
|
||||
closeMiniApp();
|
||||
} catch {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@ import { cn } from '../lib/utils';
|
||||
|
||||
const MAX_POLL_MS = 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
// ============================================================
|
||||
// Sub-components
|
||||
// ============================================================
|
||||
|
||||
function PendingState() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -600,10 +596,6 @@ function PollTimedOutState({ onRetry }: { onRetry: () => void }) {
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Main Component
|
||||
// ============================================================
|
||||
|
||||
export default function PurchaseSuccess() {
|
||||
const { t } = useTranslation();
|
||||
const { token } = useParams<{ token: string }>();
|
||||
|
||||
@@ -48,10 +48,6 @@ function formatPeriodLabel(
|
||||
return t('landing.periodLabels.nDays', { count: days });
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Sub-components
|
||||
// ============================================================
|
||||
|
||||
function LoadingSkeleton() {
|
||||
return (
|
||||
<div className="flex min-h-dvh items-center justify-center bg-dark-950">
|
||||
@@ -611,10 +607,6 @@ function SummaryCard({
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Discount Countdown
|
||||
// ============================================================
|
||||
|
||||
function TimeUnit({ value, label }: { value: number; label: string }) {
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
@@ -715,10 +707,6 @@ function DiscountBanner({
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Main Component
|
||||
// ============================================================
|
||||
|
||||
export default function QuickPurchase() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
@@ -239,9 +239,7 @@ export default function Referral() {
|
||||
text: shareText,
|
||||
url: referralLink,
|
||||
})
|
||||
.catch(() => {
|
||||
// ignore cancellation errors
|
||||
});
|
||||
.catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -425,7 +425,6 @@ export default function Wheel() {
|
||||
// Use the pending result from polling, or show a fallback
|
||||
if (pendingStarsResultRef.current) {
|
||||
setSpinResult(pendingStarsResultRef.current);
|
||||
// Haptic feedback based on result
|
||||
if (pendingStarsResultRef.current.prize_type === 'nothing') {
|
||||
haptic.notification('warning');
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user