This commit is contained in:
Dmitry 2026-01-07 00:10:50 +03:00
parent 13e2d46d6f
commit afe5879b62
3 changed files with 129 additions and 1 deletions

1
.gitignore vendored
View file

@ -31,7 +31,6 @@ develop-eggs/
downloads/ downloads/
eggs/ eggs/
.eggs/ .eggs/
lib/
lib64/ lib64/
parts/ parts/
sdist/ sdist/

View file

@ -0,0 +1,123 @@
import type { AxiosError } from 'axios'
export interface ApiErrorResponse {
error?: string
message?: string
details?: string
field?: string
code?: string
}
/**
* Formats an API error into a user-friendly message
*/
export function formatApiError(error: unknown): string {
const axiosError = error as AxiosError<ApiErrorResponse>
// Network error (no response from server)
if (!axiosError.response) {
if (axiosError.code === 'ECONNABORTED') {
return 'Request timeout: The server took too long to respond. Please check your connection and try again.'
}
if (axiosError.message?.includes('Network Error')) {
return 'Network error: Unable to connect to the server. Please check your internet connection.'
}
return 'Connection error: Unable to reach the server. Please check your connection and try again.'
}
const status = axiosError.response.status
const data = axiosError.response.data
// Get error message from response
const message = data?.message || data?.error || 'An unknown error occurred'
// Add more context based on status code
switch (status) {
case 400:
if (data?.field) {
return `Validation error: ${message} (field: ${data.field})`
}
if (data?.details) {
return `Bad request: ${message}. ${data.details}`
}
return `Invalid request: ${message}`
case 401:
return 'Authentication required: Please log in again.'
case 403:
return 'Access denied: You do not have permission to perform this action.'
case 404:
// Make 404 errors more specific based on the endpoint
{
const url = axiosError.config?.url || ''
if (url.includes('/cards/')) {
return 'Card not found: The requested card does not exist or has been deleted.'
}
if (url.includes('/packs/')) {
return 'Pack not found: The requested pack does not exist or has been deleted.'
}
if (url.includes('/users/')) {
return 'User not found: The requested user does not exist.'
}
return `Resource not found: ${message}`
}
case 409:
return `Conflict: ${message}. The resource may already exist or be in use.`
case 422:
if (data?.details) {
return `Validation failed: ${message}. ${data.details}`
}
return `Validation error: ${message}`
case 429:
return 'Too many requests: Please wait a moment and try again.'
case 500:
return `Server error: ${message}. Please try again later or contact support if the problem persists.`
case 502:
return 'Bad gateway: The server is temporarily unavailable. Please try again later.'
case 503:
return 'Service unavailable: The server is temporarily down for maintenance. Please try again later.'
case 504:
return 'Gateway timeout: The server took too long to respond. Please try again.'
default:
return `${message} (Error ${status})`
}
}
/**
* Gets a detailed error message with additional context for specific operations
*/
export function getDetailedErrorMessage(
error: unknown,
operation: string,
resource?: string
): string {
const baseMessage = formatApiError(error)
const axiosError = error as AxiosError<ApiErrorResponse>
// Add operation context
let contextMessage = baseMessage
if (operation && resource) {
contextMessage = `Failed to ${operation} ${resource}: ${baseMessage}`
} else if (operation) {
contextMessage = `Failed to ${operation}: ${baseMessage}`
}
// Add field-specific errors if available
if (axiosError.response?.data?.field) {
const field = axiosError.response.data.field
contextMessage += ` (Field: ${field})`
}
return contextMessage
}

6
admin/src/lib/utils.ts Normal file
View file

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}