diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 51256ca..0000000
--- a/.eslintignore
+++ /dev/null
@@ -1,6 +0,0 @@
-dist
-node_modules
-*.config.js
-*.config.ts
-.github
-public
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
deleted file mode 100644
index 5df859f..0000000
--- a/.eslintrc.cjs
+++ /dev/null
@@ -1,22 +0,0 @@
-module.exports = {
- root: true,
- env: { browser: true, es2020: true },
- extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:react-hooks/recommended',
- ],
- ignorePatterns: ['dist', '.eslintrc.cjs'],
- parser: '@typescript-eslint/parser',
- plugins: ['react-refresh'],
- rules: {
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
- '@typescript-eslint/no-explicit-any': 'warn',
- '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
- 'react-hooks/exhaustive-deps': 'warn',
- 'no-empty': 'warn',
- },
-}
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..53a044f
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+dist
+node_modules
+public
+*.min.js
+*.min.css
+package-lock.json
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..ca25bd0
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,13 @@
+{
+ "semi": true,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "useTabs": false,
+ "trailingComma": "all",
+ "printWidth": 100,
+ "bracketSpacing": true,
+ "arrowParens": "always",
+ "endOfLine": "auto",
+ "jsxSingleQuote": false,
+ "plugins": ["prettier-plugin-tailwindcss"]
+}
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..f33a89d
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,48 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import eslintConfigPrettier from 'eslint-config-prettier'
+
+export default tseslint.config(
+ {
+ ignores: ['dist', 'node_modules', 'public'],
+ },
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ ...tseslint.configs.recommended,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-hooks/set-state-in-effect': 'off',
+ 'react-hooks/static-components': 'off',
+ 'react-hooks/immutability': 'off',
+ 'react-hooks/refs': 'off',
+ 'react-hooks/purity': 'off',
+ 'react-hooks/variables': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ '@typescript-eslint/no-explicit-any': 'warn',
+ '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
+ 'no-empty': 'warn',
+ 'no-eval': 'error',
+ 'no-implied-eval': 'error',
+ 'no-new-func': 'error',
+ 'no-script-url': 'error',
+ },
+ },
+ eslintConfigPrettier,
+)
diff --git a/src/App.tsx b/src/App.tsx
index be685c4..23cc45b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,364 +1,416 @@
-import { lazy, Suspense } from 'react'
-import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
-import { useAuthStore } from './store/auth'
-import { useBlockingStore } from './store/blocking'
-import Layout from './components/layout/Layout'
-import PageLoader from './components/common/PageLoader'
-import { MaintenanceScreen, ChannelSubscriptionScreen } from './components/blocking'
-import { saveReturnUrl } from './utils/token'
-import { useAnalyticsCounters } from './hooks/useAnalyticsCounters'
+import { lazy, Suspense } from 'react';
+import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
+import { useAuthStore } from './store/auth';
+import { useBlockingStore } from './store/blocking';
+import Layout from './components/layout/Layout';
+import PageLoader from './components/common/PageLoader';
+import { MaintenanceScreen, ChannelSubscriptionScreen } from './components/blocking';
+import { saveReturnUrl } from './utils/token';
+import { useAnalyticsCounters } from './hooks/useAnalyticsCounters';
// Auth pages - load immediately (small)
-import Login from './pages/Login'
-import TelegramCallback from './pages/TelegramCallback'
-import TelegramRedirect from './pages/TelegramRedirect'
-import DeepLinkRedirect from './pages/DeepLinkRedirect'
-import VerifyEmail from './pages/VerifyEmail'
-import ResetPassword from './pages/ResetPassword'
+import Login from './pages/Login';
+import TelegramCallback from './pages/TelegramCallback';
+import TelegramRedirect from './pages/TelegramRedirect';
+import DeepLinkRedirect from './pages/DeepLinkRedirect';
+import VerifyEmail from './pages/VerifyEmail';
+import ResetPassword from './pages/ResetPassword';
// User pages - lazy load
-const Dashboard = lazy(() => import('./pages/Dashboard'))
-const Subscription = lazy(() => import('./pages/Subscription'))
-const Balance = lazy(() => import('./pages/Balance'))
-const Referral = lazy(() => import('./pages/Referral'))
-const Support = lazy(() => import('./pages/Support'))
-const Profile = lazy(() => import('./pages/Profile'))
-const Contests = lazy(() => import('./pages/Contests'))
-const Polls = lazy(() => import('./pages/Polls'))
-const Info = lazy(() => import('./pages/Info'))
-const Wheel = lazy(() => import('./pages/Wheel'))
+const Dashboard = lazy(() => import('./pages/Dashboard'));
+const Subscription = lazy(() => import('./pages/Subscription'));
+const Balance = lazy(() => import('./pages/Balance'));
+const Referral = lazy(() => import('./pages/Referral'));
+const Support = lazy(() => import('./pages/Support'));
+const Profile = lazy(() => import('./pages/Profile'));
+const Contests = lazy(() => import('./pages/Contests'));
+const Polls = lazy(() => import('./pages/Polls'));
+const Info = lazy(() => import('./pages/Info'));
+const Wheel = lazy(() => import('./pages/Wheel'));
// Admin pages - lazy load (only for admins)
-const AdminPanel = lazy(() => import('./pages/AdminPanel'))
-const AdminTickets = lazy(() => import('./pages/AdminTickets'))
-const AdminSettings = lazy(() => import('./pages/AdminSettings'))
-const AdminApps = lazy(() => import('./pages/AdminApps'))
-const AdminWheel = lazy(() => import('./pages/AdminWheel'))
-const AdminTariffs = lazy(() => import('./pages/AdminTariffs'))
-const AdminServers = lazy(() => import('./pages/AdminServers'))
-const AdminDashboard = lazy(() => import('./pages/AdminDashboard'))
-const AdminBanSystem = lazy(() => import('./pages/AdminBanSystem'))
-const AdminBroadcasts = lazy(() => import('./pages/AdminBroadcasts'))
-const AdminPromocodes = lazy(() => import('./pages/AdminPromocodes'))
-const AdminCampaigns = lazy(() => import('./pages/AdminCampaigns'))
-const AdminUsers = lazy(() => import('./pages/AdminUsers'))
-const AdminPayments = lazy(() => import('./pages/AdminPayments'))
-const AdminPaymentMethods = lazy(() => import('./pages/AdminPaymentMethods'))
-const AdminPromoOffers = lazy(() => import('./pages/AdminPromoOffers'))
-const AdminRemnawave = lazy(() => import('./pages/AdminRemnawave'))
-const AdminEmailTemplates = lazy(() => import('./pages/AdminEmailTemplates'))
+const AdminPanel = lazy(() => import('./pages/AdminPanel'));
+const AdminTickets = lazy(() => import('./pages/AdminTickets'));
+const AdminSettings = lazy(() => import('./pages/AdminSettings'));
+const AdminApps = lazy(() => import('./pages/AdminApps'));
+const AdminWheel = lazy(() => import('./pages/AdminWheel'));
+const AdminTariffs = lazy(() => import('./pages/AdminTariffs'));
+const AdminServers = lazy(() => import('./pages/AdminServers'));
+const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
+const AdminBanSystem = lazy(() => import('./pages/AdminBanSystem'));
+const AdminBroadcasts = lazy(() => import('./pages/AdminBroadcasts'));
+const AdminPromocodes = lazy(() => import('./pages/AdminPromocodes'));
+const AdminCampaigns = lazy(() => import('./pages/AdminCampaigns'));
+const AdminUsers = lazy(() => import('./pages/AdminUsers'));
+const AdminPayments = lazy(() => import('./pages/AdminPayments'));
+const AdminPaymentMethods = lazy(() => import('./pages/AdminPaymentMethods'));
+const AdminPromoOffers = lazy(() => import('./pages/AdminPromoOffers'));
+const AdminRemnawave = lazy(() => import('./pages/AdminRemnawave'));
+const AdminEmailTemplates = lazy(() => import('./pages/AdminEmailTemplates'));
function ProtectedRoute({ children }: { children: React.ReactNode }) {
- const { isAuthenticated, isLoading } = useAuthStore()
- const location = useLocation()
+ const { isAuthenticated, isLoading } = useAuthStore();
+ const location = useLocation();
if (isLoading) {
- return
+ return ;
}
if (!isAuthenticated) {
// Сохраняем текущий URL для возврата после авторизации
- saveReturnUrl()
- return
+ saveReturnUrl();
+ return ;
}
- return {children}
+ return {children};
}
function AdminRoute({ children }: { children: React.ReactNode }) {
- const { isAuthenticated, isLoading, isAdmin } = useAuthStore()
- const location = useLocation()
+ const { isAuthenticated, isLoading, isAdmin } = useAuthStore();
+ const location = useLocation();
if (isLoading) {
- return
+ return ;
}
if (!isAuthenticated) {
// Сохраняем текущий URL для возврата после авторизации
- saveReturnUrl()
- return
+ saveReturnUrl();
+ return ;
}
if (!isAdmin) {
- return
+ return ;
}
- return {children}
+ return {children};
}
// Suspense wrapper for lazy components
function LazyPage({ children }: { children: React.ReactNode }) {
- return (
- }>
- {children}
-
- )
+ return }>{children};
}
function BlockingOverlay() {
- const { blockingType } = useBlockingStore()
+ const { blockingType } = useBlockingStore();
if (blockingType === 'maintenance') {
- return
+ return ;
}
if (blockingType === 'channel_subscription') {
- return
+ return ;
}
- return null
+ return null;
}
function App() {
- useAnalyticsCounters()
+ useAnalyticsCounters();
return (
<>
- {/* Public routes */}
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ {/* Public routes */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
- {/* Protected routes */}
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
+ {/* Protected routes */}
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
- {/* Admin routes */}
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
+ {/* Admin routes */}
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
- {/* Catch all */}
- } />
-
+ {/* Catch all */}
+ } />
+
>
- )
+ );
}
-export default App
+export default App;
diff --git a/src/api/admin.ts b/src/api/admin.ts
index 1bd79fe..1d5faa4 100644
--- a/src/api/admin.ts
+++ b/src/api/admin.ts
@@ -1,306 +1,310 @@
-import apiClient from './client'
+import apiClient from './client';
export interface AdminTicketUser {
- id: number
- telegram_id: number
- username: string | null
- first_name: string | null
- last_name: string | null
+ id: number;
+ telegram_id: number;
+ username: string | null;
+ first_name: string | null;
+ last_name: string | null;
}
export interface AdminTicketMessage {
- id: number
- message_text: string
- is_from_admin: boolean
- has_media: boolean
- media_type: string | null
- media_file_id: string | null
- media_caption: string | null
- created_at: string
+ id: number;
+ message_text: string;
+ is_from_admin: boolean;
+ has_media: boolean;
+ media_type: string | null;
+ media_file_id: string | null;
+ media_caption: string | null;
+ created_at: string;
}
export interface AdminTicket {
- id: number
- title: string
- status: string
- priority: string
- created_at: string
- updated_at: string
- closed_at: string | null
- messages_count: number
- user: AdminTicketUser | null
- last_message: AdminTicketMessage | null
+ id: number;
+ title: string;
+ status: string;
+ priority: string;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ messages_count: number;
+ user: AdminTicketUser | null;
+ last_message: AdminTicketMessage | null;
}
export interface AdminTicketDetail {
- id: number
- title: string
- status: string
- priority: string
- created_at: string
- updated_at: string
- closed_at: string | null
- is_reply_blocked: boolean
- user: AdminTicketUser | null
- messages: AdminTicketMessage[]
+ id: number;
+ title: string;
+ status: string;
+ priority: string;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ is_reply_blocked: boolean;
+ user: AdminTicketUser | null;
+ messages: AdminTicketMessage[];
}
export interface AdminTicketStats {
- total: number
- open: number
- pending: number
- answered: number
- closed: number
+ total: number;
+ open: number;
+ pending: number;
+ answered: number;
+ closed: number;
}
export interface TicketSettings {
- sla_enabled: boolean
- sla_minutes: number
- sla_check_interval_seconds: number
- sla_reminder_cooldown_minutes: number
- support_system_mode: string // tickets, contact, both
- cabinet_user_notifications_enabled: boolean
- cabinet_admin_notifications_enabled: boolean
+ sla_enabled: boolean;
+ sla_minutes: number;
+ sla_check_interval_seconds: number;
+ sla_reminder_cooldown_minutes: number;
+ support_system_mode: string; // tickets, contact, both
+ cabinet_user_notifications_enabled: boolean;
+ cabinet_admin_notifications_enabled: boolean;
}
export interface TicketSettingsUpdate {
- sla_enabled?: boolean
- sla_minutes?: number
- sla_check_interval_seconds?: number
- sla_reminder_cooldown_minutes?: number
- support_system_mode?: string
- cabinet_user_notifications_enabled?: boolean
- cabinet_admin_notifications_enabled?: boolean
+ sla_enabled?: boolean;
+ sla_minutes?: number;
+ sla_check_interval_seconds?: number;
+ sla_reminder_cooldown_minutes?: number;
+ support_system_mode?: string;
+ cabinet_user_notifications_enabled?: boolean;
+ cabinet_admin_notifications_enabled?: boolean;
}
export interface AdminTicketListResponse {
- items: AdminTicket[]
- total: number
- page: number
- per_page: number
- pages: number
+ items: AdminTicket[];
+ total: number;
+ page: number;
+ per_page: number;
+ pages: number;
}
export const adminApi = {
// Check if current user is admin
checkIsAdmin: async (): Promise<{ is_admin: boolean }> => {
- const response = await apiClient.get('/cabinet/auth/me/is-admin')
- return response.data
+ const response = await apiClient.get('/cabinet/auth/me/is-admin');
+ return response.data;
},
// Get ticket statistics
getTicketStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/tickets/stats')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/tickets/stats');
+ return response.data;
},
// Get all tickets
- getTickets: async (params: {
- page?: number
- per_page?: number
- status?: string
- priority?: string
- } = {}): Promise => {
- const response = await apiClient.get('/cabinet/admin/tickets', { params })
- return response.data
+ getTickets: async (
+ params: {
+ page?: number;
+ per_page?: number;
+ status?: string;
+ priority?: string;
+ } = {},
+ ): Promise => {
+ const response = await apiClient.get('/cabinet/admin/tickets', { params });
+ return response.data;
},
// Get single ticket with messages
getTicket: async (ticketId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/tickets/${ticketId}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/tickets/${ticketId}`);
+ return response.data;
},
// Reply to ticket
replyToTicket: async (ticketId: number, message: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/reply`, { message })
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/reply`, { message });
+ return response.data;
},
// Update ticket status
updateTicketStatus: async (ticketId: number, status: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/status`, { status })
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/status`, { status });
+ return response.data;
},
// Update ticket priority
updateTicketPriority: async (ticketId: number, priority: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/priority`, { priority })
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/priority`, {
+ priority,
+ });
+ return response.data;
},
// Get ticket settings
getTicketSettings: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/tickets/settings')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/tickets/settings');
+ return response.data;
},
// Update ticket settings
updateTicketSettings: async (settings: TicketSettingsUpdate): Promise => {
- const response = await apiClient.patch('/cabinet/admin/tickets/settings', settings)
- return response.data
+ const response = await apiClient.patch('/cabinet/admin/tickets/settings', settings);
+ return response.data;
},
-}
+};
// ============ Dashboard Stats Types ============
export interface NodeStatus {
- uuid: string
- name: string
- address: string
- is_connected: boolean
- is_disabled: boolean
- users_online: number
- traffic_used_bytes?: number
- uptime?: string
- xray_version?: string
- node_version?: string
- last_status_message?: string
- xray_uptime?: string
- is_xray_running?: boolean
- cpu_count?: number
- cpu_model?: string
- total_ram?: string
- country_code?: string
+ uuid: string;
+ name: string;
+ address: string;
+ is_connected: boolean;
+ is_disabled: boolean;
+ users_online: number;
+ traffic_used_bytes?: number;
+ uptime?: string;
+ xray_version?: string;
+ node_version?: string;
+ last_status_message?: string;
+ xray_uptime?: string;
+ is_xray_running?: boolean;
+ cpu_count?: number;
+ cpu_model?: string;
+ total_ram?: string;
+ country_code?: string;
}
export interface NodesOverview {
- total: number
- online: number
- offline: number
- disabled: number
- total_users_online: number
- nodes: NodeStatus[]
+ total: number;
+ online: number;
+ offline: number;
+ disabled: number;
+ total_users_online: number;
+ nodes: NodeStatus[];
}
export interface RevenueData {
- date: string
- amount_kopeks: number
- amount_rubles: number
+ date: string;
+ amount_kopeks: number;
+ amount_rubles: number;
}
export interface SubscriptionStats {
- total: number
- active: number
- trial: number
- paid: number
- expired: number
- purchased_today: number
- purchased_week: number
- purchased_month: number
- trial_to_paid_conversion: number
+ total: number;
+ active: number;
+ trial: number;
+ paid: number;
+ expired: number;
+ purchased_today: number;
+ purchased_week: number;
+ purchased_month: number;
+ trial_to_paid_conversion: number;
}
export interface FinancialStats {
- income_today_kopeks: number
- income_today_rubles: number
- income_month_kopeks: number
- income_month_rubles: number
- income_total_kopeks: number
- income_total_rubles: number
- subscription_income_kopeks: number
- subscription_income_rubles: number
+ income_today_kopeks: number;
+ income_today_rubles: number;
+ income_month_kopeks: number;
+ income_month_rubles: number;
+ income_total_kopeks: number;
+ income_total_rubles: number;
+ subscription_income_kopeks: number;
+ subscription_income_rubles: number;
}
export interface ServerStats {
- total_servers: number
- available_servers: number
- servers_with_connections: number
- total_revenue_kopeks: number
- total_revenue_rubles: number
+ total_servers: number;
+ available_servers: number;
+ servers_with_connections: number;
+ total_revenue_kopeks: number;
+ total_revenue_rubles: number;
}
export interface TariffStatItem {
- tariff_id: number
- tariff_name: string
- active_subscriptions: number
- trial_subscriptions: number
- purchased_today: number
- purchased_week: number
- purchased_month: number
+ tariff_id: number;
+ tariff_name: string;
+ active_subscriptions: number;
+ trial_subscriptions: number;
+ purchased_today: number;
+ purchased_week: number;
+ purchased_month: number;
}
export interface TariffStats {
- tariffs: TariffStatItem[]
- total_tariff_subscriptions: number
+ tariffs: TariffStatItem[];
+ total_tariff_subscriptions: number;
}
export interface DashboardStats {
- nodes: NodesOverview
- subscriptions: SubscriptionStats
- financial: FinancialStats
- servers: ServerStats
- revenue_chart: RevenueData[]
- tariff_stats?: TariffStats
+ nodes: NodesOverview;
+ subscriptions: SubscriptionStats;
+ financial: FinancialStats;
+ servers: ServerStats;
+ revenue_chart: RevenueData[];
+ tariff_stats?: TariffStats;
}
// ============ Extended Stats Types ============
export interface TopReferrerItem {
- user_id: number
- telegram_id: number
- username?: string
- display_name: string
- invited_count: number
- invited_today: number
- invited_week: number
- invited_month: number
- earnings_today_kopeks: number
- earnings_week_kopeks: number
- earnings_month_kopeks: number
- earnings_total_kopeks: number
+ user_id: number;
+ telegram_id: number;
+ username?: string;
+ display_name: string;
+ invited_count: number;
+ invited_today: number;
+ invited_week: number;
+ invited_month: number;
+ earnings_today_kopeks: number;
+ earnings_week_kopeks: number;
+ earnings_month_kopeks: number;
+ earnings_total_kopeks: number;
}
export interface TopReferrersResponse {
- by_earnings: TopReferrerItem[]
- by_invited: TopReferrerItem[]
- total_referrers: number
- total_referrals: number
- total_earnings_kopeks: number
+ by_earnings: TopReferrerItem[];
+ by_invited: TopReferrerItem[];
+ total_referrers: number;
+ total_referrals: number;
+ total_earnings_kopeks: number;
}
export interface TopCampaignItem {
- id: number
- name: string
- start_parameter: string
- bonus_type: string
- is_active: boolean
- registrations: number
- conversions: number
- conversion_rate: number
- total_revenue_kopeks: number
- avg_revenue_per_user_kopeks: number
- created_at?: string
+ id: number;
+ name: string;
+ start_parameter: string;
+ bonus_type: string;
+ is_active: boolean;
+ registrations: number;
+ conversions: number;
+ conversion_rate: number;
+ total_revenue_kopeks: number;
+ avg_revenue_per_user_kopeks: number;
+ created_at?: string;
}
export interface TopCampaignsResponse {
- campaigns: TopCampaignItem[]
- total_campaigns: number
- total_registrations: number
- total_revenue_kopeks: number
+ campaigns: TopCampaignItem[];
+ total_campaigns: number;
+ total_registrations: number;
+ total_revenue_kopeks: number;
}
export interface RecentPaymentItem {
- id: number
- user_id: number
- telegram_id: number
- username?: string
- display_name: string
- amount_kopeks: number
- amount_rubles: number
- type: string
- type_display: string
- payment_method?: string
- description?: string
- created_at: string
- is_completed: boolean
+ id: number;
+ user_id: number;
+ telegram_id: number;
+ username?: string;
+ display_name: string;
+ amount_kopeks: number;
+ amount_rubles: number;
+ type: string;
+ type_display: string;
+ payment_method?: string;
+ description?: string;
+ created_at: string;
+ is_completed: boolean;
}
export interface RecentPaymentsResponse {
- payments: RecentPaymentItem[]
- total_count: number
- total_today_kopeks: number
- total_week_kopeks: number
+ payments: RecentPaymentItem[];
+ total_count: number;
+ total_today_kopeks: number;
+ total_week_kopeks: number;
}
// ============ Dashboard Stats API ============
@@ -308,43 +312,51 @@ export interface RecentPaymentsResponse {
export const statsApi = {
// Get complete dashboard stats
getDashboardStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/dashboard')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/dashboard');
+ return response.data;
},
// Get nodes status
getNodesStatus: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/nodes')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/nodes');
+ return response.data;
},
// Restart a node
restartNode: async (nodeUuid: string): Promise<{ success: boolean; message: string }> => {
- const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/restart`)
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/restart`);
+ return response.data;
},
// Toggle node (enable/disable)
- toggleNode: async (nodeUuid: string): Promise<{ success: boolean; message: string; is_disabled: boolean }> => {
- const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/toggle`)
- return response.data
+ toggleNode: async (
+ nodeUuid: string,
+ ): Promise<{ success: boolean; message: string; is_disabled: boolean }> => {
+ const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/toggle`);
+ return response.data;
},
// Get top referrers
getTopReferrers: async (limit: number = 20): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/referrals/top', { params: { limit } })
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/referrals/top', {
+ params: { limit },
+ });
+ return response.data;
},
// Get top campaigns
getTopCampaigns: async (limit: number = 20): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/campaigns/top', { params: { limit } })
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/campaigns/top', {
+ params: { limit },
+ });
+ return response.data;
},
// Get recent payments
getRecentPayments: async (limit: number = 50): Promise => {
- const response = await apiClient.get('/cabinet/admin/stats/payments/recent', { params: { limit } })
- return response.data
+ const response = await apiClient.get('/cabinet/admin/stats/payments/recent', {
+ params: { limit },
+ });
+ return response.data;
},
-}
+};
diff --git a/src/api/adminApps.ts b/src/api/adminApps.ts
index b57a78c..414f7c4 100644
--- a/src/api/adminApps.ts
+++ b/src/api/adminApps.ts
@@ -1,220 +1,240 @@
-import apiClient from './client'
+import apiClient from './client';
export interface LocalizedText {
- en: string
- ru: string
- zh?: string
- fa?: string
- fr?: string
+ en: string;
+ ru: string;
+ zh?: string;
+ fa?: string;
+ fr?: string;
}
export interface AppButton {
- id?: string // Unique identifier for React key (client-side only)
- buttonLink: string
- buttonText: LocalizedText
+ id?: string; // Unique identifier for React key (client-side only)
+ buttonLink: string;
+ buttonText: LocalizedText;
}
export interface AppStep {
- description: LocalizedText
- buttons?: AppButton[]
- title?: LocalizedText
+ description: LocalizedText;
+ buttons?: AppButton[];
+ title?: LocalizedText;
}
export interface AppDefinition {
- id: string
- name: string
- isFeatured: boolean
- urlScheme: string
- isNeedBase64Encoding?: boolean
- installationStep: AppStep
- addSubscriptionStep: AppStep
- connectAndUseStep: AppStep
- additionalBeforeAddSubscriptionStep?: AppStep
- additionalAfterAddSubscriptionStep?: AppStep
+ id: string;
+ name: string;
+ isFeatured: boolean;
+ urlScheme: string;
+ isNeedBase64Encoding?: boolean;
+ installationStep: AppStep;
+ addSubscriptionStep: AppStep;
+ connectAndUseStep: AppStep;
+ additionalBeforeAddSubscriptionStep?: AppStep;
+ additionalAfterAddSubscriptionStep?: AppStep;
}
export interface AppConfigBranding {
- name: string
- logoUrl: string
- supportUrl: string
+ name: string;
+ logoUrl: string;
+ supportUrl: string;
}
export interface AppConfigConfig {
- additionalLocales: string[]
- branding: AppConfigBranding
+ additionalLocales: string[];
+ branding: AppConfigBranding;
}
export interface AppConfigResponse {
- config: AppConfigConfig
- platforms: Record
+ config: AppConfigConfig;
+ platforms: Record;
}
export const adminAppsApi = {
// Get full app config
getConfig: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/apps')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/apps');
+ return response.data;
},
// Get available platforms
getPlatforms: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/apps/platforms')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/apps/platforms');
+ return response.data;
},
// Get apps for a platform
getPlatformApps: async (platform: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/apps/platforms/${platform}`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/apps/platforms/${platform}`,
+ );
+ return response.data;
},
// Create a new app
createApp: async (platform: string, app: AppDefinition): Promise => {
- const response = await apiClient.post(`/cabinet/admin/apps/platforms/${platform}`, {
- platform,
- app,
- })
- return response.data
+ const response = await apiClient.post(
+ `/cabinet/admin/apps/platforms/${platform}`,
+ {
+ platform,
+ app,
+ },
+ );
+ return response.data;
},
// Update an app
- updateApp: async (platform: string, appId: string, app: AppDefinition): Promise => {
- const response = await apiClient.put(`/cabinet/admin/apps/platforms/${platform}/${appId}`, {
- app,
- })
- return response.data
+ updateApp: async (
+ platform: string,
+ appId: string,
+ app: AppDefinition,
+ ): Promise => {
+ const response = await apiClient.put(
+ `/cabinet/admin/apps/platforms/${platform}/${appId}`,
+ {
+ app,
+ },
+ );
+ return response.data;
},
// Delete an app
deleteApp: async (platform: string, appId: string): Promise => {
- await apiClient.delete(`/cabinet/admin/apps/platforms/${platform}/${appId}`)
+ await apiClient.delete(`/cabinet/admin/apps/platforms/${platform}/${appId}`);
},
// Reorder apps
reorderApps: async (platform: string, appIds: string[]): Promise => {
await apiClient.post(`/cabinet/admin/apps/platforms/${platform}/reorder`, {
app_ids: appIds,
- })
+ });
},
// Copy app to another platform
- copyApp: async (platform: string, appId: string, targetPlatform: string): Promise<{ new_id: string }> => {
+ copyApp: async (
+ platform: string,
+ appId: string,
+ targetPlatform: string,
+ ): Promise<{ new_id: string }> => {
const response = await apiClient.post<{ new_id: string; target_platform: string }>(
- `/cabinet/admin/apps/platforms/${platform}/copy/${appId}?target_platform=${targetPlatform}`
- )
- return response.data
+ `/cabinet/admin/apps/platforms/${platform}/copy/${appId}?target_platform=${targetPlatform}`,
+ );
+ return response.data;
},
// Get branding
getBranding: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/apps/branding')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/apps/branding');
+ return response.data;
},
// Update branding
updateBranding: async (branding: AppConfigBranding): Promise => {
const response = await apiClient.put('/cabinet/admin/apps/branding', {
branding,
- })
- return response.data
+ });
+ return response.data;
},
// Get RemnaWave config status
getRemnaWaveStatus: async (): Promise<{ enabled: boolean; config_uuid: string | null }> => {
const response = await apiClient.get<{ enabled: boolean; config_uuid: string | null }>(
- '/cabinet/admin/apps/remnawave/status'
- )
- return response.data
+ '/cabinet/admin/apps/remnawave/status',
+ );
+ return response.data;
},
// Set RemnaWave config UUID
- setRemnaWaveUuid: async (uuid: string | null): Promise<{ enabled: boolean; config_uuid: string | null }> => {
+ setRemnaWaveUuid: async (
+ uuid: string | null,
+ ): Promise<{ enabled: boolean; config_uuid: string | null }> => {
const response = await apiClient.put<{ enabled: boolean; config_uuid: string | null }>(
'/cabinet/admin/apps/remnawave/uuid',
- { uuid }
- )
- return response.data
+ { uuid },
+ );
+ return response.data;
},
// List available RemnaWave configs
- listRemnaWaveConfigs: async (): Promise<{ uuid: string; name: string; view_position: number }[]> => {
+ listRemnaWaveConfigs: async (): Promise<
+ { uuid: string; name: string; view_position: number }[]
+ > => {
const response = await apiClient.get<{ uuid: string; name: string; view_position: number }[]>(
- '/cabinet/admin/apps/remnawave/configs'
- )
- return response.data
+ '/cabinet/admin/apps/remnawave/configs',
+ );
+ return response.data;
},
// Get RemnaWave subscription config
getRemnaWaveConfig: async (): Promise => {
const response = await apiClient.get<{
- uuid: string
- name: string
- view_position: number
- config: RemnawaveConfig
- }>('/cabinet/admin/apps/remnawave/config')
- return response.data.config
+ uuid: string;
+ name: string;
+ view_position: number;
+ config: RemnawaveConfig;
+ }>('/cabinet/admin/apps/remnawave/config');
+ return response.data.config;
},
-}
+};
// ============== RemnaWave Format Types ==============
export interface RemnawaveButton {
- url: string
- text: LocalizedText
+ url: string;
+ text: LocalizedText;
}
export interface RemnawaveBlock {
- title: LocalizedText
- description: LocalizedText
- buttons?: RemnawaveButton[]
- svgIconKey?: string
- svgIconColor?: string
+ title: LocalizedText;
+ description: LocalizedText;
+ buttons?: RemnawaveButton[];
+ svgIconKey?: string;
+ svgIconColor?: string;
}
export interface RemnawaveApp {
- name: string
- featured?: boolean
- urlScheme?: string
- isNeedBase64Encoding?: boolean
- blocks: RemnawaveBlock[]
+ name: string;
+ featured?: boolean;
+ urlScheme?: string;
+ isNeedBase64Encoding?: boolean;
+ blocks: RemnawaveBlock[];
}
export interface RemnawavePlatform {
- apps: RemnawaveApp[]
+ apps: RemnawaveApp[];
}
export interface RemnawaveSvgItem {
- svgString: string
- tags?: string[]
+ svgString: string;
+ tags?: string[];
}
export interface RemnawaveBaseSettings {
- isShowTutorialButton: boolean
- tutorialUrl: string
+ isShowTutorialButton: boolean;
+ tutorialUrl: string;
}
export interface RemnawaveBaseTranslations {
- installApp: LocalizedText
- addSubscription: LocalizedText
- connectAndUse: LocalizedText
- copyLink: LocalizedText
- openApp: LocalizedText
- tutorial: LocalizedText
- close: LocalizedText
+ installApp: LocalizedText;
+ addSubscription: LocalizedText;
+ connectAndUse: LocalizedText;
+ copyLink: LocalizedText;
+ openApp: LocalizedText;
+ tutorial: LocalizedText;
+ close: LocalizedText;
}
export interface RemnawaveBrandingSettings {
- name: string
- logoUrl: string
- supportUrl: string
+ name: string;
+ logoUrl: string;
+ supportUrl: string;
}
export interface RemnawaveConfig {
- platforms: Record
- svgLibrary?: Record
- baseSettings?: RemnawaveBaseSettings
- baseTranslations?: RemnawaveBaseTranslations
- brandingSettings?: RemnawaveBrandingSettings
+ platforms: Record;
+ svgLibrary?: Record;
+ baseSettings?: RemnawaveBaseSettings;
+ baseTranslations?: RemnawaveBaseTranslations;
+ brandingSettings?: RemnawaveBrandingSettings;
}
// ============== Converter Functions ==============
@@ -225,23 +245,29 @@ const emptyLocalizedText = (): LocalizedText => ({
zh: '',
fa: '',
fr: '',
-})
+});
// Convert Cabinet button to RemnaWave button
const cabinetButtonToRemnawave = (button: AppButton): RemnawaveButton => ({
url: button.buttonLink,
text: { ...emptyLocalizedText(), ...button.buttonText },
-})
+});
// Convert RemnaWave button to Cabinet button
const remnawaveButtonToCabinet = (button: RemnawaveButton): AppButton => ({
buttonLink: button.url,
- buttonText: { en: button.text.en || '', ru: button.text.ru || '', zh: button.text.zh, fa: button.text.fa, fr: button.text.fr },
-})
+ buttonText: {
+ en: button.text.en || '',
+ ru: button.text.ru || '',
+ zh: button.text.zh,
+ fa: button.text.fa,
+ fr: button.text.fr,
+ },
+});
// Convert Cabinet app to RemnaWave app format
export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
- const blocks: RemnawaveBlock[] = []
+ const blocks: RemnawaveBlock[] = [];
// Block 1: Installation
blocks.push({
@@ -250,17 +276,27 @@ export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
buttons: app.installationStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'download',
svgIconColor: '#3B82F6',
- })
+ });
// Block 2 (optional): Additional before subscription
- if (app.additionalBeforeAddSubscriptionStep?.description?.en || app.additionalBeforeAddSubscriptionStep?.description?.ru) {
+ if (
+ app.additionalBeforeAddSubscriptionStep?.description?.en ||
+ app.additionalBeforeAddSubscriptionStep?.description?.ru
+ ) {
blocks.push({
- title: app.additionalBeforeAddSubscriptionStep.title || { ...emptyLocalizedText(), en: 'Preparation', ru: 'Подготовка' },
- description: { ...emptyLocalizedText(), ...app.additionalBeforeAddSubscriptionStep.description },
+ title: app.additionalBeforeAddSubscriptionStep.title || {
+ ...emptyLocalizedText(),
+ en: 'Preparation',
+ ru: 'Подготовка',
+ },
+ description: {
+ ...emptyLocalizedText(),
+ ...app.additionalBeforeAddSubscriptionStep.description,
+ },
buttons: app.additionalBeforeAddSubscriptionStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'settings',
svgIconColor: '#8B5CF6',
- })
+ });
}
// Block 3: Add subscription
@@ -270,17 +306,27 @@ export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
buttons: app.addSubscriptionStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'plus',
svgIconColor: '#10B981',
- })
+ });
// Block 4 (optional): Additional after subscription
- if (app.additionalAfterAddSubscriptionStep?.description?.en || app.additionalAfterAddSubscriptionStep?.description?.ru) {
+ if (
+ app.additionalAfterAddSubscriptionStep?.description?.en ||
+ app.additionalAfterAddSubscriptionStep?.description?.ru
+ ) {
blocks.push({
- title: app.additionalAfterAddSubscriptionStep.title || { ...emptyLocalizedText(), en: 'Configuration', ru: 'Настройка' },
- description: { ...emptyLocalizedText(), ...app.additionalAfterAddSubscriptionStep.description },
+ title: app.additionalAfterAddSubscriptionStep.title || {
+ ...emptyLocalizedText(),
+ en: 'Configuration',
+ ru: 'Настройка',
+ },
+ description: {
+ ...emptyLocalizedText(),
+ ...app.additionalAfterAddSubscriptionStep.description,
+ },
buttons: app.additionalAfterAddSubscriptionStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'settings',
svgIconColor: '#F59E0B',
- })
+ });
}
// Block 5: Connect and use
@@ -290,7 +336,7 @@ export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
buttons: app.connectAndUseStep.buttons?.map(cabinetButtonToRemnawave),
svgIconKey: 'check',
svgIconColor: '#22C55E',
- })
+ });
return {
name: app.name,
@@ -298,78 +344,104 @@ export const cabinetAppToRemnawave = (app: AppDefinition): RemnawaveApp => {
urlScheme: app.urlScheme,
isNeedBase64Encoding: app.isNeedBase64Encoding,
blocks,
- }
-}
+ };
+};
// Convert RemnaWave app to Cabinet app format
export const remnawaveAppToCabinet = (app: RemnawaveApp, platform: string): AppDefinition => {
- const blocks = app.blocks || []
+ const blocks = app.blocks || [];
// Default empty step
const emptyStep = (): AppStep => ({
description: emptyLocalizedText(),
buttons: [],
- })
+ });
// Try to identify blocks by their titles or position
- let installationBlock: RemnawaveBlock | undefined
- let subscriptionBlock: RemnawaveBlock | undefined
- let connectBlock: RemnawaveBlock | undefined
- let beforeSubBlock: RemnawaveBlock | undefined
- let afterSubBlock: RemnawaveBlock | undefined
+ let installationBlock: RemnawaveBlock | undefined;
+ let subscriptionBlock: RemnawaveBlock | undefined;
+ let connectBlock: RemnawaveBlock | undefined;
+ let beforeSubBlock: RemnawaveBlock | undefined;
+ let afterSubBlock: RemnawaveBlock | undefined;
// First pass: try to identify by title keywords
for (const block of blocks) {
- const enTitle = (block.title?.en || '').toLowerCase()
- const ruTitle = (block.title?.ru || '').toLowerCase()
+ const enTitle = (block.title?.en || '').toLowerCase();
+ const ruTitle = (block.title?.ru || '').toLowerCase();
if (enTitle.includes('install') || ruTitle.includes('установ') || ruTitle.includes('скачай')) {
- if (!installationBlock) installationBlock = block
- } else if (enTitle.includes('subscription') || enTitle.includes('add') || ruTitle.includes('подписк') || ruTitle.includes('добав')) {
- if (!subscriptionBlock) subscriptionBlock = block
- } else if (enTitle.includes('connect') || enTitle.includes('use') || ruTitle.includes('подключ') || ruTitle.includes('использ')) {
- if (!connectBlock) connectBlock = block
+ if (!installationBlock) installationBlock = block;
+ } else if (
+ enTitle.includes('subscription') ||
+ enTitle.includes('add') ||
+ ruTitle.includes('подписк') ||
+ ruTitle.includes('добав')
+ ) {
+ if (!subscriptionBlock) subscriptionBlock = block;
+ } else if (
+ enTitle.includes('connect') ||
+ enTitle.includes('use') ||
+ ruTitle.includes('подключ') ||
+ ruTitle.includes('использ')
+ ) {
+ if (!connectBlock) connectBlock = block;
}
}
// Second pass: assign remaining blocks by position if not found by title
if (!installationBlock && blocks.length > 0) {
- installationBlock = blocks[0]
+ installationBlock = blocks[0];
}
if (!subscriptionBlock && blocks.length > 1) {
- subscriptionBlock = blocks.find(b => b !== installationBlock) || blocks[1]
+ subscriptionBlock = blocks.find((b) => b !== installationBlock) || blocks[1];
}
if (!connectBlock && blocks.length > 2) {
- connectBlock = blocks.find(b => b !== installationBlock && b !== subscriptionBlock) || blocks[blocks.length - 1]
+ connectBlock =
+ blocks.find((b) => b !== installationBlock && b !== subscriptionBlock) ||
+ blocks[blocks.length - 1];
}
// Assign additional blocks
- const additionalBlocks = blocks.filter(b =>
- b !== installationBlock && b !== subscriptionBlock && b !== connectBlock
- )
+ const additionalBlocks = blocks.filter(
+ (b) => b !== installationBlock && b !== subscriptionBlock && b !== connectBlock,
+ );
if (additionalBlocks.length >= 1) {
// Check if block appears before subscription
- const subIndex = blocks.indexOf(subscriptionBlock!)
- const firstAdditionalIndex = blocks.indexOf(additionalBlocks[0])
+ const subIndex = blocks.indexOf(subscriptionBlock!);
+ const firstAdditionalIndex = blocks.indexOf(additionalBlocks[0]);
if (firstAdditionalIndex < subIndex) {
- beforeSubBlock = additionalBlocks[0]
+ beforeSubBlock = additionalBlocks[0];
if (additionalBlocks.length >= 2) {
- afterSubBlock = additionalBlocks[1]
+ afterSubBlock = additionalBlocks[1];
}
} else {
- afterSubBlock = additionalBlocks[0]
+ afterSubBlock = additionalBlocks[0];
}
}
// Convert block to cabinet step
const blockToStep = (block: RemnawaveBlock | undefined): AppStep => {
- if (!block) return emptyStep()
+ if (!block) return emptyStep();
return {
- description: { en: block.description?.en || '', ru: block.description?.ru || '', zh: block.description?.zh, fa: block.description?.fa, fr: block.description?.fr },
- title: block.title ? { en: block.title.en || '', ru: block.title.ru || '', zh: block.title.zh, fa: block.title.fa, fr: block.title.fr } : undefined,
+ description: {
+ en: block.description?.en || '',
+ ru: block.description?.ru || '',
+ zh: block.description?.zh,
+ fa: block.description?.fa,
+ fr: block.description?.fr,
+ },
+ title: block.title
+ ? {
+ en: block.title.en || '',
+ ru: block.title.ru || '',
+ zh: block.title.zh,
+ fa: block.title.fa,
+ fr: block.title.fr,
+ }
+ : undefined,
buttons: block.buttons?.map(remnawaveButtonToCabinet),
- }
- }
+ };
+ };
return {
id: `${app.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${platform}-${Date.now()}`,
@@ -382,66 +454,114 @@ export const remnawaveAppToCabinet = (app: RemnawaveApp, platform: string): AppD
connectAndUseStep: blockToStep(connectBlock),
additionalBeforeAddSubscriptionStep: beforeSubBlock ? blockToStep(beforeSubBlock) : undefined,
additionalAfterAddSubscriptionStep: afterSubBlock ? blockToStep(afterSubBlock) : undefined,
- }
-}
+ };
+};
// Export all apps from cabinet to RemnaWave format
export const exportToRemnawaveFormat = (
platformApps: Record,
- branding?: AppConfigBranding
+ branding?: AppConfigBranding,
): RemnawaveConfig => {
- const platforms: Record = {}
+ const platforms: Record = {};
for (const [platform, apps] of Object.entries(platformApps)) {
platforms[platform] = {
apps: apps.map(cabinetAppToRemnawave),
- }
+ };
}
return {
platforms,
svgLibrary: {
- download: { svgString: '' },
- plus: { svgString: '' },
- check: { svgString: '' },
- settings: { svgString: '' },
+ download: {
+ svgString:
+ '',
+ },
+ plus: {
+ svgString:
+ '',
+ },
+ check: {
+ svgString:
+ '',
+ },
+ settings: {
+ svgString:
+ '',
+ },
},
baseSettings: {
isShowTutorialButton: false,
tutorialUrl: '',
},
baseTranslations: {
- installApp: { en: 'Install App', ru: 'Установить приложение', zh: '安装应用', fa: 'نصب برنامه', fr: 'Installer l\'application' },
- addSubscription: { en: 'Add Subscription', ru: 'Добавить подписку', zh: '添加订阅', fa: 'اضافه کردن اشتراک', fr: 'Ajouter un abonnement' },
- connectAndUse: { en: 'Connect and Use', ru: 'Подключиться и использовать', zh: '连接并使用', fa: 'اتصال و استفاده', fr: 'Connecter et utiliser' },
- copyLink: { en: 'Copy Link', ru: 'Скопировать ссылку', zh: '复制链接', fa: 'کپی لینک', fr: 'Copier le lien' },
- openApp: { en: 'Open App', ru: 'Открыть приложение', zh: '打开应用', fa: 'باز کردن برنامه', fr: 'Ouvrir l\'application' },
+ installApp: {
+ en: 'Install App',
+ ru: 'Установить приложение',
+ zh: '安装应用',
+ fa: 'نصب برنامه',
+ fr: "Installer l'application",
+ },
+ addSubscription: {
+ en: 'Add Subscription',
+ ru: 'Добавить подписку',
+ zh: '添加订阅',
+ fa: 'اضافه کردن اشتراک',
+ fr: 'Ajouter un abonnement',
+ },
+ connectAndUse: {
+ en: 'Connect and Use',
+ ru: 'Подключиться и использовать',
+ zh: '连接并使用',
+ fa: 'اتصال و استفاده',
+ fr: 'Connecter et utiliser',
+ },
+ copyLink: {
+ en: 'Copy Link',
+ ru: 'Скопировать ссылку',
+ zh: '复制链接',
+ fa: 'کپی لینک',
+ fr: 'Copier le lien',
+ },
+ openApp: {
+ en: 'Open App',
+ ru: 'Открыть приложение',
+ zh: '打开应用',
+ fa: 'باز کردن برنامه',
+ fr: "Ouvrir l'application",
+ },
tutorial: { en: 'Tutorial', ru: 'Инструкция', zh: '教程', fa: 'آموزش', fr: 'Tutoriel' },
close: { en: 'Close', ru: 'Закрыть', zh: '关闭', fa: 'بستن', fr: 'Fermer' },
},
- brandingSettings: branding ? {
- name: branding.name,
- logoUrl: branding.logoUrl,
- supportUrl: branding.supportUrl,
- } : undefined,
- }
-}
+ brandingSettings: branding
+ ? {
+ name: branding.name,
+ logoUrl: branding.logoUrl,
+ supportUrl: branding.supportUrl,
+ }
+ : undefined,
+ };
+};
// Import apps from RemnaWave format to cabinet
export const importFromRemnawaveFormat = (
- config: RemnawaveConfig
+ config: RemnawaveConfig,
): { platformApps: Record; branding?: AppConfigBranding } => {
- const platformApps: Record = {}
+ const platformApps: Record = {};
for (const [platform, platformData] of Object.entries(config.platforms || {})) {
- platformApps[platform] = (platformData.apps || []).map(app => remnawaveAppToCabinet(app, platform))
+ platformApps[platform] = (platformData.apps || []).map((app) =>
+ remnawaveAppToCabinet(app, platform),
+ );
}
- const branding = config.brandingSettings ? {
- name: config.brandingSettings.name,
- logoUrl: config.brandingSettings.logoUrl,
- supportUrl: config.brandingSettings.supportUrl,
- } : undefined
+ const branding = config.brandingSettings
+ ? {
+ name: config.brandingSettings.name,
+ logoUrl: config.brandingSettings.logoUrl,
+ supportUrl: config.brandingSettings.supportUrl,
+ }
+ : undefined;
- return { platformApps, branding }
-}
+ return { platformApps, branding };
+};
diff --git a/src/api/adminBroadcasts.ts b/src/api/adminBroadcasts.ts
index 43a7df4..6f60fc0 100644
--- a/src/api/adminBroadcasts.ts
+++ b/src/api/adminBroadcasts.ts
@@ -1,168 +1,187 @@
-import apiClient from './client'
+import apiClient from './client';
// Types
export interface BroadcastFilter {
- key: string
- label: string
- count: number | null
- group: string | null
+ key: string;
+ label: string;
+ count: number | null;
+ group: string | null;
}
export interface TariffFilter {
- key: string
- label: string
- tariff_id: number
- count: number
+ key: string;
+ label: string;
+ tariff_id: number;
+ count: number;
}
export interface BroadcastFiltersResponse {
- filters: BroadcastFilter[]
- tariff_filters: TariffFilter[]
- custom_filters: BroadcastFilter[]
+ filters: BroadcastFilter[];
+ tariff_filters: TariffFilter[];
+ custom_filters: BroadcastFilter[];
}
export interface TariffForBroadcast {
- id: number
- name: string
- filter_key: string
- active_users_count: number
+ id: number;
+ name: string;
+ filter_key: string;
+ active_users_count: number;
}
export interface BroadcastTariffsResponse {
- tariffs: TariffForBroadcast[]
+ tariffs: TariffForBroadcast[];
}
export interface BroadcastButton {
- key: string
- label: string
- default: boolean
+ key: string;
+ label: string;
+ default: boolean;
}
export interface BroadcastButtonsResponse {
- buttons: BroadcastButton[]
+ buttons: BroadcastButton[];
}
export interface BroadcastMedia {
- type: 'photo' | 'video' | 'document'
- file_id: string
- caption?: string
+ type: 'photo' | 'video' | 'document';
+ file_id: string;
+ caption?: string;
}
export interface BroadcastCreateRequest {
- target: string
- message_text: string
- selected_buttons: string[]
- media?: BroadcastMedia
+ target: string;
+ message_text: string;
+ selected_buttons: string[];
+ media?: BroadcastMedia;
}
export interface Broadcast {
- id: number
- target_type: string
- message_text: string
- has_media: boolean
- media_type: string | null
- media_file_id: string | null
- media_caption: string | null
- total_count: number
- sent_count: number
- failed_count: number
- status: 'queued' | 'in_progress' | 'completed' | 'partial' | 'failed' | 'cancelled' | 'cancelling'
- admin_id: number | null
- admin_name: string | null
- created_at: string
- completed_at: string | null
- progress_percent: number
+ id: number;
+ target_type: string;
+ message_text: string;
+ has_media: boolean;
+ media_type: string | null;
+ media_file_id: string | null;
+ media_caption: string | null;
+ total_count: number;
+ sent_count: number;
+ failed_count: number;
+ status:
+ | 'queued'
+ | 'in_progress'
+ | 'completed'
+ | 'partial'
+ | 'failed'
+ | 'cancelled'
+ | 'cancelling';
+ admin_id: number | null;
+ admin_name: string | null;
+ created_at: string;
+ completed_at: string | null;
+ progress_percent: number;
}
export interface BroadcastListResponse {
- items: Broadcast[]
- total: number
- limit: number
- offset: number
+ items: Broadcast[];
+ total: number;
+ limit: number;
+ offset: number;
}
export interface BroadcastPreviewRequest {
- target: string
+ target: string;
}
export interface BroadcastPreviewResponse {
- target: string
- count: number
+ target: string;
+ count: number;
}
export interface MediaUploadResponse {
- media_type: string
- file_id: string
- file_unique_id: string | null
- media_url: string
+ media_type: string;
+ file_id: string;
+ file_unique_id: string | null;
+ media_url: string;
}
export const adminBroadcastsApi = {
// Get all available filters with counts
getFilters: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/broadcasts/filters')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/broadcasts/filters',
+ );
+ return response.data;
},
// Get tariffs for filtering
getTariffs: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/broadcasts/tariffs')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/broadcasts/tariffs',
+ );
+ return response.data;
},
// Get available buttons
getButtons: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/broadcasts/buttons')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/broadcasts/buttons',
+ );
+ return response.data;
},
// Preview broadcast (get recipients count)
preview: async (target: string): Promise => {
- const response = await apiClient.post('/cabinet/admin/broadcasts/preview', {
- target,
- })
- return response.data
+ const response = await apiClient.post(
+ '/cabinet/admin/broadcasts/preview',
+ {
+ target,
+ },
+ );
+ return response.data;
},
// Create and start broadcast
create: async (data: BroadcastCreateRequest): Promise => {
- const response = await apiClient.post('/cabinet/admin/broadcasts', data)
- return response.data
+ const response = await apiClient.post('/cabinet/admin/broadcasts', data);
+ return response.data;
},
// Get list of broadcasts
list: async (limit = 20, offset = 0): Promise => {
const response = await apiClient.get('/cabinet/admin/broadcasts', {
params: { limit, offset },
- })
- return response.data
+ });
+ return response.data;
},
// Get broadcast details
get: async (id: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/broadcasts/${id}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/broadcasts/${id}`);
+ return response.data;
},
// Stop broadcast
stop: async (id: number): Promise => {
- const response = await apiClient.post(`/cabinet/admin/broadcasts/${id}/stop`)
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/broadcasts/${id}/stop`);
+ return response.data;
},
// Upload media (uses existing media endpoint)
- uploadMedia: async (file: File, mediaType: 'photo' | 'video' | 'document'): Promise => {
- const formData = new FormData()
- formData.append('file', file)
- formData.append('media_type', mediaType)
+ uploadMedia: async (
+ file: File,
+ mediaType: 'photo' | 'video' | 'document',
+ ): Promise => {
+ const formData = new FormData();
+ formData.append('file', file);
+ formData.append('media_type', mediaType);
const response = await apiClient.post('/cabinet/media/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
- })
- return response.data
+ });
+ return response.data;
},
-}
+};
-export default adminBroadcastsApi
+export default adminBroadcastsApi;
diff --git a/src/api/adminEmailTemplates.ts b/src/api/adminEmailTemplates.ts
index 4c0b225..0a0164d 100644
--- a/src/api/adminEmailTemplates.ts
+++ b/src/api/adminEmailTemplates.ts
@@ -1,85 +1,105 @@
-import apiClient from './client'
+import apiClient from './client';
export interface EmailTemplateLanguageStatus {
- has_custom: boolean
+ has_custom: boolean;
}
export interface EmailTemplateType {
- type: string
- label: Record
- description: Record
- context_vars: string[]
- languages: Record
+ type: string;
+ label: Record;
+ description: Record;
+ context_vars: string[];
+ languages: Record;
}
export interface EmailTemplateListResponse {
- items: EmailTemplateType[]
- available_languages: string[]
+ items: EmailTemplateType[];
+ available_languages: string[];
}
export interface EmailTemplateLanguageData {
- subject: string
- body_html: string
- is_default: boolean
- default_subject: string
- default_body_html: string
+ subject: string;
+ body_html: string;
+ is_default: boolean;
+ default_subject: string;
+ default_body_html: string;
}
export interface EmailTemplateDetail {
- notification_type: string
- label: Record
- description: Record
- context_vars: string[]
- languages: Record
+ notification_type: string;
+ label: Record;
+ description: Record;
+ context_vars: string[];
+ languages: Record;
}
export interface EmailTemplateUpdateRequest {
- subject: string
- body_html: string
+ subject: string;
+ body_html: string;
}
export interface EmailTemplatePreviewRequest {
- language: string
- subject?: string
- body_html?: string
+ language: string;
+ subject?: string;
+ body_html?: string;
}
export interface EmailTemplatePreviewResponse {
- subject: string
- body_html: string
+ subject: string;
+ body_html: string;
}
export interface EmailTemplateSendTestRequest {
- language: string
- email?: string
+ language: string;
+ email?: string;
}
export const adminEmailTemplatesApi = {
getTemplateTypes: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/email-templates')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/email-templates',
+ );
+ return response.data;
},
getTemplate: async (notificationType: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/email-templates/${notificationType}`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/email-templates/${notificationType}`,
+ );
+ return response.data;
},
- updateTemplate: async (notificationType: string, language: string, data: EmailTemplateUpdateRequest): Promise => {
- await apiClient.put(`/cabinet/admin/email-templates/${notificationType}/${language}`, data)
+ updateTemplate: async (
+ notificationType: string,
+ language: string,
+ data: EmailTemplateUpdateRequest,
+ ): Promise => {
+ await apiClient.put(`/cabinet/admin/email-templates/${notificationType}/${language}`, data);
},
deleteTemplate: async (notificationType: string, language: string): Promise => {
- await apiClient.delete(`/cabinet/admin/email-templates/${notificationType}/${language}`)
+ await apiClient.delete(`/cabinet/admin/email-templates/${notificationType}/${language}`);
},
- previewTemplate: async (notificationType: string, data: EmailTemplatePreviewRequest): Promise => {
- const response = await apiClient.post(`/cabinet/admin/email-templates/${notificationType}/preview`, data)
- return response.data
+ previewTemplate: async (
+ notificationType: string,
+ data: EmailTemplatePreviewRequest,
+ ): Promise => {
+ const response = await apiClient.post(
+ `/cabinet/admin/email-templates/${notificationType}/preview`,
+ data,
+ );
+ return response.data;
},
- sendTestEmail: async (notificationType: string, data: EmailTemplateSendTestRequest): Promise<{ sent_to: string }> => {
- const response = await apiClient.post<{ status: string; sent_to: string }>(`/cabinet/admin/email-templates/${notificationType}/test`, data)
- return response.data
+ sendTestEmail: async (
+ notificationType: string,
+ data: EmailTemplateSendTestRequest,
+ ): Promise<{ sent_to: string }> => {
+ const response = await apiClient.post<{ status: string; sent_to: string }>(
+ `/cabinet/admin/email-templates/${notificationType}/test`,
+ data,
+ );
+ return response.data;
},
-}
+};
diff --git a/src/api/adminPaymentMethods.ts b/src/api/adminPaymentMethods.ts
index 0a866d4..b8f83fc 100644
--- a/src/api/adminPaymentMethods.ts
+++ b/src/api/adminPaymentMethods.ts
@@ -1,31 +1,35 @@
-import apiClient from './client'
-import type { PaymentMethodConfig, PromoGroupSimple } from '../types'
+import apiClient from './client';
+import type { PaymentMethodConfig, PromoGroupSimple } from '../types';
export const adminPaymentMethodsApi = {
getAll: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/payment-methods')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/payment-methods');
+ return response.data;
},
getOne: async (methodId: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/payment-methods/${methodId}`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/payment-methods/${methodId}`,
+ );
+ return response.data;
},
update: async (methodId: string, data: Record): Promise => {
const response = await apiClient.put(
`/cabinet/admin/payment-methods/${methodId}`,
- data
- )
- return response.data
+ data,
+ );
+ return response.data;
},
updateOrder: async (methodIds: string[]): Promise => {
- await apiClient.put('/cabinet/admin/payment-methods/order', { method_ids: methodIds })
+ await apiClient.put('/cabinet/admin/payment-methods/order', { method_ids: methodIds });
},
getPromoGroups: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/payment-methods/promo-groups')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/payment-methods/promo-groups',
+ );
+ return response.data;
},
-}
+};
diff --git a/src/api/adminPayments.ts b/src/api/adminPayments.ts
index a169517..091e867 100644
--- a/src/api/adminPayments.ts
+++ b/src/api/adminPayments.ts
@@ -1,39 +1,46 @@
-import apiClient from './client'
-import type { PaginatedResponse, PendingPayment, ManualCheckResponse } from '../types'
+import apiClient from './client';
+import type { PaginatedResponse, PendingPayment, ManualCheckResponse } from '../types';
export interface PaymentsStats {
- total_pending: number
- by_method: Record
+ total_pending: number;
+ by_method: Record;
}
export const adminPaymentsApi = {
// Get all pending payments (admin)
getPendingPayments: async (params?: {
- page?: number
- per_page?: number
- method_filter?: string
+ page?: number;
+ per_page?: number;
+ method_filter?: string;
}): Promise> => {
- const response = await apiClient.get>('/cabinet/admin/payments', {
- params,
- })
- return response.data
+ const response = await apiClient.get>(
+ '/cabinet/admin/payments',
+ {
+ params,
+ },
+ );
+ return response.data;
},
// Get payments statistics
getStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/payments/stats')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/payments/stats');
+ return response.data;
},
// Get specific payment details
getPayment: async (method: string, paymentId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/payments/${method}/${paymentId}`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/payments/${method}/${paymentId}`,
+ );
+ return response.data;
},
// Manually check payment status
checkPaymentStatus: async (method: string, paymentId: number): Promise => {
- const response = await apiClient.post(`/cabinet/admin/payments/${method}/${paymentId}/check`)
- return response.data
+ const response = await apiClient.post(
+ `/cabinet/admin/payments/${method}/${paymentId}/check`,
+ );
+ return response.data;
},
-}
+};
diff --git a/src/api/adminRemnawave.ts b/src/api/adminRemnawave.ts
index a044e03..2043925 100644
--- a/src/api/adminRemnawave.ts
+++ b/src/api/adminRemnawave.ts
@@ -1,227 +1,227 @@
-import { apiClient } from './client'
+import { apiClient } from './client';
// ============ Types ============
// Status & Connection
export interface ConnectionStatus {
- status: string
- message: string
- api_url?: string
- status_code?: number
- system_info?: Record
+ status: string;
+ message: string;
+ api_url?: string;
+ status_code?: number;
+ system_info?: Record;
}
export interface RemnaWaveStatusResponse {
- is_configured: boolean
- configuration_error?: string
- connection?: ConnectionStatus
+ is_configured: boolean;
+ configuration_error?: string;
+ connection?: ConnectionStatus;
}
// System Statistics
export interface SystemSummary {
- users_online: number
- total_users: number
- active_connections: number
- nodes_online: number
- users_last_day: number
- users_last_week: number
- users_never_online: number
- total_user_traffic: number
+ users_online: number;
+ total_users: number;
+ active_connections: number;
+ nodes_online: number;
+ users_last_day: number;
+ users_last_week: number;
+ users_never_online: number;
+ total_user_traffic: number;
}
export interface ServerInfo {
- cpu_cores: number
- cpu_physical_cores: number
- memory_total: number
- memory_used: number
- memory_free: number
- memory_available: number
- uptime_seconds: number
+ cpu_cores: number;
+ cpu_physical_cores: number;
+ memory_total: number;
+ memory_used: number;
+ memory_free: number;
+ memory_available: number;
+ uptime_seconds: number;
}
export interface Bandwidth {
- realtime_download: number
- realtime_upload: number
- realtime_total: number
+ realtime_download: number;
+ realtime_upload: number;
+ realtime_total: number;
}
export interface TrafficPeriod {
- current: number
- previous: number
- difference?: string
+ current: number;
+ previous: number;
+ difference?: string;
}
export interface TrafficPeriods {
- last_2_days: TrafficPeriod
- last_7_days: TrafficPeriod
- last_30_days: TrafficPeriod
- current_month: TrafficPeriod
- current_year: TrafficPeriod
+ last_2_days: TrafficPeriod;
+ last_7_days: TrafficPeriod;
+ last_30_days: TrafficPeriod;
+ current_month: TrafficPeriod;
+ current_year: TrafficPeriod;
}
export interface SystemStatsResponse {
- system: SystemSummary
- users_by_status: Record
- server_info: ServerInfo
- bandwidth: Bandwidth
- traffic_periods: TrafficPeriods
- nodes_realtime: Record[]
- nodes_weekly: Record[]
- last_updated?: string
+ system: SystemSummary;
+ users_by_status: Record;
+ server_info: ServerInfo;
+ bandwidth: Bandwidth;
+ traffic_periods: TrafficPeriods;
+ nodes_realtime: Record[];
+ nodes_weekly: Record[];
+ last_updated?: string;
}
// Nodes
export interface NodeInfo {
- uuid: string
- name: string
- address: string
- country_code?: string
- is_connected: boolean
- is_disabled: boolean
- is_node_online: boolean
- is_xray_running: boolean
- users_online?: number
- traffic_used_bytes?: number
- traffic_limit_bytes?: number
- last_status_change?: string
- last_status_message?: string
- xray_uptime?: string
- is_traffic_tracking_active: boolean
- traffic_reset_day?: number
- notify_percent?: number
- consumption_multiplier: number
- cpu_count?: number
- cpu_model?: string
- total_ram?: string
- created_at?: string
- updated_at?: string
- provider_uuid?: string
+ uuid: string;
+ name: string;
+ address: string;
+ country_code?: string;
+ is_connected: boolean;
+ is_disabled: boolean;
+ is_node_online: boolean;
+ is_xray_running: boolean;
+ users_online?: number;
+ traffic_used_bytes?: number;
+ traffic_limit_bytes?: number;
+ last_status_change?: string;
+ last_status_message?: string;
+ xray_uptime?: string;
+ is_traffic_tracking_active: boolean;
+ traffic_reset_day?: number;
+ notify_percent?: number;
+ consumption_multiplier: number;
+ cpu_count?: number;
+ cpu_model?: string;
+ total_ram?: string;
+ created_at?: string;
+ updated_at?: string;
+ provider_uuid?: string;
}
export interface NodesListResponse {
- items: NodeInfo[]
- total: number
+ items: NodeInfo[];
+ total: number;
}
export interface NodesOverview {
- total: number
- online: number
- offline: number
- disabled: number
- total_users_online: number
- nodes: NodeInfo[]
+ total: number;
+ online: number;
+ offline: number;
+ disabled: number;
+ total_users_online: number;
+ nodes: NodeInfo[];
}
export interface NodeStatisticsResponse {
- node: NodeInfo
- realtime?: Record
- usage_history: Record[]
- last_updated?: string
+ node: NodeInfo;
+ realtime?: Record;
+ usage_history: Record[];
+ last_updated?: string;
}
export interface NodeActionResponse {
- success: boolean
- message?: string
- is_disabled?: boolean
+ success: boolean;
+ message?: string;
+ is_disabled?: boolean;
}
// Squads
export interface SquadWithLocalInfo {
- uuid: string
- name: string
- members_count: number
- inbounds_count: number
- inbounds: Record[]
- local_id?: number
- display_name?: string
- country_code?: string
- is_available?: boolean
- is_trial_eligible?: boolean
- price_kopeks?: number
- max_users?: number
- current_users?: number
- is_synced: boolean
+ uuid: string;
+ name: string;
+ members_count: number;
+ inbounds_count: number;
+ inbounds: Record[];
+ local_id?: number;
+ display_name?: string;
+ country_code?: string;
+ is_available?: boolean;
+ is_trial_eligible?: boolean;
+ price_kopeks?: number;
+ max_users?: number;
+ current_users?: number;
+ is_synced: boolean;
}
export interface SquadsListResponse {
- items: SquadWithLocalInfo[]
- total: number
+ items: SquadWithLocalInfo[];
+ total: number;
}
export interface SquadDetailResponse extends SquadWithLocalInfo {
- description?: string
- sort_order?: number
- active_subscriptions: number
+ description?: string;
+ sort_order?: number;
+ active_subscriptions: number;
}
export interface SquadOperationResponse {
- success: boolean
- message?: string
- data?: Record
+ success: boolean;
+ message?: string;
+ data?: Record;
}
// Migration
export interface MigrationPreviewResponse {
- squad_uuid: string
- squad_name: string
- current_users: number
- max_users?: number
- users_to_migrate: number
+ squad_uuid: string;
+ squad_name: string;
+ current_users: number;
+ max_users?: number;
+ users_to_migrate: number;
}
export interface MigrationStats {
- source_uuid: string
- target_uuid: string
- total: number
- updated: number
- panel_updated: number
- panel_failed: number
- source_removed: number
- target_added: number
+ source_uuid: string;
+ target_uuid: string;
+ total: number;
+ updated: number;
+ panel_updated: number;
+ panel_failed: number;
+ source_removed: number;
+ target_added: number;
}
export interface MigrationResponse {
- success: boolean
- message?: string
- error?: string
- data?: MigrationStats
+ success: boolean;
+ message?: string;
+ error?: string;
+ data?: MigrationStats;
}
// Inbounds
export interface InboundsListResponse {
- items: Record[]
- total: number
+ items: Record[];
+ total: number;
}
// Auto Sync
export interface AutoSyncStatus {
- enabled: boolean
- times: string[]
- next_run?: string
- is_running: boolean
- last_run_started_at?: string
- last_run_finished_at?: string
- last_run_success?: boolean
- last_run_reason?: string
- last_run_error?: string
- last_user_stats?: Record
- last_server_stats?: Record
+ enabled: boolean;
+ times: string[];
+ next_run?: string;
+ is_running: boolean;
+ last_run_started_at?: string;
+ last_run_finished_at?: string;
+ last_run_success?: boolean;
+ last_run_reason?: string;
+ last_run_error?: string;
+ last_user_stats?: Record;
+ last_server_stats?: Record;
}
export interface AutoSyncRunResponse {
- started: boolean
- success?: boolean
- error?: string
- user_stats?: Record
- server_stats?: Record
- reason?: string
+ started: boolean;
+ success?: boolean;
+ error?: string;
+ user_stats?: Record;
+ server_stats?: Record;
+ reason?: string;
}
// Sync
export interface SyncResponse {
- success: boolean
- message?: string
- data?: Record
+ success: boolean;
+ message?: string;
+ data?: Record;
}
// ============ API ============
@@ -229,158 +229,176 @@ export interface SyncResponse {
export const adminRemnawaveApi = {
// Status & Connection
getStatus: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/status')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/status');
+ return response.data;
},
// System Statistics
getSystemStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/system')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/system');
+ return response.data;
},
// Nodes
getNodes: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/nodes')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/nodes');
+ return response.data;
},
getNodesOverview: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/nodes/overview')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/nodes/overview');
+ return response.data;
},
getNodesRealtime: async (): Promise[]> => {
- const response = await apiClient.get('/cabinet/admin/remnawave/nodes/realtime')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/nodes/realtime');
+ return response.data;
},
getNode: async (uuid: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/remnawave/nodes/${uuid}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/remnawave/nodes/${uuid}`);
+ return response.data;
},
getNodeStatistics: async (uuid: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/remnawave/nodes/${uuid}/statistics`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/remnawave/nodes/${uuid}/statistics`);
+ return response.data;
},
- nodeAction: async (uuid: string, action: 'enable' | 'disable' | 'restart'): Promise => {
- const response = await apiClient.post(`/cabinet/admin/remnawave/nodes/${uuid}/action`, { action })
- return response.data
+ nodeAction: async (
+ uuid: string,
+ action: 'enable' | 'disable' | 'restart',
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/remnawave/nodes/${uuid}/action`, {
+ action,
+ });
+ return response.data;
},
restartAllNodes: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/nodes/restart-all')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/nodes/restart-all');
+ return response.data;
},
// Squads
getSquads: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/squads')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/squads');
+ return response.data;
},
getSquad: async (uuid: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/remnawave/squads/${uuid}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/remnawave/squads/${uuid}`);
+ return response.data;
},
- createSquad: async (data: { name: string; inbound_uuids?: string[] }): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/squads', data)
- return response.data
+ createSquad: async (data: {
+ name: string;
+ inbound_uuids?: string[];
+ }): Promise => {
+ const response = await apiClient.post('/cabinet/admin/remnawave/squads', data);
+ return response.data;
},
- updateSquad: async (uuid: string, data: { name?: string; inbound_uuids?: string[] }): Promise => {
- const response = await apiClient.patch(`/cabinet/admin/remnawave/squads/${uuid}`, data)
- return response.data
+ updateSquad: async (
+ uuid: string,
+ data: { name?: string; inbound_uuids?: string[] },
+ ): Promise => {
+ const response = await apiClient.patch(`/cabinet/admin/remnawave/squads/${uuid}`, data);
+ return response.data;
},
deleteSquad: async (uuid: string): Promise => {
- const response = await apiClient.delete(`/cabinet/admin/remnawave/squads/${uuid}`)
- return response.data
+ const response = await apiClient.delete(`/cabinet/admin/remnawave/squads/${uuid}`);
+ return response.data;
},
- squadAction: async (uuid: string, data: {
- action: 'add_all_users' | 'remove_all_users' | 'delete' | 'rename' | 'update_inbounds'
- name?: string
- inbound_uuids?: string[]
- }): Promise => {
- const response = await apiClient.post(`/cabinet/admin/remnawave/squads/${uuid}/action`, data)
- return response.data
+ squadAction: async (
+ uuid: string,
+ data: {
+ action: 'add_all_users' | 'remove_all_users' | 'delete' | 'rename' | 'update_inbounds';
+ name?: string;
+ inbound_uuids?: string[];
+ },
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/remnawave/squads/${uuid}/action`, data);
+ return response.data;
},
// Migration
getMigrationPreview: async (uuid: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/remnawave/squads/${uuid}/migration-preview`)
- return response.data
+ const response = await apiClient.get(
+ `/cabinet/admin/remnawave/squads/${uuid}/migration-preview`,
+ );
+ return response.data;
},
migrateSquad: async (sourceUuid: string, targetUuid: string): Promise => {
const response = await apiClient.post('/cabinet/admin/remnawave/squads/migrate', {
source_uuid: sourceUuid,
target_uuid: targetUuid,
- })
- return response.data
+ });
+ return response.data;
},
// Inbounds
getInbounds: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/inbounds')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/inbounds');
+ return response.data;
},
// Auto Sync
getAutoSyncStatus: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/sync/auto/status')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/sync/auto/status');
+ return response.data;
},
toggleAutoSync: async (enabled: boolean): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/auto/toggle', { enabled })
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/auto/toggle', { enabled });
+ return response.data;
},
runAutoSync: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/auto/run')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/auto/run');
+ return response.data;
},
// Manual Sync
- syncFromPanel: async (mode: 'all' | 'new_only' | 'update_only' = 'all'): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/from-panel', { mode })
- return response.data
+ syncFromPanel: async (
+ mode: 'all' | 'new_only' | 'update_only' = 'all',
+ ): Promise => {
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/from-panel', { mode });
+ return response.data;
},
syncToPanel: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/to-panel')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/to-panel');
+ return response.data;
},
syncServers: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/servers')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/servers');
+ return response.data;
},
validateSubscriptions: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/validate')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/validate');
+ return response.data;
},
cleanupSubscriptions: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/cleanup')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/cleanup');
+ return response.data;
},
syncSubscriptionStatuses: async (): Promise => {
- const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/statuses')
- return response.data
+ const response = await apiClient.post('/cabinet/admin/remnawave/sync/subscriptions/statuses');
+ return response.data;
},
getSyncRecommendations: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/remnawave/sync/recommendations')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/remnawave/sync/recommendations');
+ return response.data;
},
-}
+};
-export default adminRemnawaveApi
+export default adminRemnawaveApi;
diff --git a/src/api/adminSettings.ts b/src/api/adminSettings.ts
index 391dca3..84d3932 100644
--- a/src/api/adminSettings.ts
+++ b/src/api/adminSettings.ts
@@ -1,73 +1,79 @@
-import apiClient from './client'
+import apiClient from './client';
export interface SettingCategoryRef {
- key: string
- label: string
+ key: string;
+ label: string;
}
export interface SettingCategorySummary {
- key: string
- label: string
- description: string
- items: number
+ key: string;
+ label: string;
+ description: string;
+ items: number;
}
export interface SettingChoice {
- value: unknown
- label: string
- description?: string | null
+ value: unknown;
+ label: string;
+ description?: string | null;
}
export interface SettingHint {
- description: string
- format: string
- example: string
- warning: string
+ description: string;
+ format: string;
+ example: string;
+ warning: string;
}
export interface SettingDefinition {
- key: string
- name: string
- category: SettingCategoryRef
- type: string
- is_optional: boolean
- current: unknown
- original: unknown
- has_override: boolean
- read_only: boolean
- choices: SettingChoice[]
- hint?: SettingHint | null
+ key: string;
+ name: string;
+ category: SettingCategoryRef;
+ type: string;
+ is_optional: boolean;
+ current: unknown;
+ original: unknown;
+ has_override: boolean;
+ read_only: boolean;
+ choices: SettingChoice[];
+ hint?: SettingHint | null;
}
export const adminSettingsApi = {
// Get list of setting categories
getCategories: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/settings/categories')
- return response.data
+ const response = await apiClient.get(
+ '/cabinet/admin/settings/categories',
+ );
+ return response.data;
},
// Get all settings or settings for a specific category
getSettings: async (categoryKey?: string): Promise => {
- const params = categoryKey ? { category_key: categoryKey } : {}
- const response = await apiClient.get('/cabinet/admin/settings', { params })
- return response.data
+ const params = categoryKey ? { category_key: categoryKey } : {};
+ const response = await apiClient.get('/cabinet/admin/settings', {
+ params,
+ });
+ return response.data;
},
// Get a specific setting by key
getSetting: async (key: string): Promise => {
- const response = await apiClient.get(`/cabinet/admin/settings/${key}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/settings/${key}`);
+ return response.data;
},
// Update a setting value
updateSetting: async (key: string, value: unknown): Promise => {
- const response = await apiClient.put(`/cabinet/admin/settings/${key}`, { value })
- return response.data
+ const response = await apiClient.put(`/cabinet/admin/settings/${key}`, {
+ value,
+ });
+ return response.data;
},
// Reset a setting to default
resetSetting: async (key: string): Promise => {
- const response = await apiClient.delete(`/cabinet/admin/settings/${key}`)
- return response.data
+ const response = await apiClient.delete(`/cabinet/admin/settings/${key}`);
+ return response.data;
},
-}
+};
diff --git a/src/api/adminUsers.ts b/src/api/adminUsers.ts
index b0e48be..88e204d 100644
--- a/src/api/adminUsers.ts
+++ b/src/api/adminUsers.ts
@@ -1,418 +1,478 @@
-import apiClient from './client'
+import apiClient from './client';
// ============ Types ============
export interface UserSubscriptionInfo {
- id: number
- status: string
- is_trial: boolean
- start_date: string | null
- end_date: string | null
- traffic_limit_gb: number
- traffic_used_gb: number
- device_limit: number
- tariff_id: number | null
- tariff_name: string | null
- autopay_enabled: boolean
- is_active: boolean
- days_remaining: number
+ id: number;
+ status: string;
+ is_trial: boolean;
+ start_date: string | null;
+ end_date: string | null;
+ traffic_limit_gb: number;
+ traffic_used_gb: number;
+ device_limit: number;
+ tariff_id: number | null;
+ tariff_name: string | null;
+ autopay_enabled: boolean;
+ is_active: boolean;
+ days_remaining: number;
}
export interface UserPromoGroupInfo {
- id: number
- name: string
- is_default: boolean
+ id: number;
+ name: string;
+ is_default: boolean;
}
export interface UserListItem {
- id: number
- telegram_id: number
- username: string | null
- first_name: string | null
- last_name: string | null
- full_name: string
- status: string
- balance_kopeks: number
- balance_rubles: number
- created_at: string
- last_activity: string | null
- has_subscription: boolean
- subscription_status: string | null
- subscription_is_trial: boolean
- subscription_end_date: string | null
- promo_group_id: number | null
- promo_group_name: string | null
- total_spent_kopeks: number
- purchase_count: number
- has_restrictions: boolean
- restriction_topup: boolean
- restriction_subscription: boolean
+ id: number;
+ telegram_id: number;
+ username: string | null;
+ first_name: string | null;
+ last_name: string | null;
+ full_name: string;
+ status: string;
+ balance_kopeks: number;
+ balance_rubles: number;
+ created_at: string;
+ last_activity: string | null;
+ has_subscription: boolean;
+ subscription_status: string | null;
+ subscription_is_trial: boolean;
+ subscription_end_date: string | null;
+ promo_group_id: number | null;
+ promo_group_name: string | null;
+ total_spent_kopeks: number;
+ purchase_count: number;
+ has_restrictions: boolean;
+ restriction_topup: boolean;
+ restriction_subscription: boolean;
}
export interface UsersListResponse {
- users: UserListItem[]
- total: number
- offset: number
- limit: number
+ users: UserListItem[];
+ total: number;
+ offset: number;
+ limit: number;
}
export interface UserTransactionItem {
- id: number
- type: string
- amount_kopeks: number
- amount_rubles: number
- description: string | null
- payment_method: string | null
- is_completed: boolean
- created_at: string
+ id: number;
+ type: string;
+ amount_kopeks: number;
+ amount_rubles: number;
+ description: string | null;
+ payment_method: string | null;
+ is_completed: boolean;
+ created_at: string;
}
export interface UserReferralInfo {
- referral_code: string
- referrals_count: number
- total_earnings_kopeks: number
- commission_percent: number | null
- referred_by_id: number | null
- referred_by_username: string | null
+ referral_code: string;
+ referrals_count: number;
+ total_earnings_kopeks: number;
+ commission_percent: number | null;
+ referred_by_id: number | null;
+ referred_by_username: string | null;
}
export interface UserDetailResponse {
- id: number
- telegram_id: number
- username: string | null
- first_name: string | null
- last_name: string | null
- full_name: string
- status: string
- language: string
- balance_kopeks: number
- balance_rubles: number
- email: string | null
- email_verified: boolean
- created_at: string
- updated_at: string | null
- last_activity: string | null
- cabinet_last_login: string | null
- subscription: UserSubscriptionInfo | null
- promo_group: UserPromoGroupInfo | null
- referral: UserReferralInfo
- total_spent_kopeks: number
- purchase_count: number
- used_promocodes: number
- has_had_paid_subscription: boolean
- lifetime_used_traffic_bytes: number
- restriction_topup: boolean
- restriction_subscription: boolean
- restriction_reason: string | null
- promo_offer_discount_percent: number
- promo_offer_discount_source: string | null
- promo_offer_discount_expires_at: string | null
- recent_transactions: UserTransactionItem[]
- remnawave_uuid: string | null
+ id: number;
+ telegram_id: number;
+ username: string | null;
+ first_name: string | null;
+ last_name: string | null;
+ full_name: string;
+ status: string;
+ language: string;
+ balance_kopeks: number;
+ balance_rubles: number;
+ email: string | null;
+ email_verified: boolean;
+ created_at: string;
+ updated_at: string | null;
+ last_activity: string | null;
+ cabinet_last_login: string | null;
+ subscription: UserSubscriptionInfo | null;
+ promo_group: UserPromoGroupInfo | null;
+ referral: UserReferralInfo;
+ total_spent_kopeks: number;
+ purchase_count: number;
+ used_promocodes: number;
+ has_had_paid_subscription: boolean;
+ lifetime_used_traffic_bytes: number;
+ restriction_topup: boolean;
+ restriction_subscription: boolean;
+ restriction_reason: string | null;
+ promo_offer_discount_percent: number;
+ promo_offer_discount_source: string | null;
+ promo_offer_discount_expires_at: string | null;
+ recent_transactions: UserTransactionItem[];
+ remnawave_uuid: string | null;
}
export interface UsersStatsResponse {
- total_users: number
- active_users: number
- blocked_users: number
- deleted_users: number
- new_today: number
- new_week: number
- new_month: number
- users_with_subscription: number
- users_with_active_subscription: number
- users_with_trial: number
- users_with_expired_subscription: number
- total_balance_kopeks: number
- total_balance_rubles: number
- avg_balance_kopeks: number
- active_today: number
- active_week: number
- active_month: number
+ total_users: number;
+ active_users: number;
+ blocked_users: number;
+ deleted_users: number;
+ new_today: number;
+ new_week: number;
+ new_month: number;
+ users_with_subscription: number;
+ users_with_active_subscription: number;
+ users_with_trial: number;
+ users_with_expired_subscription: number;
+ total_balance_kopeks: number;
+ total_balance_rubles: number;
+ avg_balance_kopeks: number;
+ active_today: number;
+ active_week: number;
+ active_month: number;
}
// Available tariffs
export interface PeriodPriceInfo {
- days: number
- price_kopeks: number
- price_rubles: number
+ days: number;
+ price_kopeks: number;
+ price_rubles: number;
}
export interface UserAvailableTariff {
- id: number
- name: string
- description: string | null
- is_active: boolean
- is_trial_available: boolean
- traffic_limit_gb: number
- device_limit: number
- tier_level: number
- display_order: number
- period_prices: PeriodPriceInfo[]
- is_daily: boolean
- daily_price_kopeks: number
- custom_days_enabled: boolean
- price_per_day_kopeks: number
- min_days: number
- max_days: number
- is_available: boolean
- requires_promo_group: boolean
+ id: number;
+ name: string;
+ description: string | null;
+ is_active: boolean;
+ is_trial_available: boolean;
+ traffic_limit_gb: number;
+ device_limit: number;
+ tier_level: number;
+ display_order: number;
+ period_prices: PeriodPriceInfo[];
+ is_daily: boolean;
+ daily_price_kopeks: number;
+ custom_days_enabled: boolean;
+ price_per_day_kopeks: number;
+ min_days: number;
+ max_days: number;
+ is_available: boolean;
+ requires_promo_group: boolean;
}
export interface UserAvailableTariffsResponse {
- user_id: number
- promo_group_id: number | null
- promo_group_name: string | null
- tariffs: UserAvailableTariff[]
- total: number
- current_tariff_id: number | null
- current_tariff_name: string | null
+ user_id: number;
+ promo_group_id: number | null;
+ promo_group_name: string | null;
+ tariffs: UserAvailableTariff[];
+ total: number;
+ current_tariff_id: number | null;
+ current_tariff_name: string | null;
}
// Sync types
export interface PanelUserInfo {
- uuid: string | null
- short_uuid: string | null
- username: string | null
- status: string | null
- expire_at: string | null
- traffic_limit_gb: number
- traffic_used_gb: number
- device_limit: number
- subscription_url: string | null
- active_squads: string[]
+ uuid: string | null;
+ short_uuid: string | null;
+ username: string | null;
+ status: string | null;
+ expire_at: string | null;
+ traffic_limit_gb: number;
+ traffic_used_gb: number;
+ device_limit: number;
+ subscription_url: string | null;
+ active_squads: string[];
}
export interface SyncFromPanelResponse {
- success: boolean
- message: string
- panel_user: PanelUserInfo | null
- changes: Record
- errors: string[]
+ success: boolean;
+ message: string;
+ panel_user: PanelUserInfo | null;
+ changes: Record;
+ errors: string[];
}
export interface SyncToPanelResponse {
- success: boolean
- message: string
- action: string
- panel_uuid: string | null
- changes: Record
- errors: string[]
+ success: boolean;
+ message: string;
+ action: string;
+ panel_uuid: string | null;
+ changes: Record;
+ errors: string[];
}
export interface PanelSyncStatusResponse {
- user_id: number
- telegram_id: number
- remnawave_uuid: string | null
- last_sync: string | null
- bot_subscription_status: string | null
- bot_subscription_end_date: string | null
- bot_traffic_limit_gb: number
- bot_traffic_used_gb: number
- bot_device_limit: number
- bot_squads: string[]
- panel_found: boolean
- panel_status: string | null
- panel_expire_at: string | null
- panel_traffic_limit_gb: number
- panel_traffic_used_gb: number
- panel_device_limit: number
- panel_squads: string[]
- has_differences: boolean
- differences: string[]
+ user_id: number;
+ telegram_id: number;
+ remnawave_uuid: string | null;
+ last_sync: string | null;
+ bot_subscription_status: string | null;
+ bot_subscription_end_date: string | null;
+ bot_traffic_limit_gb: number;
+ bot_traffic_used_gb: number;
+ bot_device_limit: number;
+ bot_squads: string[];
+ panel_found: boolean;
+ panel_status: string | null;
+ panel_expire_at: string | null;
+ panel_traffic_limit_gb: number;
+ panel_traffic_used_gb: number;
+ panel_device_limit: number;
+ panel_squads: string[];
+ has_differences: boolean;
+ differences: string[];
}
// Update types
export interface UpdateBalanceRequest {
- amount_kopeks: number
- description?: string
- create_transaction?: boolean
+ amount_kopeks: number;
+ description?: string;
+ create_transaction?: boolean;
}
export interface UpdateBalanceResponse {
- success: boolean
- old_balance_kopeks: number
- new_balance_kopeks: number
- message: string
+ success: boolean;
+ old_balance_kopeks: number;
+ new_balance_kopeks: number;
+ message: string;
}
export interface UpdateSubscriptionRequest {
- action: 'extend' | 'set_end_date' | 'change_tariff' | 'set_traffic' | 'toggle_autopay' | 'cancel' | 'activate' | 'create'
- days?: number
- end_date?: string
- tariff_id?: number
- traffic_limit_gb?: number
- traffic_used_gb?: number
- autopay_enabled?: boolean
- is_trial?: boolean
- device_limit?: number
+ action:
+ | 'extend'
+ | 'set_end_date'
+ | 'change_tariff'
+ | 'set_traffic'
+ | 'toggle_autopay'
+ | 'cancel'
+ | 'activate'
+ | 'create';
+ days?: number;
+ end_date?: string;
+ tariff_id?: number;
+ traffic_limit_gb?: number;
+ traffic_used_gb?: number;
+ autopay_enabled?: boolean;
+ is_trial?: boolean;
+ device_limit?: number;
}
export interface UpdateSubscriptionResponse {
- success: boolean
- message: string
- subscription: UserSubscriptionInfo | null
+ success: boolean;
+ message: string;
+ subscription: UserSubscriptionInfo | null;
}
export interface UpdateUserStatusResponse {
- success: boolean
- old_status: string
- new_status: string
- message: string
+ success: boolean;
+ old_status: string;
+ new_status: string;
+ message: string;
}
export interface UpdateRestrictionsRequest {
- restriction_topup?: boolean
- restriction_subscription?: boolean
- restriction_reason?: string
+ restriction_topup?: boolean;
+ restriction_subscription?: boolean;
+ restriction_reason?: string;
}
export interface UpdateRestrictionsResponse {
- success: boolean
- restriction_topup: boolean
- restriction_subscription: boolean
- restriction_reason: string | null
- message: string
+ success: boolean;
+ restriction_topup: boolean;
+ restriction_subscription: boolean;
+ restriction_reason: string | null;
+ message: string;
}
export interface SyncFromPanelRequest {
- update_subscription?: boolean
- update_traffic?: boolean
- create_if_missing?: boolean
+ update_subscription?: boolean;
+ update_traffic?: boolean;
+ create_if_missing?: boolean;
}
export interface SyncToPanelRequest {
- create_if_missing?: boolean
- update_status?: boolean
- update_traffic_limit?: boolean
- update_expire_date?: boolean
- update_squads?: boolean
+ create_if_missing?: boolean;
+ update_status?: boolean;
+ update_traffic_limit?: boolean;
+ update_expire_date?: boolean;
+ update_squads?: boolean;
}
// ============ API ============
export const adminUsersApi = {
// List users
- getUsers: async (params: {
- offset?: number
- limit?: number
- search?: string
- status?: 'active' | 'blocked' | 'deleted'
- sort_by?: 'created_at' | 'balance' | 'traffic' | 'last_activity' | 'total_spent' | 'purchase_count'
- } = {}): Promise => {
- const response = await apiClient.get('/cabinet/admin/users', { params })
- return response.data
+ getUsers: async (
+ params: {
+ offset?: number;
+ limit?: number;
+ search?: string;
+ status?: 'active' | 'blocked' | 'deleted';
+ sort_by?:
+ | 'created_at'
+ | 'balance'
+ | 'traffic'
+ | 'last_activity'
+ | 'total_spent'
+ | 'purchase_count';
+ } = {},
+ ): Promise => {
+ const response = await apiClient.get('/cabinet/admin/users', { params });
+ return response.data;
},
// Get users stats
getStats: async (): Promise => {
- const response = await apiClient.get('/cabinet/admin/users/stats')
- return response.data
+ const response = await apiClient.get('/cabinet/admin/users/stats');
+ return response.data;
},
// Get user detail
getUser: async (userId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/users/${userId}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/users/${userId}`);
+ return response.data;
},
// Get user by telegram ID
getUserByTelegram: async (telegramId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/users/by-telegram/${telegramId}`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/users/by-telegram/${telegramId}`);
+ return response.data;
},
// Get available tariffs for user
- getAvailableTariffs: async (userId: number, includeInactive = false): Promise => {
+ getAvailableTariffs: async (
+ userId: number,
+ includeInactive = false,
+ ): Promise => {
const response = await apiClient.get(`/cabinet/admin/users/${userId}/available-tariffs`, {
- params: { include_inactive: includeInactive }
- })
- return response.data
+ params: { include_inactive: includeInactive },
+ });
+ return response.data;
},
// Update balance
- updateBalance: async (userId: number, data: UpdateBalanceRequest): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/balance`, data)
- return response.data
+ updateBalance: async (
+ userId: number,
+ data: UpdateBalanceRequest,
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/balance`, data);
+ return response.data;
},
// Update subscription
- updateSubscription: async (userId: number, data: UpdateSubscriptionRequest): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/subscription`, data)
- return response.data
+ updateSubscription: async (
+ userId: number,
+ data: UpdateSubscriptionRequest,
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/subscription`, data);
+ return response.data;
},
// Update status
- updateStatus: async (userId: number, status: 'active' | 'blocked' | 'deleted', reason?: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/status`, { status, reason })
- return response.data
+ updateStatus: async (
+ userId: number,
+ status: 'active' | 'blocked' | 'deleted',
+ reason?: string,
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/status`, {
+ status,
+ reason,
+ });
+ return response.data;
},
// Block user
blockUser: async (userId: number, reason?: string): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/block`, null, { params: { reason } })
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/block`, null, {
+ params: { reason },
+ });
+ return response.data;
},
// Unblock user
unblockUser: async (userId: number): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/unblock`)
- return response.data
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/unblock`);
+ return response.data;
},
// Update restrictions
- updateRestrictions: async (userId: number, data: UpdateRestrictionsRequest): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/restrictions`, data)
- return response.data
+ updateRestrictions: async (
+ userId: number,
+ data: UpdateRestrictionsRequest,
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/restrictions`, data);
+ return response.data;
},
// Update promo group
- updatePromoGroup: async (userId: number, promoGroupId: number | null): Promise<{ success: boolean; message: string }> => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/promo-group`, { promo_group_id: promoGroupId })
- return response.data
+ updatePromoGroup: async (
+ userId: number,
+ promoGroupId: number | null,
+ ): Promise<{ success: boolean; message: string }> => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/promo-group`, {
+ promo_group_id: promoGroupId,
+ });
+ return response.data;
},
// Delete user
- deleteUser: async (userId: number, softDelete = true, reason?: string): Promise<{ success: boolean; message: string }> => {
+ deleteUser: async (
+ userId: number,
+ softDelete = true,
+ reason?: string,
+ ): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.delete(`/cabinet/admin/users/${userId}`, {
- data: { soft_delete: softDelete, reason }
- })
- return response.data
+ data: { soft_delete: softDelete, reason },
+ });
+ return response.data;
},
// Get referrals
getReferrals: async (userId: number, offset = 0, limit = 50): Promise => {
const response = await apiClient.get(`/cabinet/admin/users/${userId}/referrals`, {
- params: { offset, limit }
- })
- return response.data
+ params: { offset, limit },
+ });
+ return response.data;
},
// Get transactions
- getTransactions: async (userId: number, params: {
- offset?: number
- limit?: number
- transaction_type?: string
- } = {}): Promise<{ transactions: UserTransactionItem[]; total: number; offset: number; limit: number }> => {
- const response = await apiClient.get(`/cabinet/admin/users/${userId}/transactions`, { params })
- return response.data
+ getTransactions: async (
+ userId: number,
+ params: {
+ offset?: number;
+ limit?: number;
+ transaction_type?: string;
+ } = {},
+ ): Promise<{
+ transactions: UserTransactionItem[];
+ total: number;
+ offset: number;
+ limit: number;
+ }> => {
+ const response = await apiClient.get(`/cabinet/admin/users/${userId}/transactions`, { params });
+ return response.data;
},
// Sync status
getSyncStatus: async (userId: number): Promise => {
- const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`)
- return response.data
+ const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`);
+ return response.data;
},
// Sync from panel
- syncFromPanel: async (userId: number, data: SyncFromPanelRequest = {}): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/from-panel`, data)
- return response.data
+ syncFromPanel: async (
+ userId: number,
+ data: SyncFromPanelRequest = {},
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/from-panel`, data);
+ return response.data;
},
// Sync to panel
- syncToPanel: async (userId: number, data: SyncToPanelRequest = {}): Promise => {
- const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/to-panel`, data)
- return response.data
+ syncToPanel: async (
+ userId: number,
+ data: SyncToPanelRequest = {},
+ ): Promise => {
+ const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/to-panel`, data);
+ return response.data;
},
-}
+};
diff --git a/src/api/auth.ts b/src/api/auth.ts
index a5697e2..d7104e9 100644
--- a/src/api/auth.ts
+++ b/src/api/auth.ts
@@ -1,27 +1,27 @@
-import apiClient from './client'
-import type { AuthResponse, RegisterResponse, TokenResponse, User } from '../types'
+import apiClient from './client';
+import type { AuthResponse, RegisterResponse, TokenResponse, User } from '../types';
export const authApi = {
// Telegram WebApp authentication
loginTelegram: async (initData: string): Promise => {
const response = await apiClient.post('/cabinet/auth/telegram', {
init_data: initData,
- })
- return response.data
+ });
+ return response.data;
},
// Telegram Login Widget authentication
loginTelegramWidget: async (data: {
- id: number
- first_name: string
- last_name?: string
- username?: string
- photo_url?: string
- auth_date: number
- hash: string
+ id: number;
+ first_name: string;
+ last_name?: string;
+ username?: string;
+ photo_url?: string;
+ auth_date: number;
+ hash: string;
}): Promise => {
- const response = await apiClient.post('/cabinet/auth/telegram/widget', data)
- return response.data
+ const response = await apiClient.post('/cabinet/auth/telegram/widget', data);
+ return response.data;
},
// Email login
@@ -29,63 +29,69 @@ export const authApi = {
const response = await apiClient.post('/cabinet/auth/email/login', {
email,
password,
- })
- return response.data
+ });
+ return response.data;
},
// Register email (link to existing Telegram account)
- registerEmail: async (email: string, password: string): Promise<{ message: string; email: string }> => {
+ registerEmail: async (
+ email: string,
+ password: string,
+ ): Promise<{ message: string; email: string }> => {
const response = await apiClient.post('/cabinet/auth/email/register', {
email,
password,
- })
- return response.data
+ });
+ return response.data;
},
// Register standalone email account (no Telegram required)
// Returns message - user must verify email before login
registerEmailStandalone: async (data: {
- email: string
- password: string
- first_name?: string
- language?: string
- referral_code?: string
+ email: string;
+ password: string;
+ first_name?: string;
+ language?: string;
+ referral_code?: string;
}): Promise => {
- const response = await apiClient.post('/cabinet/auth/email/register/standalone', data)
- return response.data
+ const response = await apiClient.post(
+ '/cabinet/auth/email/register/standalone',
+ data,
+ );
+ return response.data;
},
// Verify email and get auth tokens
verifyEmail: async (token: string): Promise => {
- const response = await apiClient.post('/cabinet/auth/email/verify', { token })
- return response.data
+ const response = await apiClient.post('/cabinet/auth/email/verify', { token });
+ return response.data;
},
// Resend verification email
resendVerification: async (): Promise<{ message: string }> => {
- const response = await apiClient.post('/cabinet/auth/email/resend')
- return response.data
+ const response = await apiClient.post('/cabinet/auth/email/resend');
+ return response.data;
},
// Refresh token
refreshToken: async (refreshToken: string): Promise => {
const response = await apiClient.post('/cabinet/auth/refresh', {
refresh_token: refreshToken,
- })
- return response.data
+ });
+ return response.data;
},
// Logout
logout: async (refreshToken: string): Promise => {
await apiClient.post('/cabinet/auth/logout', {
refresh_token: refreshToken,
- })
+ });
},
// Forgot password
forgotPassword: async (email: string): Promise<{ message: string }> => {
- const response = await apiClient.post('/cabinet/auth/password/forgot', { email })
- return response.data
+ const response = await apiClient.post('/cabinet/auth/password/forgot', { email });
+ return response.data;
},
// Reset password
@@ -93,13 +99,13 @@ export const authApi = {
const response = await apiClient.post('/cabinet/auth/password/reset', {
token,
password,
- })
- return response.data
+ });
+ return response.data;
},
// Get current user
getMe: async (): Promise => {
- const response = await apiClient.get('/cabinet/auth/me')
- return response.data
+ const response = await apiClient.get('/cabinet/auth/me');
+ return response.data;
},
-}
+};
diff --git a/src/api/balance.ts b/src/api/balance.ts
index d2aeef4..1772485 100644
--- a/src/api/balance.ts
+++ b/src/api/balance.ts
@@ -1,100 +1,124 @@
-import apiClient from './client'
-import type { Balance, Transaction, PaymentMethod, PaginatedResponse, PendingPayment, ManualCheckResponse } from '../types'
+import apiClient from './client';
+import type {
+ Balance,
+ Transaction,
+ PaymentMethod,
+ PaginatedResponse,
+ PendingPayment,
+ ManualCheckResponse,
+} from '../types';
export const balanceApi = {
// Get current balance
getBalance: async (): Promise => {
- const response = await apiClient.get('/cabinet/balance')
- return response.data
+ const response = await apiClient.get('/cabinet/balance');
+ return response.data;
},
// Get transaction history
getTransactions: async (params?: {
- page?: number
- per_page?: number
- type?: string
+ page?: number;
+ per_page?: number;
+ type?: string;
}): Promise> => {
- const response = await apiClient.get>('/cabinet/balance/transactions', {
- params,
- })
- return response.data
+ const response = await apiClient.get