Add new Frontend API folder
This commit is contained in:
50
frontend/src/api/utils/error.ts
Normal file
50
frontend/src/api/utils/error.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// frontend/src/api/utils/error.ts
|
||||
/**
|
||||
* Error Handling Utilities
|
||||
*/
|
||||
|
||||
import type { ApiError } from '../types';
|
||||
|
||||
export class ApiErrorHandler {
|
||||
static formatError(error: any): string {
|
||||
if (error?.response?.data) {
|
||||
const errorData = error.response.data as ApiError;
|
||||
return errorData.detail || errorData.message || 'An error occurred';
|
||||
}
|
||||
|
||||
if (error?.message) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return 'An unexpected error occurred';
|
||||
}
|
||||
|
||||
static getErrorCode(error: any): string | undefined {
|
||||
return error?.response?.data?.error_code;
|
||||
}
|
||||
|
||||
static isNetworkError(error: any): boolean {
|
||||
return !error?.response && error?.message?.includes('Network');
|
||||
}
|
||||
|
||||
static isAuthError(error: any): boolean {
|
||||
const status = error?.response?.status;
|
||||
return status === 401 || status === 403;
|
||||
}
|
||||
|
||||
static isValidationError(error: any): boolean {
|
||||
return error?.response?.status === 422;
|
||||
}
|
||||
|
||||
static isServerError(error: any): boolean {
|
||||
const status = error?.response?.status;
|
||||
return status >= 500;
|
||||
}
|
||||
|
||||
static shouldRetry(error: any): boolean {
|
||||
if (this.isNetworkError(error)) return true;
|
||||
if (this.isServerError(error)) return true;
|
||||
const status = error?.response?.status;
|
||||
return status === 408 || status === 429; // Timeout or Rate limited
|
||||
}
|
||||
}
|
||||
9
frontend/src/api/utils/index.ts
Normal file
9
frontend/src/api/utils/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// frontend/src/api/utils/index.ts
|
||||
/**
|
||||
* Main Utils Export
|
||||
*/
|
||||
|
||||
export { ApiErrorHandler } from './error';
|
||||
export { ResponseProcessor } from './response';
|
||||
export { RequestValidator } from './validation';
|
||||
export { DataTransformer } from './transform';
|
||||
34
frontend/src/api/utils/response.ts
Normal file
34
frontend/src/api/utils/response.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// frontend/src/api/utils/response.ts
|
||||
/**
|
||||
* Response Processing Utilities
|
||||
*/
|
||||
|
||||
import type { ApiResponse, PaginatedResponse } from '../types';
|
||||
|
||||
export class ResponseProcessor {
|
||||
static extractData<T>(response: ApiResponse<T>): T {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
static extractPaginatedData<T>(response: PaginatedResponse<T>): {
|
||||
data: T[];
|
||||
pagination: PaginatedResponse<T>['pagination'];
|
||||
} {
|
||||
return {
|
||||
data: response.data,
|
||||
pagination: response.pagination,
|
||||
};
|
||||
}
|
||||
|
||||
static isSuccessResponse(response: ApiResponse): boolean {
|
||||
return response.status === 'success' || response.status === 'ok';
|
||||
}
|
||||
|
||||
static extractMessage(response: ApiResponse): string | undefined {
|
||||
return response.message;
|
||||
}
|
||||
|
||||
static extractMeta(response: ApiResponse): any {
|
||||
return response.meta;
|
||||
}
|
||||
}
|
||||
70
frontend/src/api/utils/transform.ts
Normal file
70
frontend/src/api/utils/transform.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// frontend/src/api/utils/transform.ts
|
||||
/**
|
||||
* Data Transformation Utilities
|
||||
*/
|
||||
|
||||
export class DataTransformer {
|
||||
static formatDate(date: string | Date): string {
|
||||
const d = new Date(date);
|
||||
return d.toLocaleDateString();
|
||||
}
|
||||
|
||||
static formatDateTime(date: string | Date): string {
|
||||
const d = new Date(date);
|
||||
return d.toLocaleString();
|
||||
}
|
||||
|
||||
static formatCurrency(amount: number, currency = 'EUR'): string {
|
||||
return new Intl.NumberFormat('es-ES', {
|
||||
style: 'currency',
|
||||
currency,
|
||||
}).format(amount);
|
||||
}
|
||||
|
||||
static formatPercentage(value: number, decimals = 1): string {
|
||||
return `${(value * 100).toFixed(decimals)}%`;
|
||||
}
|
||||
|
||||
static formatFileSize(bytes: number): string {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return `${Math.round(bytes / Math.pow(1024, i) * 100) / 100} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
static slugify(text: string): string {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^\w ]+/g, '')
|
||||
.replace(/ +/g, '-');
|
||||
}
|
||||
|
||||
static truncate(text: string, length: number): string {
|
||||
if (text.length <= length) return text;
|
||||
return `${text.substring(0, length)}...`;
|
||||
}
|
||||
|
||||
static camelToKebab(str: string): string {
|
||||
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
|
||||
}
|
||||
|
||||
static kebabToCamel(str: string): string {
|
||||
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
||||
}
|
||||
|
||||
static deepClone<T>(obj: T): T {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
static removeEmpty(obj: Record<string, any>): Record<string, any> {
|
||||
const cleaned: Record<string, any> = {};
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
if (obj[key] !== null && obj[key] !== undefined && obj[key] !== '') {
|
||||
cleaned[key] = obj[key];
|
||||
}
|
||||
});
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
}
|
||||
72
frontend/src/api/utils/validation.ts
Normal file
72
frontend/src/api/utils/validation.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
// frontend/src/api/utils/validation.ts
|
||||
/**
|
||||
* Request Validation Utilities
|
||||
*/
|
||||
|
||||
export class RequestValidator {
|
||||
static validateEmail(email: string): boolean {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
static validatePassword(password: string): {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
} {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (password.length < 8) {
|
||||
errors.push('Password must be at least 8 characters long');
|
||||
}
|
||||
|
||||
if (!/(?=.*[a-z])/.test(password)) {
|
||||
errors.push('Password must contain at least one lowercase letter');
|
||||
}
|
||||
|
||||
if (!/(?=.*[A-Z])/.test(password)) {
|
||||
errors.push('Password must contain at least one uppercase letter');
|
||||
}
|
||||
|
||||
if (!/(?=.*\d)/.test(password)) {
|
||||
errors.push('Password must contain at least one number');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
static validateFile(file: File, allowedTypes: string[], maxSize: number): {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
} {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
errors.push(`File type ${file.type} is not allowed`);
|
||||
}
|
||||
|
||||
if (file.size > maxSize) {
|
||||
errors.push(`File size exceeds maximum of ${maxSize} bytes`);
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
static validatePhoneNumber(phone: string): boolean {
|
||||
// Basic international phone number validation
|
||||
const phoneRegex = /^\+?[1-9]\d{1,14}$/;
|
||||
return phoneRegex.test(phone.replace(/\s/g, ''));
|
||||
}
|
||||
|
||||
static validateRequired(value: any, fieldName: string): string | null {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return `${fieldName} is required`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user