mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
refactor: full codebase cleanup, dependency updates, and lint fixes
Phase 0: Remove ~920 lines of dead code (ThemeBentoPicker, PromoDiscountBadge, AdminLayout, SettingsSidebar, MovingGradient, EmptyState, miniapp API, unused types/functions/transitions/skeleton helpers). Fix API barrel file (add 20 missing exports). Phase 1: Add ErrorBoundary (app/page/widget levels), centralize constants, add axios timeout, fix staleTime:0 in React Query. Phase 2: Consolidate hexToHsl, extract email validation, fix duplicate code. Phase 3: Fix auth race condition (await checkAdminStatus), memoize useCurrency, add WebSocket message validation. Phase 4: Extract useBranding, useFeatureFlags, useScrollRestoration from AppShell. Phase 5: Remove all eslint-disable react-hooks/exhaustive-deps (14 total), simplify logger, remove deprecated hooks (useBackButton, useTelegramDnd, useTelegramWebApp). Dependencies: React 19, react-router 7, zustand 5, i18next 25, react-i18next 16, eslint-plugin-react-refresh 0.5. Remove unused @lottiefiles/dotlottie-react. Convert vite manualChunks to function-based approach for react-router v7 compat. Lint: Fix logger.ts no-unused-expressions, fix react-refresh/only-export-components in 6 Radix primitive files (const re-exports → direct re-exports), fix WebSocketProvider exhaustive-deps. Result: 0 errors, 0 warnings. Add CLAUDE.md to .gitignore.
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminAppsApi } from '../api/adminApps';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
export default function AdminApps() {
|
||||
@@ -11,8 +10,6 @@ export default function AdminApps() {
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
// RemnaWave status
|
||||
const { data: status } = useQuery({
|
||||
queryKey: ['remnawave-status'],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
banSystemApi,
|
||||
@@ -206,6 +206,8 @@ export default function AdminBanSystem() {
|
||||
const [report, setReport] = useState<BanReportResponse | null>(null);
|
||||
const [health, setHealth] = useState<BanHealthResponse | null>(null);
|
||||
const [reportHours, setReportHours] = useState(24);
|
||||
const reportHoursRef = useRef(reportHours);
|
||||
reportHoursRef.current = reportHours;
|
||||
const [settingLoading, setSettingLoading] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -240,19 +242,7 @@ export default function AdminBanSystem() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadStatus();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (status?.enabled && status?.configured) {
|
||||
loadTabData(activeTab);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeTab, status]);
|
||||
|
||||
const loadStatus = async () => {
|
||||
const loadStatus = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await banSystemApi.getStatus();
|
||||
@@ -265,71 +255,84 @@ export default function AdminBanSystem() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
const loadTabData = async (tab: TabType) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const loadTabData = useCallback(
|
||||
async (tab: TabType) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
switch (tab) {
|
||||
case 'dashboard': {
|
||||
const statsData = await banSystemApi.getStats();
|
||||
setStats(statsData);
|
||||
break;
|
||||
}
|
||||
case 'users': {
|
||||
const usersData = await banSystemApi.getUsers({ limit: 50 });
|
||||
setUsers(usersData);
|
||||
break;
|
||||
}
|
||||
case 'punishments': {
|
||||
const punishmentsData = await banSystemApi.getPunishments();
|
||||
setPunishments(punishmentsData);
|
||||
break;
|
||||
}
|
||||
case 'nodes': {
|
||||
const nodesData = await banSystemApi.getNodes();
|
||||
setNodes(nodesData);
|
||||
break;
|
||||
}
|
||||
case 'agents': {
|
||||
const agentsData = await banSystemApi.getAgents();
|
||||
setAgents(agentsData);
|
||||
break;
|
||||
}
|
||||
case 'violations': {
|
||||
const violationsData = await banSystemApi.getTrafficViolations();
|
||||
setViolations(violationsData);
|
||||
break;
|
||||
}
|
||||
case 'settings': {
|
||||
const settingsData = await banSystemApi.getSettings();
|
||||
setSettings(settingsData);
|
||||
break;
|
||||
}
|
||||
case 'traffic': {
|
||||
const trafficData = await banSystemApi.getTraffic();
|
||||
setTraffic(trafficData);
|
||||
break;
|
||||
}
|
||||
case 'reports': {
|
||||
const reportData = await banSystemApi.getReport(reportHours);
|
||||
setReport(reportData);
|
||||
break;
|
||||
}
|
||||
case 'health': {
|
||||
const healthData = await banSystemApi.getHealth();
|
||||
setHealth(healthData);
|
||||
break;
|
||||
switch (tab) {
|
||||
case 'dashboard': {
|
||||
const statsData = await banSystemApi.getStats();
|
||||
setStats(statsData);
|
||||
break;
|
||||
}
|
||||
case 'users': {
|
||||
const usersData = await banSystemApi.getUsers({ limit: 50 });
|
||||
setUsers(usersData);
|
||||
break;
|
||||
}
|
||||
case 'punishments': {
|
||||
const punishmentsData = await banSystemApi.getPunishments();
|
||||
setPunishments(punishmentsData);
|
||||
break;
|
||||
}
|
||||
case 'nodes': {
|
||||
const nodesData = await banSystemApi.getNodes();
|
||||
setNodes(nodesData);
|
||||
break;
|
||||
}
|
||||
case 'agents': {
|
||||
const agentsData = await banSystemApi.getAgents();
|
||||
setAgents(agentsData);
|
||||
break;
|
||||
}
|
||||
case 'violations': {
|
||||
const violationsData = await banSystemApi.getTrafficViolations();
|
||||
setViolations(violationsData);
|
||||
break;
|
||||
}
|
||||
case 'settings': {
|
||||
const settingsData = await banSystemApi.getSettings();
|
||||
setSettings(settingsData);
|
||||
break;
|
||||
}
|
||||
case 'traffic': {
|
||||
const trafficData = await banSystemApi.getTraffic();
|
||||
setTraffic(trafficData);
|
||||
break;
|
||||
}
|
||||
case 'reports': {
|
||||
const reportData = await banSystemApi.getReport(reportHoursRef.current);
|
||||
setReport(reportData);
|
||||
break;
|
||||
}
|
||||
case 'health': {
|
||||
const healthData = await banSystemApi.getHealth();
|
||||
setHealth(healthData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
setError(t('banSystem.loadError'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} catch {
|
||||
setError(t('banSystem.loadError'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
},
|
||||
[t],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
loadStatus();
|
||||
}, [loadStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status?.enabled && status?.configured) {
|
||||
loadTabData(activeTab);
|
||||
}
|
||||
};
|
||||
}, [activeTab, status, loadTabData]);
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!searchQuery.trim()) {
|
||||
@@ -403,8 +406,7 @@ export default function AdminBanSystem() {
|
||||
if (activeTab === 'reports' && status?.enabled) {
|
||||
loadTabData('reports');
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [reportHours]);
|
||||
}, [reportHours, activeTab, status, loadTabData]);
|
||||
|
||||
const formatBytes = (bytes: number) => {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useRef, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
CombinedBroadcastCreateRequest,
|
||||
} from '../api/adminBroadcasts';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const BroadcastIcon = () => (
|
||||
@@ -119,8 +118,6 @@ export default function AdminBroadcastCreate() {
|
||||
const queryClient = useQueryClient();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useBackButton(() => navigate('/admin/broadcasts'));
|
||||
|
||||
// Channel selection
|
||||
const [channel, setChannel] = useState<BroadcastChannel>('telegram');
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminBroadcastsApi, type BroadcastChannel } from '../api/adminBroadcasts';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
|
||||
@@ -159,8 +158,6 @@ export default function AdminBroadcastDetail() {
|
||||
|
||||
const broadcastId = id ? parseInt(id, 10) : null;
|
||||
|
||||
useBackButton(() => navigate('/admin/broadcasts'));
|
||||
|
||||
// Fetch broadcast details
|
||||
const {
|
||||
data: broadcast,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminBroadcastsApi } from '../api/adminBroadcasts';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
@@ -131,9 +130,6 @@ export default function AdminBroadcasts() {
|
||||
const navigate = useNavigate();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const limit = 20;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
} from '../api/campaigns';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const CampaignIcon = () => (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
@@ -156,8 +154,6 @@ export default function AdminCampaignCreate() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useBackButton(() => navigate('/admin/campaigns'));
|
||||
|
||||
// Form state
|
||||
const [name, setName] = useState('');
|
||||
const [startParameter, setStartParameter] = useState('');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { CheckIcon, CampaignIcon } from '../components/icons';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Bonus type config
|
||||
const bonusTypeConfig: Record<
|
||||
@@ -133,8 +132,6 @@ export default function AdminCampaignEdit() {
|
||||
|
||||
const campaignId = parseInt(id || '0');
|
||||
|
||||
useBackButton(() => navigate('/admin/campaigns'));
|
||||
|
||||
// Fetch campaign
|
||||
const {
|
||||
data: campaign,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||
import { useParams, useNavigate, Link } from 'react-router';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { campaignsApi, CampaignBonusType } from '../api/campaigns';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const CopyIcon = () => (
|
||||
@@ -98,8 +97,6 @@ export default function AdminCampaignStats() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showUsers, setShowUsers] = useState(false);
|
||||
|
||||
useBackButton(() => navigate('/admin/campaigns'));
|
||||
|
||||
// Fetch stats
|
||||
const {
|
||||
data: stats,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { campaignsApi, CampaignListItem, CampaignBonusType } from '../api/campaigns';
|
||||
import { PlusIcon, EditIcon, TrashIcon, CheckIcon, XIcon, ChartIcon } from '../components/icons';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Bonus type labels and colors
|
||||
@@ -67,9 +66,6 @@ export default function AdminCampaigns() {
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<number | null>(null);
|
||||
|
||||
// Queries
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
statsApi,
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
type RecentPaymentsResponse,
|
||||
} from '../api/admin';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons - styled like main navigation
|
||||
@@ -365,9 +364,6 @@ export default function AdminDashboard() {
|
||||
const { formatAmount, currencySymbol } = useCurrency();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -380,7 +376,7 @@ export default function AdminDashboard() {
|
||||
const [payments, setPayments] = useState<RecentPaymentsResponse | null>(null);
|
||||
const [referrersTab, setReferrersTab] = useState<'earnings' | 'invited'>('earnings');
|
||||
|
||||
const fetchStats = async () => {
|
||||
const fetchStats = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
@@ -392,9 +388,9 @@ export default function AdminDashboard() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
const fetchExtendedStats = async () => {
|
||||
const fetchExtendedStats = useCallback(async () => {
|
||||
try {
|
||||
const [referrersData, campaignsData, paymentsData] = await Promise.all([
|
||||
statsApi.getTopReferrers(10),
|
||||
@@ -407,7 +403,7 @@ export default function AdminDashboard() {
|
||||
} catch (err) {
|
||||
console.error('Failed to load extended stats:', err);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStats();
|
||||
@@ -418,8 +414,7 @@ export default function AdminDashboard() {
|
||||
fetchExtendedStats();
|
||||
}, 30000);
|
||||
return () => clearInterval(interval);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [fetchStats, fetchExtendedStats]);
|
||||
|
||||
const handleRestartNode = async (uuid: string) => {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminEmailTemplatesApi } from '../api/adminEmailTemplates';
|
||||
@@ -23,7 +23,11 @@ export default function AdminEmailTemplatePreview() {
|
||||
const state = location.state as PreviewState | null;
|
||||
|
||||
// Preview mutation
|
||||
const previewMutation = useMutation({
|
||||
const {
|
||||
mutate: loadPreview,
|
||||
isPending: previewPending,
|
||||
isError: previewError,
|
||||
} = useMutation({
|
||||
mutationFn: () => {
|
||||
if (!type || !lang || !state) {
|
||||
throw new Error('Missing required data');
|
||||
@@ -45,9 +49,8 @@ export default function AdminEmailTemplatePreview() {
|
||||
navigate('/admin/email-templates');
|
||||
return;
|
||||
}
|
||||
previewMutation.mutate();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [type, lang]);
|
||||
loadPreview();
|
||||
}, [type, lang, state, navigate, loadPreview]);
|
||||
|
||||
// Write preview HTML into iframe
|
||||
useEffect(() => {
|
||||
@@ -85,11 +88,11 @@ export default function AdminEmailTemplatePreview() {
|
||||
|
||||
{/* Preview content */}
|
||||
<div className="flex-1 overflow-hidden rounded-xl border border-dark-700 bg-white">
|
||||
{previewMutation.isPending ? (
|
||||
{previewPending ? (
|
||||
<div className="flex h-full items-center justify-center bg-dark-800">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-2 border-accent-500 border-t-transparent" />
|
||||
</div>
|
||||
) : previewMutation.isError ? (
|
||||
) : previewError ? (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-4 bg-dark-800">
|
||||
<p className="text-dark-400">{t('common.error')}</p>
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RemnawaveIcon } from '../components/icons';
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminPaymentMethodsApi } from '../api/adminPaymentMethods';
|
||||
import type { PromoGroupSimple } from '../types';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
const BackIcon = () => (
|
||||
@@ -72,9 +71,6 @@ export default function AdminPaymentMethodEdit() {
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin/payment-methods'));
|
||||
|
||||
// Fetch payment methods
|
||||
const { data: methods, isLoading } = useQuery({
|
||||
queryKey: ['admin-payment-methods'],
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
import {
|
||||
DndContext,
|
||||
@@ -11,9 +10,8 @@ import {
|
||||
useSensor,
|
||||
useSensors,
|
||||
type DragEndEvent,
|
||||
type DragStartEvent,
|
||||
} from '@dnd-kit/core';
|
||||
import { useTelegramDnd } from '../hooks/useTelegramDnd';
|
||||
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
@@ -239,9 +237,6 @@ export default function AdminPaymentMethods() {
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [methods, setMethods] = useState<PaymentMethodConfig[]>([]);
|
||||
const [orderChanged, setOrderChanged] = useState(false);
|
||||
const [toastMessage, setToastMessage] = useState<string | null>(null);
|
||||
@@ -278,40 +273,18 @@ export default function AdminPaymentMethods() {
|
||||
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
|
||||
);
|
||||
|
||||
// Telegram swipe behavior for drag-and-drop
|
||||
const {
|
||||
onDragStart: onTelegramDragStart,
|
||||
onDragEnd: onTelegramDragEnd,
|
||||
onDragCancel: onTelegramDragCancel,
|
||||
} = useTelegramDnd();
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(_event: DragStartEvent) => {
|
||||
onTelegramDragStart();
|
||||
},
|
||||
[onTelegramDragStart],
|
||||
);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
onTelegramDragEnd();
|
||||
const { active, over } = event;
|
||||
if (over && active.id !== over.id) {
|
||||
setMethods((prev) => {
|
||||
const oldIndex = prev.findIndex((m) => m.method_id === active.id);
|
||||
const newIndex = prev.findIndex((m) => m.method_id === over.id);
|
||||
if (oldIndex === -1 || newIndex === -1) return prev;
|
||||
return arrayMove(prev, oldIndex, newIndex);
|
||||
});
|
||||
setOrderChanged(true);
|
||||
}
|
||||
},
|
||||
[onTelegramDragEnd],
|
||||
);
|
||||
|
||||
const handleDragCancel = useCallback(() => {
|
||||
onTelegramDragCancel();
|
||||
}, [onTelegramDragCancel]);
|
||||
const handleDragEnd = useCallback((event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (over && active.id !== over.id) {
|
||||
setMethods((prev) => {
|
||||
const oldIndex = prev.findIndex((m) => m.method_id === active.id);
|
||||
const newIndex = prev.findIndex((m) => m.method_id === over.id);
|
||||
if (oldIndex === -1 || newIndex === -1) return prev;
|
||||
return arrayMove(prev, oldIndex, newIndex);
|
||||
});
|
||||
setOrderChanged(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleSaveOrder = () => {
|
||||
saveOrderMutation.mutate(methods.map((m) => m.method_id));
|
||||
@@ -367,12 +340,7 @@ export default function AdminPaymentMethods() {
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-2 border-accent-500 border-t-transparent" />
|
||||
</div>
|
||||
) : methods.length > 0 ? (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragCancel={handleDragCancel}
|
||||
>
|
||||
<DndContext sensors={sensors} onDragEnd={handleDragEnd}>
|
||||
<SortableContext
|
||||
items={methods.map((m) => m.method_id)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminPaymentsApi } from '../api/adminPayments';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import type { PendingPayment, PaginatedResponse } from '../types';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// BackIcon
|
||||
@@ -28,9 +27,6 @@ export default function AdminPayments() {
|
||||
const { formatAmount, currencySymbol } = useCurrency();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [methodFilter, setMethodFilter] = useState<string>('');
|
||||
const [checkingPaymentId, setCheckingPaymentId] = useState<string | null>(null);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
PromoGroupUpdateRequest,
|
||||
} from '../api/promocodes';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const PlusIcon = () => (
|
||||
@@ -56,8 +55,6 @@ export default function AdminPromoGroupCreate() {
|
||||
const queryClient = useQueryClient();
|
||||
const isEdit = !!id;
|
||||
|
||||
useBackButton(() => navigate('/admin/promo-groups'));
|
||||
|
||||
// Form state
|
||||
const [name, setName] = useState('');
|
||||
const [serverDiscount, setServerDiscount] = useState<number | ''>(0);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { promocodesApi, PromoGroup } from '../api/promocodes';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
@@ -61,9 +60,6 @@ export default function AdminPromoGroups() {
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<number | null>(null);
|
||||
|
||||
// Query
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from '../api/promoOffers';
|
||||
import { adminUsersApi, UserListItem } from '../api/adminUsers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const SendIcon = () => (
|
||||
@@ -76,8 +75,6 @@ export default function AdminPromoOfferSend() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useBackButton(() => navigate('/admin/promo-offers'));
|
||||
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState<number | null>(null);
|
||||
const [sendMode, setSendMode] = useState<'segment' | 'user'>('segment');
|
||||
const [selectedTarget, setSelectedTarget] = useState<TargetSegment>('active');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from '../api/promoOffers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
const getOfferTypeIcon = (offerType: string): string => {
|
||||
return OFFER_TYPE_CONFIG[offerType as OfferType]?.icon || '🎁';
|
||||
@@ -22,8 +21,6 @@ export default function AdminPromoOfferTemplateEdit() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useBackButton(() => navigate('/admin/promo-offers'));
|
||||
|
||||
// Form state
|
||||
const [name, setName] = useState('');
|
||||
const [messageText, setMessageText] = useState('');
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import i18n from '../i18n';
|
||||
import { promoOffersApi, PromoOfferLog, OFFER_TYPE_CONFIG, OfferType } from '../api/promoOffers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
@@ -108,9 +107,6 @@ export default function AdminPromoOffers() {
|
||||
const navigate = useNavigate();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'templates' | 'logs'>('templates');
|
||||
|
||||
// Queries
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
PromoCodeUpdateRequest,
|
||||
PromoGroup,
|
||||
} from '../api/promocodes';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
@@ -50,9 +49,6 @@ export default function AdminPromocodeCreate() {
|
||||
const { capabilities } = usePlatform();
|
||||
const isEdit = !!id;
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin/promocodes'));
|
||||
|
||||
// Form state
|
||||
const [code, setCode] = useState('');
|
||||
const [type, setType] = useState<PromoCodeType>('balance');
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { promocodesApi, PromoCodeType } from '../api/promocodes';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const EditIcon = () => (
|
||||
@@ -89,8 +88,6 @@ export default function AdminPromocodeStats() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
useBackButton(() => navigate('/admin/promocodes'));
|
||||
|
||||
const {
|
||||
data: promocode,
|
||||
isLoading,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { promocodesApi, PromoCode, PromoCodeType } from '../api/promocodes';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
@@ -113,9 +112,6 @@ export default function AdminPromocodes() {
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<number | null>(null);
|
||||
const [copiedCode, setCopiedCode] = useState<string | null>(null);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
SystemStatsResponse,
|
||||
AutoSyncStatus,
|
||||
} from '../api/adminRemnawave';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
import {
|
||||
ServerIcon,
|
||||
@@ -878,9 +877,6 @@ export default function AdminRemnawave() {
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
// State
|
||||
const [activeTab, setActiveTab] = useState<TabType>('overview');
|
||||
const [syncResults, setSyncResults] = useState<
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminRemnawaveApi, SquadWithLocalInfo } from '../api/adminRemnawave';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { ServerIcon, UsersIcon, CheckIcon, XIcon } from '../components/icons';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Country flags helper
|
||||
const getCountryFlag = (code: string | null | undefined): string => {
|
||||
if (!code) return '🌍';
|
||||
@@ -55,8 +53,6 @@ export default function AdminRemnawaveSquadDetail() {
|
||||
const { uuid } = useParams<{ uuid: string }>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useBackButton(() => navigate('/admin/remnawave'));
|
||||
|
||||
// Fetch all squads and find the one we need
|
||||
const {
|
||||
data: squadsData,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { serversApi, ServerUpdateRequest } from '../api/servers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { ServerIcon } from '../components/icons';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Country flags (simple emoji mapping)
|
||||
const getCountryFlag = (code: string | null): string => {
|
||||
@@ -49,8 +48,6 @@ export default function AdminServerEdit() {
|
||||
|
||||
const serverId = parseInt(id || '0');
|
||||
|
||||
useBackButton(() => navigate('/admin/servers'));
|
||||
|
||||
const {
|
||||
data: server,
|
||||
isLoading,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { serversApi, ServerListItem } from '../api/servers';
|
||||
import { SyncIcon, EditIcon, CheckIcon, XIcon, UsersIcon, GiftIcon } from '../components/icons';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// BackIcon
|
||||
@@ -58,9 +57,6 @@ export default function AdminServers() {
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
// Queries
|
||||
const { data: serversData, isLoading } = useQuery({
|
||||
queryKey: ['admin-servers'],
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminSettingsApi, SettingDefinition } from '../api/adminSettings';
|
||||
import { themeColorsApi } from '../api/themeColors';
|
||||
import { useFavoriteSettings } from '../hooks/useFavoriteSettings';
|
||||
import { MENU_SECTIONS, MenuItem, formatSettingKey } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
import { AnalyticsTab } from '../components/admin/AnalyticsTab';
|
||||
import { BrandingTab } from '../components/admin/BrandingTab';
|
||||
@@ -50,9 +49,6 @@ export default function AdminSettings() {
|
||||
const navigate = useNavigate();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
// State
|
||||
const [activeSection, setActiveSection] = useState('branding');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from '../api/tariffs';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const PlusIcon = () => (
|
||||
@@ -92,8 +91,6 @@ export default function AdminTariffCreate() {
|
||||
const queryClient = useQueryClient();
|
||||
const isEdit = !!id;
|
||||
|
||||
useBackButton(() => navigate('/admin/tariffs'));
|
||||
|
||||
// Step: null = type selection, 'period' or 'daily' = form
|
||||
const [tariffType, setTariffType] = useState<TariffType>(null);
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { tariffsApi, TariffListItem } from '../api/tariffs';
|
||||
import { useDestructiveConfirm, useNotify } from '@/platform';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
@@ -76,9 +75,6 @@ export default function AdminTariffs() {
|
||||
const notify = useNotify();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
// Queries
|
||||
const { data: tariffsData, isLoading } = useQuery({
|
||||
queryKey: ['admin-tariffs'],
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminApi } from '../api/admin';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
const SettingsIcon = () => (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
@@ -22,8 +21,6 @@ export default function AdminTicketSettings() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useBackButton(() => navigate('/admin/tickets'));
|
||||
|
||||
const {
|
||||
data: settings,
|
||||
isLoading,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminApi, AdminTicket, AdminTicketDetail, AdminTicketMessage } from '../api/admin';
|
||||
import { ticketsApi } from '../api/tickets';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
function AdminMessageMedia({
|
||||
@@ -123,9 +122,6 @@ export default function AdminTickets() {
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [selectedTicketId, setSelectedTicketId] = useState<number | null>(null);
|
||||
const [statusFilter, setStatusFilter] = useState<string>('');
|
||||
const [replyText, setReplyText] = useState('');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
type UpdateSubscriptionRequest,
|
||||
} from '../api/adminUsers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// ============ Icons ============
|
||||
|
||||
@@ -82,8 +81,6 @@ export default function AdminUserDetail() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
useBackButton(() => navigate('/admin/users'));
|
||||
|
||||
const localeMap: Record<string, string> = { ru: 'ru-RU', en: 'en-US', zh: 'zh-CN', fa: 'fa-IR' };
|
||||
const locale = localeMap[i18n.language] || 'ru-RU';
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { useToast } from '../components/Toast';
|
||||
import { adminUsersApi, type UserListItem, type UsersStatsResponse } from '../api/adminUsers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// ============ Icons ============
|
||||
@@ -398,9 +397,6 @@ export default function AdminUsers() {
|
||||
const { showToast } = useToast();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [users, setUsers] = useState<UserListItem[]>([]);
|
||||
const [stats, setStats] = useState<UsersStatsResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
closestCenter,
|
||||
type DragEndEvent,
|
||||
} from '@dnd-kit/core';
|
||||
import { useTelegramDnd } from '../hooks/useTelegramDnd';
|
||||
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
@@ -26,7 +26,6 @@ import { useDestructiveConfirm } from '@/platform';
|
||||
import { useNotify } from '@/platform/hooks/useNotify';
|
||||
import FortuneWheel from '../components/wheel/FortuneWheel';
|
||||
import { ColorPicker } from '@/components/ColorPicker';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
@@ -295,9 +294,6 @@ export default function AdminWheel() {
|
||||
const { capabilities } = usePlatform();
|
||||
const notify = useNotify();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [activeTab, setActiveTab] = useState<Tab>('settings');
|
||||
const [expandedPrizeId, setExpandedPrizeId] = useState<number | null>(null);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
@@ -433,22 +429,13 @@ export default function AdminWheel() {
|
||||
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
|
||||
);
|
||||
|
||||
// Telegram DnD support
|
||||
const {
|
||||
onDragStart: onTelegramDragStart,
|
||||
onDragEnd: onTelegramDragEnd,
|
||||
onDragCancel: onTelegramDragCancel,
|
||||
} = useTelegramDnd();
|
||||
|
||||
const handleDragStart = useCallback(() => {
|
||||
onTelegramDragStart();
|
||||
// Collapse expanded card to avoid collision detection issues with varying heights
|
||||
setExpandedPrizeId(null);
|
||||
}, [onTelegramDragStart]);
|
||||
}, []);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
onTelegramDragEnd();
|
||||
const { active, over } = event;
|
||||
if (over && active.id !== over.id && config) {
|
||||
// Use current displayed order (either local or from config)
|
||||
@@ -468,7 +455,7 @@ export default function AdminWheel() {
|
||||
}
|
||||
}
|
||||
},
|
||||
[config, localPrizeOrder, onTelegramDragEnd],
|
||||
[config, localPrizeOrder],
|
||||
);
|
||||
|
||||
// Save prize order
|
||||
@@ -894,7 +881,6 @@ export default function AdminWheel() {
|
||||
collisionDetection={closestCenter}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragCancel={onTelegramDragCancel}
|
||||
>
|
||||
<SortableContext
|
||||
items={displayedPrizes.map((p) => p.id)}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams, useNavigate } from 'react-router-dom';
|
||||
import { useSearchParams, useNavigate } from 'react-router';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
import { useAuthStore } from '../store/auth';
|
||||
import { balanceApi } from '../api/balance';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { API } from '../config/constants';
|
||||
import { useToast } from '../components/Toast';
|
||||
import type { PaginatedResponse, Transaction } from '../types';
|
||||
|
||||
@@ -47,19 +48,18 @@ export default function Balance() {
|
||||
const { showToast } = useToast();
|
||||
const paymentHandledRef = useRef(false);
|
||||
|
||||
// Fetch balance directly from API with no caching
|
||||
// Fetch balance from API
|
||||
const { data: balanceData, refetch: refetchBalance } = useQuery({
|
||||
queryKey: ['balance'],
|
||||
queryFn: balanceApi.getBalance,
|
||||
staleTime: 0,
|
||||
staleTime: API.BALANCE_STALE_TIME_MS,
|
||||
refetchOnMount: 'always',
|
||||
});
|
||||
|
||||
// Refresh user data on mount to sync balance in store
|
||||
useEffect(() => {
|
||||
refreshUser();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [refreshUser]);
|
||||
|
||||
// Handle payment return from payment gateway
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useEffect, useCallback, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { openLink as sdkOpenLink } from '@telegram-apps/sdk-react';
|
||||
import { subscriptionApi } from '../api/subscription';
|
||||
import { useTelegramWebApp } from '../hooks/useTelegramWebApp';
|
||||
import { useBackButton, useHaptic } from '@/platform';
|
||||
import { useTelegramSDK } from '../hooks/useTelegramSDK';
|
||||
import { useHaptic } from '@/platform';
|
||||
import { resolveTemplate, hasTemplates } from '../utils/templateEngine';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
import type { AppConfig, LocalizedText } from '../types';
|
||||
@@ -15,7 +15,7 @@ export default function Connection() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuthStore();
|
||||
const { isTelegramWebApp } = useTelegramWebApp();
|
||||
const { isTelegramWebApp } = useTelegramSDK();
|
||||
const { impact: hapticImpact } = useHaptic();
|
||||
|
||||
const hapticRef = useRef(hapticImpact);
|
||||
@@ -45,13 +45,6 @@ export default function Connection() {
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleGoBack]);
|
||||
|
||||
const handleBackButton = useCallback(() => {
|
||||
hapticRef.current('light');
|
||||
handleGoBack();
|
||||
}, [handleGoBack]);
|
||||
|
||||
useBackButton(handleBackButton);
|
||||
|
||||
const resolveUrl = useCallback(
|
||||
(url: string): string => {
|
||||
if (!hasTemplates(url) || !appConfig?.subscriptionUrl) return url;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
import { subscriptionApi } from '../api/subscription';
|
||||
@@ -10,6 +10,7 @@ import { wheelApi } from '../api/wheel';
|
||||
import Onboarding, { useOnboarding } from '../components/Onboarding';
|
||||
import PromoOffersSection from '../components/PromoOffersSection';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { API } from '../config/constants';
|
||||
|
||||
// Icons
|
||||
const ArrowRightIcon = () => (
|
||||
@@ -57,14 +58,13 @@ export default function Dashboard() {
|
||||
// Refresh user data on mount
|
||||
useEffect(() => {
|
||||
refreshUser();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [refreshUser]);
|
||||
|
||||
// Fetch balance from API with no caching
|
||||
// Fetch balance from API
|
||||
const { data: balanceData } = useQuery({
|
||||
queryKey: ['balance'],
|
||||
queryFn: balanceApi.getBalance,
|
||||
staleTime: 0,
|
||||
staleTime: API.BALANCE_STALE_TIME_MS,
|
||||
refetchOnMount: 'always',
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function Dashboard() {
|
||||
queryKey: ['subscription'],
|
||||
queryFn: subscriptionApi.getSubscription,
|
||||
retry: false,
|
||||
staleTime: 0,
|
||||
staleTime: API.BALANCE_STALE_TIME_MS,
|
||||
refetchOnMount: 'always',
|
||||
});
|
||||
|
||||
@@ -167,7 +167,7 @@ export default function Dashboard() {
|
||||
|
||||
const lastRefresh = localStorage.getItem('traffic_refresh_ts');
|
||||
const now = Date.now();
|
||||
const cacheMs = 30 * 1000;
|
||||
const cacheMs = API.TRAFFIC_CACHE_MS;
|
||||
|
||||
if (lastRefresh && now - parseInt(lastRefresh, 10) < cacheMs) {
|
||||
const elapsed = now - parseInt(lastRefresh, 10);
|
||||
@@ -179,8 +179,7 @@ export default function Dashboard() {
|
||||
}
|
||||
|
||||
refreshTrafficMutation.mutate();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [subscription]);
|
||||
}, [subscription, refreshTrafficMutation]);
|
||||
|
||||
// User has no subscription if API returns has_subscription: false
|
||||
const hasNoSubscription = subscriptionResponse?.has_subscription === false && !subLoading;
|
||||
@@ -243,8 +242,8 @@ export default function Dashboard() {
|
||||
|
||||
// Calculate traffic percentage color
|
||||
const getTrafficColor = (percent: number) => {
|
||||
if (percent > 90) return 'bg-error-500';
|
||||
if (percent > 70) return 'bg-warning-500';
|
||||
if (percent > API.TRAFFIC_CRITICAL_PERCENT) return 'bg-error-500';
|
||||
if (percent > API.TRAFFIC_WARN_PERCENT) return 'bg-warning-500';
|
||||
return 'bg-success-500';
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import { useSearchParams, useNavigate } from 'react-router-dom';
|
||||
import { useSearchParams, useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { brandingApi } from '../api/branding';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useLocation, useSearchParams } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
import { authApi } from '../api/auth';
|
||||
import { isValidEmail } from '../utils/validation';
|
||||
import {
|
||||
brandingApi,
|
||||
getCachedBranding,
|
||||
@@ -139,8 +140,7 @@ export default function Login() {
|
||||
setError('');
|
||||
|
||||
// Валидация email
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!email.trim() || !emailRegex.test(email.trim())) {
|
||||
if (!email.trim() || !isValidEmail(email.trim())) {
|
||||
setError(t('auth.invalidEmail', 'Please enter a valid email address'));
|
||||
return;
|
||||
}
|
||||
@@ -200,8 +200,7 @@ export default function Login() {
|
||||
e.preventDefault();
|
||||
setForgotPasswordError('');
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!forgotPasswordEmail.trim() || !emailRegex.test(forgotPasswordEmail.trim())) {
|
||||
if (!forgotPasswordEmail.trim() || !isValidEmail(forgotPasswordEmail.trim())) {
|
||||
setForgotPasswordError(t('auth.invalidEmail', 'Please enter a valid email address'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
import { authApi } from '../api/auth';
|
||||
import { isValidEmail } from '../utils/validation';
|
||||
import {
|
||||
notificationsApi,
|
||||
NotificationSettings,
|
||||
@@ -196,8 +197,7 @@ export default function Profile() {
|
||||
setError(null);
|
||||
setSuccess(null);
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!email.trim() || !emailRegex.test(email.trim())) {
|
||||
if (!email.trim() || !isValidEmail(email.trim())) {
|
||||
setError(t('profile.invalidEmail', 'Please enter a valid email address'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useSearchParams, Link, useNavigate } from 'react-router-dom';
|
||||
import { useSearchParams, Link, useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { authApi } from '../api/auth';
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router';
|
||||
import { AxiosError } from 'axios';
|
||||
import { subscriptionApi } from '../api/subscription';
|
||||
import { promoApi } from '../api/promo';
|
||||
@@ -537,8 +537,7 @@ export default function Subscription() {
|
||||
}
|
||||
|
||||
refreshTrafficMutation.mutate();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [subscription]);
|
||||
}, [subscription, refreshTrafficMutation]);
|
||||
|
||||
// Auto-scroll to switch tariff modal when it appears
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { balanceApi } from '../api/balance';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { checkRateLimit, getRateLimitResetTime, RATE_LIMIT_KEYS } from '../utils/rateLimit';
|
||||
import { useCloseOnSuccessNotification } from '../store/successNotification';
|
||||
import { useBackButton, useHaptic, usePlatform } from '@/platform';
|
||||
import { useHaptic, usePlatform } from '@/platform';
|
||||
import { staggerContainer, staggerItem } from '@/components/motion/transitions';
|
||||
import type { PaymentMethod } from '../types';
|
||||
import BentoCard from '../components/ui/BentoCard';
|
||||
@@ -108,9 +108,6 @@ export default function TopUpAmount() {
|
||||
navigate(returnTo || '/balance', { replace: true });
|
||||
}, [navigate, returnTo]);
|
||||
|
||||
// Telegram back button
|
||||
useBackButton(handleNavigateBack);
|
||||
|
||||
// Keyboard: Escape to go back
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { balanceApi } from '../api/balance';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { useBackButton } from '@/platform';
|
||||
import { Card } from '@/components/data-display/Card';
|
||||
import { staggerContainer, staggerItem } from '@/components/motion/transitions';
|
||||
|
||||
@@ -15,8 +14,6 @@ export default function TopUpMethodSelect() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const { formatAmount, currencySymbol } = useCurrency();
|
||||
|
||||
useBackButton(() => navigate('/balance'));
|
||||
|
||||
const { data: paymentMethods, isLoading } = useQuery({
|
||||
queryKey: ['payment-methods'],
|
||||
queryFn: balanceApi.getPaymentMethods,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams, Link, useNavigate } from 'react-router-dom';
|
||||
import { useSearchParams, Link, useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { authApi } from '../api/auth';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
|
||||
Reference in New Issue
Block a user