initialize Vite + React + TypeScript project with Tailwind CSS and API service setup

This commit is contained in:
2025-02-01 00:33:24 -03:00
commit f133e18302
31 changed files with 5317 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
import { ApiService } from './index';
import { Pet } from '../../types/Pet';
import { PetCreationRequest } from '../../types/PetCreationRequest';
import { PetUpdateActionRequest } from '../../types/PetUpdateActionRequest';
// Get API service instance
const api = ApiService.getInstance();
export async function fetchPets(): Promise<Pet[]> {
try {
const response = await api.get<Pet[]>('/api/v1/pet');
return response.data;
} catch (error: any) {
console.error('Failed to fetch pets:', error.message);
throw error;
}
}
export async function createPet(data: PetCreationRequest): Promise<Pet> {
try {
const response = await api.post<Pet>('/api/v1/pet', data);
return response.data;
} catch (error: any) {
console.error('Failed to create pet:', error.message);
throw error;
}
}
export async function updatePetAction(petId: string, data: PetUpdateActionRequest): Promise<Pet> {
try {
const response = await api.put<Pet>(`/api/v1/pet/${petId}/action`, data);
return response.data;
} catch (error: any) {
console.error('Failed to update pet action:', error.message);
throw error;
}
}
+14
View File
@@ -0,0 +1,14 @@
import { ApiConfig } from './types';
// API configuration
export const apiConfig: ApiConfig = {
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5278',
timeout: 10000, // 10 seconds
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
},
};
+52
View File
@@ -0,0 +1,52 @@
import { AxiosError } from 'axios';
import { ApiError } from './types';
export class ApiErrorHandler {
static handle(error: AxiosError): ApiError {
if (error.response) {
// Server responded with a status code outside of 2xx range
return {
message: error.response.data?.message || 'Server error occurred',
code: 'SERVER_ERROR',
status: error.response.status,
details: error.response.data,
};
} else if (error.request) {
// Request was made but no response received
return {
message: 'No response received from server',
code: 'NETWORK_ERROR',
status: 0,
details: {
request: error.request,
},
};
} else {
// Error occurred while setting up the request
return {
message: error.message || 'An error occurred while making the request',
code: 'REQUEST_SETUP_ERROR',
status: 0,
details: {
config: error.config,
},
};
}
}
static isNetworkError(error: ApiError): boolean {
return error.code === 'NETWORK_ERROR';
}
static isTimeoutError(error: ApiError): boolean {
return error.message.toLowerCase().includes('timeout');
}
static isServerError(error: ApiError): boolean {
return error.status >= 500;
}
static isClientError(error: ApiError): boolean {
return error.status >= 400 && error.status < 500;
}
}
+45
View File
@@ -0,0 +1,45 @@
// Example usage of the API service
import { ApiService } from './index';
import { Pet } from '../../types/Pet';
// Get API service instance
const api = ApiService.getInstance();
// Example functions using the API service
export async function getPet(id: string) {
try {
const response = await api.get<Pet>(`/pets/${id}`);
return response.data;
} catch (error: any) {
if (api.isNetworkError(error)) {
console.error('Network error occurred');
} else if (api.isTimeoutError(error)) {
console.error('Request timed out');
} else if (api.isServerError(error)) {
console.error('Server error occurred');
}
throw error;
}
}
export async function updatePet(id: string, data: Partial<Pet>) {
try {
const response = await api.put<Pet>(`/pets/${id}`, data);
return response.data;
} catch (error: any) {
console.error('Failed to update pet:', error.message);
throw error;
}
}
export async function gatherResources(id: string, resourceType: string) {
try {
const response = await api.post<{ success: boolean }>(`/pets/${id}/gather`, {
resourceType,
});
return response.data;
} catch (error: any) {
console.error('Failed to gather resources:', error.message);
throw error;
}
}
+90
View File
@@ -0,0 +1,90 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { apiConfig } from './config';
import { setupInterceptors } from './interceptors';
import { ApiErrorHandler } from './error';
import { ApiResponse, RequestOptions, ApiError } from './types';
class ApiService {
private static instance: ApiService;
private axios: AxiosInstance;
private constructor() {
// Create axios instance with base configuration
this.axios = axios.create(apiConfig);
// Setup interceptors
setupInterceptors(this.axios);
}
public static getInstance(): ApiService {
if (!ApiService.instance) {
ApiService.instance = new ApiService();
}
return ApiService.instance;
}
private async request<T>(
method: string,
url: string,
data?: any,
options: RequestOptions = {}
): Promise<ApiResponse<T>> {
try {
const config: AxiosRequestConfig = {
method,
url,
...options,
};
if (data) {
config.data = data;
}
const response = await this.axios.request<T>(config);
return {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers as Record<string, string>,
};
} catch (error) {
throw ApiErrorHandler.handle(error as any);
}
}
public async get<T>(url: string, options?: RequestOptions): Promise<ApiResponse<T>> {
return this.request<T>('GET', url, undefined, options);
}
public async post<T>(url: string, data?: any, options?: RequestOptions): Promise<ApiResponse<T>> {
return this.request<T>('POST', url, data, options);
}
public async put<T>(url: string, data?: any, options?: RequestOptions): Promise<ApiResponse<T>> {
return this.request<T>('PUT', url, data, options);
}
public async delete<T>(url: string, options?: RequestOptions): Promise<ApiResponse<T>> {
return this.request<T>('DELETE', url, undefined, options);
}
// Utility method to check if an error is a specific type
public isNetworkError(error: ApiError): boolean {
return ApiErrorHandler.isNetworkError(error);
}
public isTimeoutError(error: ApiError): boolean {
return ApiErrorHandler.isTimeoutError(error);
}
public isServerError(error: ApiError): boolean {
return ApiErrorHandler.isServerError(error);
}
public isClientError(error: ApiError): boolean {
return ApiErrorHandler.isClientError(error);
}
}
export { ApiService }
+66
View File
@@ -0,0 +1,66 @@
import { AxiosInstance } from 'axios';
import { ApiError } from './types';
export function setupInterceptors(axiosInstance: AxiosInstance) {
// Request interceptor
axiosInstance.interceptors.request.use(
(config) => {
// Get token from storage
const token = localStorage.getItem('auth_token');
// Add authorization header if token exists
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Log request (development only)
if (import.meta.env.DEV) {
console.log(`🚀 [API] ${config.method?.toUpperCase()} ${config.url}`, {
data: config.data,
params: config.params,
});
}
return config;
},
(error) => {
// Log request error (development only)
if (import.meta.env.DEV) {
console.error('❌ [API] Request Error:', error);
}
return Promise.reject(error);
}
);
// Response interceptor
axiosInstance.interceptors.response.use(
(response) => {
// Log response (development only)
if (import.meta.env.DEV) {
console.log(`✅ [API] Response:`, {
status: response.status,
data: response.data,
});
}
return response;
},
(error) => {
// Log response error (development only)
if (import.meta.env.DEV) {
console.error('❌ [API] Response Error:', error.response || error);
}
// Handle authentication errors
if (error.response?.status === 401) {
// Clear token and redirect to login
localStorage.removeItem('auth_token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
return axiosInstance;
}
+32
View File
@@ -0,0 +1,32 @@
// HTTP Methods supported by the API
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
// Standard API Response interface
export interface ApiResponse<T = any> {
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
}
// Standard API Error interface
export interface ApiError {
message: string;
code: string;
status: number;
details?: Record<string, any>;
}
// API Configuration interface
export interface ApiConfig {
baseURL: string;
timeout: number;
headers?: Record<string, string>;
}
// Request options interface
export interface RequestOptions {
headers?: Record<string, string>;
params?: Record<string, any>;
timeout?: number;
}