initialize Vite + React + TypeScript project with Tailwind CSS and API service setup
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+14
@@ -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'
|
||||
},
|
||||
};
|
||||
Executable
+52
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+45
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+90
@@ -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 }
|
||||
Executable
+66
@@ -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;
|
||||
}
|
||||
Executable
+32
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user