537 lines
12 KiB
Markdown
537 lines
12 KiB
Markdown
# API Integration
|
|
|
|
## RTK Query Setup
|
|
|
|
```typescript
|
|
// src/services/api.ts
|
|
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
|
import Config from 'react-native-config';
|
|
import { RootState } from '@redux/store';
|
|
|
|
export const api = createApi({
|
|
reducerPath: 'api',
|
|
baseQuery: fetchBaseQuery({
|
|
baseUrl: Config.API_BASE_URL,
|
|
prepareHeaders: (headers, { getState }) => {
|
|
const token = (getState() as RootState).auth.token;
|
|
if (token) {
|
|
headers.set('authorization', `Bearer ${token}`);
|
|
}
|
|
headers.set('Content-Type', 'application/json');
|
|
return headers;
|
|
},
|
|
}),
|
|
tagTypes: ['User', 'NGO', 'Donation', 'Request'],
|
|
endpoints: () => ({}),
|
|
});
|
|
```
|
|
|
|
## Auth API
|
|
|
|
```typescript
|
|
// src/services/authAPI.ts
|
|
import { api } from './api';
|
|
|
|
interface LoginRequest {
|
|
email: string;
|
|
password: string;
|
|
}
|
|
|
|
interface LoginResponse {
|
|
user: {
|
|
id: string;
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
};
|
|
token: string;
|
|
refreshToken: string;
|
|
}
|
|
|
|
interface RegisterRequest {
|
|
firstName: string;
|
|
lastName: string;
|
|
email: string;
|
|
password: string;
|
|
phone: string;
|
|
}
|
|
|
|
export const authAPI = api.injectEndpoints({
|
|
endpoints: (builder) => ({
|
|
login: builder.mutation<LoginResponse, LoginRequest>({
|
|
query: (credentials) => ({
|
|
url: '/auth/login',
|
|
method: 'POST',
|
|
body: credentials,
|
|
}),
|
|
}),
|
|
register: builder.mutation<LoginResponse, RegisterRequest>({
|
|
query: (userData) => ({
|
|
url: '/auth/register',
|
|
method: 'POST',
|
|
body: userData,
|
|
}),
|
|
}),
|
|
refreshToken: builder.mutation<{ token: string }, { refreshToken: string }>(
|
|
{
|
|
query: ({ refreshToken }) => ({
|
|
url: '/auth/refresh',
|
|
method: 'POST',
|
|
body: { refreshToken },
|
|
}),
|
|
},
|
|
),
|
|
logout: builder.mutation<void, void>({
|
|
query: () => ({
|
|
url: '/auth/logout',
|
|
method: 'POST',
|
|
}),
|
|
}),
|
|
forgotPassword: builder.mutation<void, { email: string }>({
|
|
query: ({ email }) => ({
|
|
url: '/auth/forgot-password',
|
|
method: 'POST',
|
|
body: { email },
|
|
}),
|
|
}),
|
|
verifyOTP: builder.mutation<void, { email: string; otp: string }>({
|
|
query: ({ email, otp }) => ({
|
|
url: '/auth/verify-otp',
|
|
method: 'POST',
|
|
body: { email, otp },
|
|
}),
|
|
}),
|
|
}),
|
|
});
|
|
|
|
export const {
|
|
useLoginMutation,
|
|
useRegisterMutation,
|
|
useRefreshTokenMutation,
|
|
useLogoutMutation,
|
|
useForgotPasswordMutation,
|
|
useVerifyOTPMutation,
|
|
} = authAPI;
|
|
```
|
|
|
|
## User API
|
|
|
|
```typescript
|
|
// src/services/userAPI.ts
|
|
import { api } from './api';
|
|
|
|
interface User {
|
|
id: string;
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
phone?: string;
|
|
avatar?: string;
|
|
isNGO: boolean;
|
|
ngoDetails?: NGODetails;
|
|
}
|
|
|
|
interface NGODetails {
|
|
ngoName: string;
|
|
registrationNumber: string;
|
|
address: string;
|
|
description: string;
|
|
causes: string[];
|
|
}
|
|
|
|
interface UpdateProfileRequest {
|
|
firstName?: string;
|
|
lastName?: string;
|
|
phone?: string;
|
|
avatar?: string;
|
|
}
|
|
|
|
export const userAPI = api.injectEndpoints({
|
|
endpoints: (builder) => ({
|
|
getProfile: builder.query<User, void>({
|
|
query: () => '/user/profile',
|
|
providesTags: ['User'],
|
|
}),
|
|
updateProfile: builder.mutation<User, UpdateProfileRequest>({
|
|
query: (updates) => ({
|
|
url: '/user/profile',
|
|
method: 'PUT',
|
|
body: updates,
|
|
}),
|
|
invalidatesTags: ['User'],
|
|
}),
|
|
uploadAvatar: builder.mutation<{ avatarUrl: string }, FormData>({
|
|
query: (formData) => ({
|
|
url: '/user/avatar',
|
|
method: 'POST',
|
|
body: formData,
|
|
}),
|
|
invalidatesTags: ['User'],
|
|
}),
|
|
deleteAccount: builder.mutation<void, void>({
|
|
query: () => ({
|
|
url: '/user/account',
|
|
method: 'DELETE',
|
|
}),
|
|
}),
|
|
}),
|
|
});
|
|
|
|
export const {
|
|
useGetProfileQuery,
|
|
useUpdateProfileMutation,
|
|
useUploadAvatarMutation,
|
|
useDeleteAccountMutation,
|
|
} = userAPI;
|
|
```
|
|
|
|
## NGO API
|
|
|
|
```typescript
|
|
// src/services/ngoAPI.ts
|
|
import { api } from './api';
|
|
|
|
interface NGO {
|
|
id: string;
|
|
ngoName: string;
|
|
description: string;
|
|
registrationNumber: string;
|
|
address: string;
|
|
causes: string[];
|
|
coverImage?: string;
|
|
isVerified: boolean;
|
|
totalDonations: number;
|
|
activeRequests: number;
|
|
}
|
|
|
|
interface CreateNGORequest {
|
|
ngoName: string;
|
|
description: string;
|
|
registrationNumber: string;
|
|
address: string;
|
|
causes: string[];
|
|
documents: string[];
|
|
}
|
|
|
|
export const ngoAPI = api.injectEndpoints({
|
|
endpoints: (builder) => ({
|
|
getNGOList: builder.query<
|
|
NGO[],
|
|
{ page?: number; limit?: number; search?: string }
|
|
>({
|
|
query: ({ page = 1, limit = 10, search }) => ({
|
|
url: '/ngo',
|
|
params: { page, limit, search },
|
|
}),
|
|
providesTags: ['NGO'],
|
|
}),
|
|
getNGOById: builder.query<NGO, string>({
|
|
query: (id) => `/ngo/${id}`,
|
|
providesTags: ['NGO'],
|
|
}),
|
|
createNGO: builder.mutation<NGO, CreateNGORequest>({
|
|
query: (ngoData) => ({
|
|
url: '/ngo',
|
|
method: 'POST',
|
|
body: ngoData,
|
|
}),
|
|
invalidatesTags: ['NGO', 'User'],
|
|
}),
|
|
updateNGO: builder.mutation<
|
|
NGO,
|
|
{ id: string; updates: Partial<CreateNGORequest> }
|
|
>({
|
|
query: ({ id, updates }) => ({
|
|
url: `/ngo/${id}`,
|
|
method: 'PUT',
|
|
body: updates,
|
|
}),
|
|
invalidatesTags: ['NGO'],
|
|
}),
|
|
getFavoriteNGOs: builder.query<NGO[], void>({
|
|
query: () => '/ngo/favorites',
|
|
providesTags: ['NGO'],
|
|
}),
|
|
toggleFavoriteNGO: builder.mutation<void, string>({
|
|
query: (ngoId) => ({
|
|
url: `/ngo/${ngoId}/favorite`,
|
|
method: 'POST',
|
|
}),
|
|
invalidatesTags: ['NGO'],
|
|
}),
|
|
}),
|
|
});
|
|
|
|
export const {
|
|
useGetNGOListQuery,
|
|
useGetNGOByIdQuery,
|
|
useCreateNGOMutation,
|
|
useUpdateNGOMutation,
|
|
useGetFavoriteNGOsQuery,
|
|
useToggleFavoriteNGOMutation,
|
|
} = ngoAPI;
|
|
```
|
|
|
|
## Request API
|
|
|
|
```typescript
|
|
// src/services/requestAPI.ts
|
|
import { api } from './api';
|
|
|
|
interface FundraisingRequest {
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
category: string;
|
|
targetAmount: number;
|
|
currentAmount: number;
|
|
images: string[];
|
|
location: {
|
|
latitude: number;
|
|
longitude: number;
|
|
address: string;
|
|
};
|
|
status: 'active' | 'completed' | 'cancelled';
|
|
createdBy: string;
|
|
ngoId?: string;
|
|
isUrgent: boolean;
|
|
endDate: string;
|
|
}
|
|
|
|
interface CreateRequestData {
|
|
title: string;
|
|
description: string;
|
|
category: string;
|
|
targetAmount: number;
|
|
images: string[];
|
|
location: {
|
|
latitude: number;
|
|
longitude: number;
|
|
address: string;
|
|
};
|
|
isUrgent: boolean;
|
|
endDate: string;
|
|
}
|
|
|
|
export const requestAPI = api.injectEndpoints({
|
|
endpoints: (builder) => ({
|
|
getRequests: builder.query<
|
|
FundraisingRequest[],
|
|
{
|
|
page?: number;
|
|
limit?: number;
|
|
category?: string;
|
|
location?: string;
|
|
urgent?: boolean;
|
|
}
|
|
>({
|
|
query: (params) => ({
|
|
url: '/requests',
|
|
params,
|
|
}),
|
|
providesTags: ['Request'],
|
|
}),
|
|
getRequestById: builder.query<FundraisingRequest, string>({
|
|
query: (id) => `/requests/${id}`,
|
|
providesTags: ['Request'],
|
|
}),
|
|
createRequest: builder.mutation<FundraisingRequest, CreateRequestData>({
|
|
query: (requestData) => ({
|
|
url: '/requests',
|
|
method: 'POST',
|
|
body: requestData,
|
|
}),
|
|
invalidatesTags: ['Request'],
|
|
}),
|
|
updateRequest: builder.mutation<
|
|
FundraisingRequest,
|
|
{
|
|
id: string;
|
|
updates: Partial<CreateRequestData>;
|
|
}
|
|
>({
|
|
query: ({ id, updates }) => ({
|
|
url: `/requests/${id}`,
|
|
method: 'PUT',
|
|
body: updates,
|
|
}),
|
|
invalidatesTags: ['Request'],
|
|
}),
|
|
deleteRequest: builder.mutation<void, string>({
|
|
query: (id) => ({
|
|
url: `/requests/${id}`,
|
|
method: 'DELETE',
|
|
}),
|
|
invalidatesTags: ['Request'],
|
|
}),
|
|
getMyRequests: builder.query<FundraisingRequest[], void>({
|
|
query: () => '/requests/my-requests',
|
|
providesTags: ['Request'],
|
|
}),
|
|
}),
|
|
});
|
|
|
|
export const {
|
|
useGetRequestsQuery,
|
|
useGetRequestByIdQuery,
|
|
useCreateRequestMutation,
|
|
useUpdateRequestMutation,
|
|
useDeleteRequestMutation,
|
|
useGetMyRequestsQuery,
|
|
} = requestAPI;
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
```typescript
|
|
// src/utils/errorHandler.ts
|
|
import { SerializedError } from '@reduxjs/toolkit';
|
|
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
|
|
|
|
export interface APIError {
|
|
message: string;
|
|
status?: number;
|
|
code?: string;
|
|
}
|
|
|
|
export const handleAPIError = (
|
|
error: FetchBaseQueryError | SerializedError,
|
|
): APIError => {
|
|
if ('status' in error) {
|
|
// FetchBaseQueryError
|
|
const status = error.status;
|
|
const data = error.data as any;
|
|
|
|
switch (status) {
|
|
case 400:
|
|
return {
|
|
message: data?.message || 'Bad request',
|
|
status: 400,
|
|
code: 'BAD_REQUEST',
|
|
};
|
|
case 401:
|
|
return {
|
|
message: 'Authentication required',
|
|
status: 401,
|
|
code: 'UNAUTHORIZED',
|
|
};
|
|
case 403:
|
|
return {
|
|
message: 'Access denied',
|
|
status: 403,
|
|
code: 'FORBIDDEN',
|
|
};
|
|
case 404:
|
|
return {
|
|
message: 'Resource not found',
|
|
status: 404,
|
|
code: 'NOT_FOUND',
|
|
};
|
|
case 500:
|
|
return {
|
|
message: 'Server error. Please try again later.',
|
|
status: 500,
|
|
code: 'SERVER_ERROR',
|
|
};
|
|
default:
|
|
return {
|
|
message: data?.message || 'An unexpected error occurred',
|
|
status: typeof status === 'number' ? status : undefined,
|
|
code: 'UNKNOWN_ERROR',
|
|
};
|
|
}
|
|
} else {
|
|
// SerializedError
|
|
return {
|
|
message: error.message || 'Network error',
|
|
code: 'NETWORK_ERROR',
|
|
};
|
|
}
|
|
};
|
|
```
|
|
|
|
## API Hooks
|
|
|
|
```typescript
|
|
// src/hooks/useAPI.ts
|
|
import { useState, useCallback } from 'react';
|
|
import { handleAPIError, APIError } from '@utils/errorHandler';
|
|
|
|
interface UseAPIState<T> {
|
|
data: T | null;
|
|
loading: boolean;
|
|
error: APIError | null;
|
|
}
|
|
|
|
export const useAPI = <T>() => {
|
|
const [state, setState] = useState<UseAPIState<T>>({
|
|
data: null,
|
|
loading: false,
|
|
error: null,
|
|
});
|
|
|
|
const execute = useCallback(async (apiCall: () => Promise<T>) => {
|
|
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
|
|
try {
|
|
const data = await apiCall();
|
|
setState({ data, loading: false, error: null });
|
|
return data;
|
|
} catch (error: any) {
|
|
const apiError = handleAPIError(error);
|
|
setState((prev) => ({ ...prev, loading: false, error: apiError }));
|
|
throw apiError;
|
|
}
|
|
}, []);
|
|
|
|
const reset = useCallback(() => {
|
|
setState({ data: null, loading: false, error: null });
|
|
}, []);
|
|
|
|
return {
|
|
...state,
|
|
execute,
|
|
reset,
|
|
};
|
|
};
|
|
```
|
|
|
|
## Store Integration
|
|
|
|
```typescript
|
|
// src/redux/store/index.ts (updated)
|
|
import { configureStore } from '@reduxjs/toolkit';
|
|
import { persistStore, persistReducer } from 'redux-persist';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import { combineReducers } from '@reduxjs/toolkit';
|
|
import { api } from '@services/api';
|
|
import authSlice from '../slices/authSlice';
|
|
|
|
const persistConfig = {
|
|
key: 'root',
|
|
storage: AsyncStorage,
|
|
whitelist: ['auth'],
|
|
};
|
|
|
|
const rootReducer = combineReducers({
|
|
auth: authSlice,
|
|
[api.reducerPath]: api.reducer,
|
|
});
|
|
|
|
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
|
|
|
export const store = configureStore({
|
|
reducer: persistedReducer,
|
|
middleware: (getDefaultMiddleware) =>
|
|
getDefaultMiddleware({
|
|
serializableCheck: {
|
|
ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
|
|
},
|
|
}).concat(api.middleware),
|
|
});
|
|
|
|
export const persistor = persistStore(store);
|
|
export type RootState = ReturnType<typeof store.getState>;
|
|
export type AppDispatch = typeof store.dispatch;
|
|
```
|