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:
c0mrade
2026-03-13 17:50:49 +03:00
parent 682b6b70dc
commit 2dab25c5a0
43 changed files with 72 additions and 647 deletions

View File

@@ -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();

View File

@@ -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();

View File

@@ -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() {

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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;

View File

@@ -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">

View File

@@ -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();

View File

@@ -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();
}
};

View File

@@ -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 }>();

View File

@@ -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();

View File

@@ -239,9 +239,7 @@ export default function Referral() {
text: shareText,
url: referralLink,
})
.catch(() => {
// ignore cancellation errors
});
.catch(() => {});
return;
}

View File

@@ -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 {