This commit is contained in:
Dmitry 2025-12-03 04:32:20 +03:00
parent bfa3d44146
commit 1ed7334995
6 changed files with 1324 additions and 18 deletions

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,8 @@
"dev": "vite", "dev": "vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview" "preview": "vite preview",
"test": "vitest"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
@ -39,6 +40,8 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.5", "@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
@ -47,8 +50,10 @@
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24", "eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0", "globals": "^16.5.0",
"jsdom": "^27.0.1",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.46.4", "typescript-eslint": "^8.46.4",
"vite": "^7.2.4" "vite": "^7.2.4",
"vitest": "^3.2.4"
} }
} }

View file

@ -1,16 +1,16 @@
import { apiClient } from './client' import { adminApiClient } from './client'
import type { AuthResponse } from '@/types/models' import type { AuthResponse } from '@/types/models'
export const authApi = { export const authApi = {
// Request authentication code to be sent to all admin Telegram accounts // Request authentication code to be sent to all admin Telegram accounts
requestCode: async (): Promise<{ success: boolean; message: string }> => { requestCode: async (): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.post('/api/v2/admin/auth/request-code') const response = await adminApiClient.post('/api/v2/admin/auth/request-code')
return response.data return response.data
}, },
// Verify authentication code // Verify authentication code
verifyCode: async (code: string): Promise<AuthResponse> => { verifyCode: async (code: string): Promise<AuthResponse> => {
const response = await apiClient.post('/api/v2/admin/auth/verify-code', { const response = await adminApiClient.post('/api/v2/admin/auth/verify-code', {
code, code,
}) })
return response.data return response.data
@ -18,7 +18,7 @@ export const authApi = {
// Get current admin info (for token validation) // Get current admin info (for token validation)
getCurrentUser: async (): Promise<any> => { getCurrentUser: async (): Promise<any> => {
const response = await apiClient.get('/api/v2/admin/auth/me') const response = await adminApiClient.get('/api/v2/admin/auth/me')
return response.data return response.data
}, },
} }

View file

@ -2,6 +2,10 @@ import axios from 'axios'
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://api.mnemo-cards.online' const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://api.mnemo-cards.online'
// Admin API always uses production backend (admin auth requires production server)
const ADMIN_API_BASE_URL = 'https://api.mnemo-cards.online'
// Regular API client for non-admin endpoints
export const apiClient = axios.create({ export const apiClient = axios.create({
baseURL: API_BASE_URL, baseURL: API_BASE_URL,
headers: { headers: {
@ -9,23 +13,35 @@ export const apiClient = axios.create({
}, },
}) })
// Admin API client - always uses production backend
export const adminApiClient = axios.create({
baseURL: ADMIN_API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
})
// Request interceptor to add auth token // Request interceptor to add auth token
apiClient.interceptors.request.use((config) => { const addAuthToken = (config: any) => {
const token = localStorage.getItem('admin_token') const token = localStorage.getItem('admin_token')
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}` config.headers.Authorization = `Bearer ${token}`
} }
return config return config
}) }
// Response interceptor for error handling // Response interceptor for error handling
apiClient.interceptors.response.use( const handleAuthError = (error: any) => {
(response) => response,
(error) => {
if (error.response?.status === 401) { if (error.response?.status === 401) {
localStorage.removeItem('admin_token') localStorage.removeItem('admin_token')
window.location.href = '/login' window.location.href = '/login'
} }
return Promise.reject(error) return Promise.reject(error)
} }
)
// Apply interceptors to both clients
apiClient.interceptors.request.use(addAuthToken)
apiClient.interceptors.response.use((response) => response, handleAuthError)
adminApiClient.interceptors.request.use(addAuthToken)
adminApiClient.interceptors.response.use((response) => response, handleAuthError)

View file

@ -1,3 +1,4 @@
/// <reference types="vitest" />
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import path from 'path' import path from 'path'
@ -10,4 +11,9 @@ export default defineConfig({
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
}, },
}, },
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
},
}) })

Binary file not shown.