+
Added on: 28 May 1988
diff --git a/src/pages/LocationDetail/types.ts b/src/pages/LocationDetail/types.ts
index b65e628..f3b4bf0 100755
--- a/src/pages/LocationDetail/types.ts
+++ b/src/pages/LocationDetail/types.ts
@@ -2,7 +2,7 @@ import { NullValueRes } from "../../types/common"
import { SlideImage } from "yet-another-react-lightbox"
export interface ILocationDetail {
- id: Number,
+ id: number,
name: String,
address: String,
regency_name: String,
@@ -10,11 +10,11 @@ export interface ILocationDetail {
region_name: String,
google_maps_link: String,
thumbnail: string | null,
- submitted_by: Number,
- critic_score: Number,
- critic_count: Number,
- user_score: Number,
- user_count: Number
+ submitted_by: number,
+ critic_score: number,
+ critic_count: number,
+ user_score: number,
+ user_count: number
}
export function emptyLocationDetail(): ILocationDetail {
@@ -64,14 +64,14 @@ export function EmptyLocationDetailResponse(): LocationDetailResponse {
}
export interface LocationImage extends SlideImage {
- id: Number,
+ id: number,
src: string,
created_at: String,
uploaded_by: String
}
export interface LocationResponse {
- total_image: Number,
+ total_image: number,
images: Array
}
@@ -83,13 +83,13 @@ export function emptyLocationResponse(): LocationResponse {
}
export type CurrentUserLocationReviews = {
- id: Number,
+ id: number,
comments: string,
is_from_critic: boolean,
is_hided: boolean,
- location_id: Number,
- score: Number,
- submitted_by: Number,
+ location_id: number,
+ score: number,
+ submitted_by: number,
created_at: NullValueRes<"Time", string>,
updated_at: NullValueRes<"Time", string>,
}
\ No newline at end of file
diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx
index bdeb53c..eba2f1a 100755
--- a/src/pages/Login/index.tsx
+++ b/src/pages/Login/index.tsx
@@ -58,6 +58,11 @@ function Login() {
})
if (res.error) {
+ if(res.error.response.status == 409) {
+ setErrorMsg([{ field: 'username', msg: 'Username Already exist' }])
+ return;
+ }
+ console.log(res.error)
setErrorMsg(res.error.response.data.errors)
return;
}
@@ -96,6 +101,7 @@ function Login() {
+ {console.log(errorMsg)}
{errorMsg.map(x => (
{x.msg}
))}
diff --git a/src/services/auth.ts b/src/services/auth.ts
index f36840b..7703340 100755
--- a/src/services/auth.ts
+++ b/src/services/auth.ts
@@ -1,55 +1,76 @@
-import { AxiosError } from "axios";
-import { LOGIN_URI, SIGNUP_URI } from "../constants/api";
+import { useMutation, UseMutationOptions } from "@tanstack/react-query";
+import { LOGIN_URI, SIGNUP_URI, LOGOUT_URI } from "../constants/api";
import { client } from "./config";
import { IHttpResponse } from "../types/common";
-const initialState: IHttpResponse = {
- data: null,
- error: AxiosError
-}
-
interface IAuthentication {
- username: String
- password: String
+ username: string
+ password: string
}
+// API Functions
+const createAccount = async ({ username, password }: IAuthentication) => {
+ const response = await client({ method: 'POST', url: SIGNUP_URI, data: { username, password }, withCredentials: true })
+ return response.data
+}
+
+const login = async ({ username, password }: IAuthentication) => {
+ const response = await client({ method: 'POST', url: LOGIN_URI, data: { username, password }, withCredentials: true })
+ return response.data
+}
+
+const logout = async () => {
+ const response = await client({ method: 'POST', url: LOGOUT_URI, withCredentials: true })
+ return response.data
+}
+
+// React Query Hooks
+export const useCreateAccount = (options?: UseMutationOptions) => {
+ return useMutation({
+ mutationFn: createAccount,
+ ...options
+ })
+}
+
+export const useLogin = (options?: UseMutationOptions) => {
+ return useMutation({
+ mutationFn: login,
+ ...options
+ })
+}
+
+export const useLogout = (options?: UseMutationOptions) => {
+ return useMutation({
+ mutationFn: logout,
+ ...options
+ })
+}
+
+// Legacy service functions for backward compatibility
async function createAccountService({ username, password }: IAuthentication) {
- const newState = { ...initialState };
try {
- const response = await client({ method: 'POST', url: SIGNUP_URI, data: { username, password }, withCredentials: true })
- newState.data = response.data
- newState.error = null
- return newState
+ const data = await createAccount({ username, password })
+ return { data, error: null }
} catch (error) {
- newState.error = error
- return newState
+ return { data: null, error }
}
}
async function loginService({ username, password }: IAuthentication) {
- const newState = { ...initialState };
try {
- const response = await client({ method: 'POST', url: LOGIN_URI, data: { username, password }, withCredentials: true })
- newState.data = response.data
- newState.error = null
- return newState
+ const data = await login({ username, password })
+ return { data, error: null }
} catch (error) {
- newState.error = error
- return newState
+ return { data: null, error }
}
-
}
async function logoutService() {
- const newState = { ...initialState };
try {
- const response = await client({ method: 'POST', url: LOGIN_URI})
- newState.data = response.data
- newState.error = null
- return newState
+ const data = await logout()
+ return { data, error: null }
} catch (error) {
- newState.error = error
- return newState
+ return { data: null, error }
}
}
diff --git a/src/services/config.ts b/src/services/config.ts
index 62e757c..9fc9085 100755
--- a/src/services/config.ts
+++ b/src/services/config.ts
@@ -1,20 +1,62 @@
-import axios, { AxiosPromise, AxiosRequestConfig } from "axios";
-import {BASE_URL} from '../constants/api'
+import { BASE_URL } from '../constants/api'
-export const client = (props: AxiosRequestConfig): AxiosPromise => axios({
- method: props.method,
- baseURL: `${BASE_URL}`,
- url: props.url,
- headers: props.headers,
- data: props.data,
- ...props
-})
+interface FetchConfig extends RequestInit {
+ url: string;
+ data?: any;
+ withCredentials?: boolean;
+}
-// export const authClient = (props: AxiosRequestConfig) => axios({
-// method: props.method,
-// baseURL: `${BASE_URL}`,
-// url: props.url,
-// headers: {
-// 'Authorization':
-// }
-// })
\ No newline at end of file
+export async function client(config: FetchConfig): Promise<{ data: T; status: number; request: { status: number } }> {
+ const { url, data, withCredentials, headers, ...rest } = config;
+
+ const fullUrl = url.startsWith('http') ? url : `${BASE_URL}${url}`;
+
+ const fetchOptions: RequestInit = {
+ ...rest,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...headers,
+ },
+ credentials: withCredentials ? 'include' : 'same-origin',
+ };
+
+ // Handle body data
+ if (data) {
+ if (data instanceof FormData) {
+ // Remove Content-Type header for FormData to let browser set it with boundary
+ const headersObj = fetchOptions.headers as Record;
+ delete headersObj['Content-Type'];
+ fetchOptions.body = data;
+ } else {
+ fetchOptions.body = JSON.stringify(data);
+ }
+ }
+
+ const response = await fetch(fullUrl, fetchOptions);
+
+ let responseData: T;
+ const contentType = response.headers.get('content-type');
+
+ if (contentType && contentType.includes('application/json')) {
+ responseData = await response.json();
+ } else {
+ responseData = await response.text() as any;
+ }
+
+ if (!response.ok) {
+ const error: any = new Error('HTTP Error');
+ error.response = {
+ data: responseData,
+ status: response.status,
+ statusText: response.statusText,
+ };
+ error.status = response.status;
+ throw error;
+ }
+
+ return {
+ data: responseData,
+ status: response.status,
+ request: { status: response.status }
+ };
+}
\ No newline at end of file
diff --git a/src/services/images.ts b/src/services/images.ts
index c441250..97fa7e0 100755
--- a/src/services/images.ts
+++ b/src/services/images.ts
@@ -1,34 +1,37 @@
+import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { GetRequestPagination } from "../types/common"
import { GET_IMAGES_BY_LOCATION_URI } from "../constants/api"
import { client } from "./config"
-import statusCode from "./status-code"
-
-const initialState: any = {
- data: null,
- error: null
-}
interface getImagesReq extends GetRequestPagination {
- location_id?: Number
+ location_id?: number
}
-
-async function getImagesByLocationService({ page, page_size, location_id }: getImagesReq) {
- const newState = { ...initialState }
+// API Functions
+const fetchImagesByLocation = async ({ page, page_size, location_id }: getImagesReq) => {
const url = `${GET_IMAGES_BY_LOCATION_URI}?location_id=${location_id}&page=${page}&page_size=${page_size}`
+ const response = await client({ method: 'GET', url })
+ return response.data
+}
+// React Query Hooks
+export const useImagesByLocation = (params: getImagesReq, options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['images', 'location', params],
+ queryFn: () => fetchImagesByLocation(params),
+ enabled: !!params.location_id,
+ ...options
+ })
+}
+
+// Legacy service functions for backward compatibility
+async function getImagesByLocationService({ page, page_size, location_id }: getImagesReq) {
try {
- const response = await client({ method: 'GET', url: url})
- switch (response.request.status) {
- case statusCode.OK:
- newState.data = response.data;
- return newState
- default:
- newState.error = response.data;
- return newState
- }
+ const data = await fetchImagesByLocation({ page, page_size, location_id })
+ return { data, error: null }
} catch (error) {
console.log(`GET IMAGE BY LOCATION SERVICE ERROR: ${error}`)
+ return { data: null, error }
}
}
diff --git a/src/services/locations.ts b/src/services/locations.ts
index 463ee67..b898996 100755
--- a/src/services/locations.ts
+++ b/src/services/locations.ts
@@ -1,21 +1,15 @@
+import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from "@tanstack/react-query";
import { GetRequestPagination, IHttpResponse } from "../types/common";
-import {
- GET_LIST_LOCATIONS_URI,
- GET_LIST_RECENT_LOCATIONS_RATING_URI,
- GET_LIST_TOP_LOCATIONS,
- GET_LOCATION_TAGS_URI,
+import {
+ GET_LIST_LOCATIONS_URI,
+ GET_LIST_RECENT_LOCATIONS_RATING_URI,
+ GET_LIST_TOP_LOCATIONS,
+ GET_LOCATION_TAGS_URI,
GET_LOCATION_URI,
GET_SEARCH_LOCATIONS_URI,
POST_CREATE_LOCATION
} from "../constants/api";
import { client } from "./config";
-import statusCode from "./status-code";
-import { AxiosError } from "axios";
-
-const initialState: any = {
- data: null,
- error: null
-}
interface GetListLocationsArg extends GetRequestPagination {
order_by?: number,
@@ -27,131 +21,181 @@ interface GetSearchLocations extends GetRequestPagination {
filter?: string
}
-async function getListLocationsService({ page, page_size }: GetListLocationsArg) {
- const newState = { ...initialState };
+// API Functions
+const fetchListLocations = async ({ page, page_size }: GetListLocationsArg) => {
const url = `${GET_LIST_LOCATIONS_URI}?page=${page}&page_size=${page_size}`
+ const response = await client({ method: 'GET', url })
+ return response.data
+}
+
+const fetchRecentLocationsRatings = async (page_size: number, page: number) => {
+ const url = `${GET_LIST_RECENT_LOCATIONS_RATING_URI}?page_size=${page_size}&page=${page}`
+ const response = await client({ method: 'GET', url })
+ return response.data
+}
+
+const fetchTopLocations = async ({ page, page_size, order_by, region_type }: GetListLocationsArg) => {
+ const url = `${GET_LIST_TOP_LOCATIONS}?page=${page}&page_size=${page_size}&order_by=${order_by}®ion_type=${region_type}`
+ const response = await client({ method: 'GET', url })
+ return response.data
+}
+
+const fetchLocation = async (id: number) => {
+ const url = `${GET_LOCATION_URI}/${id}`
+ const response = await client({ method: 'GET', url })
+ return response.data
+}
+
+const fetchLocationTags = async (id: number) => {
+ const url = `${GET_LOCATION_TAGS_URI}/${id}`
+ const response = await client({ method: 'GET', url })
+ return response.data
+}
+
+const createLocation = async (data: FormData) => {
+ const response = await client({ method: 'POST', url: POST_CREATE_LOCATION, data, withCredentials: true })
+ return response.data
+}
+
+const searchLocations = async (arg: GetSearchLocations) => {
+ const filter = arg.filter ? arg.filter : ''
+ const pageSize = arg.page_size ? arg.page_size : 12
+ const page = arg.page ? arg.page : 1
+ const response = await client({
+ method: 'GET',
+ url: `${GET_SEARCH_LOCATIONS_URI}?name=${arg.name}&filter${filter}&limit=${pageSize}&offset=${page}`
+ })
+ return response.data
+}
+
+// React Query Hooks
+export const useListLocations = (params: GetListLocationsArg, options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['locations', params],
+ queryFn: () => fetchListLocations(params),
+ ...options
+ })
+}
+
+export const useRecentLocationsRatings = (page_size: number, page: number, options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['locations', 'recent-ratings', page_size, page],
+ queryFn: () => fetchRecentLocationsRatings(page_size, page),
+ ...options
+ })
+}
+
+export const useTopLocations = (params: GetListLocationsArg, options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['locations', 'top', params],
+ queryFn: () => fetchTopLocations(params),
+ ...options
+ })
+}
+
+export const useLocation = (id: number, options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['location', id],
+ queryFn: () => fetchLocation(id),
+ enabled: !!id,
+ ...options
+ })
+}
+
+export const useLocationTags = (id: number, options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['location', 'tags', id],
+ queryFn: () => fetchLocationTags(id),
+ enabled: !!id,
+ ...options
+ })
+}
+
+export const useSearchLocations = (params: GetSearchLocations, options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['locations', 'search', params],
+ queryFn: () => searchLocations(params),
+ enabled: !!params.name,
+ ...options
+ })
+}
+
+export const useCreateLocation = (options?: UseMutationOptions) => {
+ return useMutation({
+ mutationFn: createLocation,
+ ...options
+ })
+}
+
+// Legacy service functions for backward compatibility
+async function getListLocationsService({ page, page_size }: GetListLocationsArg) {
try {
- const response = await client({ method: 'GET', url: url })
- switch (response.request.status) {
- case statusCode.OK:
- newState.data = response.data;
- return newState;
- default:
- newState.error = response.data;
- return newState
- }
+ const data = await fetchListLocations({ page, page_size })
+ return { data, error: null }
} catch (error) {
- console.log(error)
+ return { data: null, error }
}
}
async function getListRecentLocationsRatingsService(page_size: number, page: number) {
- const newState = { ...initialState };
- const url = `${GET_LIST_RECENT_LOCATIONS_RATING_URI}?page_size=${page_size}&page=${page}`
try {
- const response = await client({ method: 'GET', url: url })
- switch (response.request.status) {
- case statusCode.OK:
- newState.data = response.data;
- return newState;
- default:
- newState.error = response.data;
- return newState
- }
+ const data = await fetchRecentLocationsRatings(page_size, page)
+ return { data, error: null }
} catch (error) {
- console.log(error)
+ return { data: null, error }
}
}
-async function getListTopLocationsService({ page, page_size, order_by, region_type }: GetListLocationsArg) {
- const newState = { ...initialState };
- const url = `${GET_LIST_TOP_LOCATIONS}?page=${page}&page_size=${page_size}&order_by=${order_by}®ion_type=${region_type}`
+async function getListTopLocationsService(params: GetListLocationsArg) {
try {
- const response = await client({ method: 'GET', url: url })
- switch (response.request.status) {
- case statusCode.OK:
- newState.data = response.data;
- return newState;
- default:
- newState.error = response.data;
- return newState
- }
+ const data = await fetchTopLocations(params)
+ return { data, error: null }
} catch (error) {
- console.log(error)
+ return { data: null, error }
}
}
-async function getLocationService(id: Number) {
- const newState = { ...initialState };
- const url = `${GET_LOCATION_URI}/${id}`
+async function getLocationService(id: number) {
try {
- const response = await client({ method: 'GET', url: url })
- switch (response.request.status) {
- case statusCode.OK:
- newState.data = response.data;
- return newState;
- default:
- newState.error = response.data;
- return newState;
- }
+ const data = await fetchLocation(id)
+ return { data, error: null }
} catch (error) {
- throw(error)
+ throw error
}
}
-async function getLocationTagsService(id: Number) {
- const newState = { ...initialState };
- const url = `${GET_LOCATION_TAGS_URI}/${id}`
+async function getLocationTagsService(id: number) {
try {
- const response = await client({ method: 'GET', url: url })
- switch (response.request.status) {
- case statusCode.OK:
- newState.data = response.data;
- return newState;
- default:
- newState.error = response.data;
- return newState;
- }
+ const data = await fetchLocationTags(id)
+ return { data, error: null }
} catch (error) {
- console.log(error)
+ return { data: null, error }
}
}
async function createLocationService(data: FormData): Promise {
- const newState: IHttpResponse = { data: null, error: null};
- try {
- const response = await client({ method: 'POST', url: POST_CREATE_LOCATION, data: data, withCredentials: true})
- newState.data = response.data;
- newState.status = response.status
- return newState;
- } catch (error) {
- let err = error as AxiosError;
- newState.error = err;
- newState.status = err.status;
- return newState;
+ try {
+ const responseData = await createLocation(data)
+ return { data: responseData, error: null }
+ } catch (error: any) {
+ return {
+ data: null,
+ error,
+ status: error.status
+ }
}
}
async function getSearchLocationService(arg: GetSearchLocations): Promise {
- const newState: IHttpResponse = { data: null, error: null};
-
try {
- const filter = arg.filter ? arg.filter : ''
- const pageSize= arg.page_size ? arg.page_size : 12
- const page = arg.page ? arg.page : 1
- const response = await client({
- method: 'GET',
- url: `${GET_SEARCH_LOCATIONS_URI}?name=${arg.name}&filter${filter}&limit=${pageSize}&offset=${page}`
- })
-
- newState.data = response.data;
- newState.status = response.status;
- return newState;
- } catch(error) {
- const err = error as AxiosError;
- newState.error = err;
- newState.status = err.status;
- return newState;
+ const data = await searchLocations(arg)
+ return { data, error: null }
+ } catch(error: any) {
+ return {
+ data: null,
+ error,
+ status: error.status
+ }
}
}
diff --git a/src/services/news.ts b/src/services/news.ts
index 0f0b6e2..ea3d857 100755
--- a/src/services/news.ts
+++ b/src/services/news.ts
@@ -1,4 +1,4 @@
-import { AxiosError } from "axios";
+import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from "@tanstack/react-query";
import { GetRequestPagination, IHttpResponse } from "..//types/common";
import { client } from "./config";
import { GET_NEWS_EVENTS_URI, POST_NEWS_EVENTS_URI } from "../../src/constants/api";
@@ -7,21 +7,6 @@ interface GetNewsSevice extends GetRequestPagination {
is_with_approval: number
}
-async function getNewsServices({ page, page_size, is_with_approval}: GetNewsSevice): Promise {
- const newState: IHttpResponse = { data: null, error: null};
- try {
- const response = await client({ method: 'GET', url: `${GET_NEWS_EVENTS_URI}?page=${page}&page_size=${page_size}&is_with_approval=${is_with_approval}`});
- newState.data = response.data;
- newState.status = response.status;
- return newState;
- } catch (error) {
- let err = error as AxiosError;
- newState.error = err;
- newState.status = err.status
- throw(newState)
- }
-}
-
interface PostNewsServiceBody {
title: string,
url: string,
@@ -29,18 +14,52 @@ interface PostNewsServiceBody {
submitted_by: number
}
-async function postNewsService(req: PostNewsServiceBody): Promise {
- const newState: IHttpResponse = { data: null, error: null}
+// API Functions
+const fetchNews = async ({ page, page_size, is_with_approval }: GetNewsSevice) => {
+ const response = await client({
+ method: 'GET',
+ url: `${GET_NEWS_EVENTS_URI}?page=${page}&page_size=${page_size}&is_with_approval=${is_with_approval}`
+ })
+ return response.data
+}
+
+const postNews = async (req: PostNewsServiceBody) => {
+ const response = await client({ method: 'POST', url: POST_NEWS_EVENTS_URI, data: req, withCredentials: true })
+ return response.data
+}
+
+// React Query Hooks
+export const useNews = (params: GetNewsSevice, options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['news', params],
+ queryFn: () => fetchNews(params),
+ ...options
+ })
+}
+
+export const usePostNews = (options?: UseMutationOptions) => {
+ return useMutation({
+ mutationFn: postNews,
+ ...options
+ })
+}
+
+// Legacy service functions for backward compatibility
+async function getNewsServices({ page, page_size, is_with_approval}: GetNewsSevice): Promise {
try {
- const response = await client({ method: 'POST', url: POST_NEWS_EVENTS_URI, data: req, withCredentials: true})
- newState.data = response.data
- newState.status = response.status
- return newState
- } catch (error) {
- let err = error as AxiosError;
- newState.error = err;
- newState.status = err.status
- throw(newState)
+ const data = await fetchNews({ page, page_size, is_with_approval })
+ return { data, error: null }
+ } catch (error: any) {
+ throw { data: null, error, status: error.status }
+ }
+}
+
+async function postNewsService(req: PostNewsServiceBody): Promise {
+ try {
+ const data = await postNews(req)
+ return { data, error: null }
+ } catch (error: any) {
+ throw { data: null, error, status: error.status }
}
}
diff --git a/src/services/regions.ts b/src/services/regions.ts
index 394bdb6..331b21c 100755
--- a/src/services/regions.ts
+++ b/src/services/regions.ts
@@ -1,41 +1,74 @@
+import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { client } from "./config";
import { GET_PROVINCES, GET_REGENCIES, GET_REGIONS } from "../constants/api";
import { IHttpResponse } from "src/types/common";
+// API Functions
+const fetchRegions = async () => {
+ const response = await client({ method: 'GET', url: GET_REGIONS })
+ return response.data
+}
+
+const fetchProvinces = async () => {
+ const response = await client({ method: 'GET', url: GET_PROVINCES })
+ return response.data
+}
+
+const fetchRegencies = async () => {
+ const response = await client({ method: 'GET', url: GET_REGENCIES })
+ return response.data
+}
+
+// React Query Hooks
+export const useRegions = (options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['regions'],
+ queryFn: fetchRegions,
+ ...options
+ })
+}
+
+export const useProvinces = (options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['provinces'],
+ queryFn: fetchProvinces,
+ ...options
+ })
+}
+
+export const useRegencies = (options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['regencies'],
+ queryFn: fetchRegencies,
+ ...options
+ })
+}
+
+// Legacy service functions for backward compatibility
async function getRegionsService(): Promise {
- const newState: IHttpResponse = {data: null, error: null}
try {
- const response = await client({ method: 'GET', url: GET_REGIONS})
- newState.data = response.data;
- return newState
+ const data = await fetchRegions()
+ return { data, error: null }
} catch(err) {
- newState.error = err
- throw (newState)
+ throw { data: null, error: err }
}
}
async function getProvincesService(): Promise {
- const newState: IHttpResponse = { data: null, error: null}
try {
- const response = await client({ method: 'GET', url: GET_PROVINCES})
- newState.data = response.data;
- return newState
+ const data = await fetchProvinces()
+ return { data, error: null }
} catch(err) {
- newState.error = err
- throw (newState)
+ throw { data: null, error: err }
}
}
async function getRegenciesService(): Promise {
- const newState: IHttpResponse = { data: null, error: null};
try {
- const response = await client({ method: 'GET', url: GET_REGENCIES})
- newState.data = response.data;
- newState.status = response.status
- return newState
+ const response = await client({ method: 'GET', url: GET_REGENCIES })
+ return { data: response.data, error: null, status: response.status }
} catch(err) {
- newState.error = err
- throw (newState)
+ throw { data: null, error: err }
}
}
diff --git a/src/services/review.ts b/src/services/review.ts
index f4747dd..62a83c4 100755
--- a/src/services/review.ts
+++ b/src/services/review.ts
@@ -1,14 +1,8 @@
-import { AxiosError } from "axios"
+import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from "@tanstack/react-query";
import { client } from "./config";
import { GET_CURRENT_USER_REVIEW_LOCATION_URI, POST_REVIEW_LOCATION_URI } from "../constants/api";
import { IHttpResponse } from "src/types/common";
-const initialState: IHttpResponse = {
- data: null,
- error: AxiosError,
- status: 0,
-}
-
interface postReviewLocationReq {
submitted_by: number,
comments: string,
@@ -18,31 +12,54 @@ interface postReviewLocationReq {
location_id: number
}
+// API Functions
+const postReview = async (req: postReviewLocationReq) => {
+ const response = await client({ method: 'POST', url: POST_REVIEW_LOCATION_URI, data: req, withCredentials: true })
+ return response.data
+}
+
+const fetchCurrentUserLocationReview = async (location_id: number) => {
+ const response = await client({
+ method: 'GET',
+ url: `${GET_CURRENT_USER_REVIEW_LOCATION_URI}/${location_id}`,
+ withCredentials: true
+ })
+ return response.data
+}
+
+// React Query Hooks
+export const usePostReview = (options?: UseMutationOptions) => {
+ return useMutation({
+ mutationFn: postReview,
+ ...options
+ })
+}
+
+export const useCurrentUserLocationReview = (location_id: number, options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['user', 'review', location_id],
+ queryFn: () => fetchCurrentUserLocationReview(location_id),
+ enabled: !!location_id,
+ ...options
+ })
+}
+
+// Legacy service functions for backward compatibility
async function postReviewLocation(req: postReviewLocationReq) {
- const newState = { ...initialState };
try {
- const response = await client({ method: 'POST', url: POST_REVIEW_LOCATION_URI, data: req, withCredentials: true})
- newState.data = response.data
- newState.error = null
- return newState
+ const data = await postReview(req)
+ return { data, error: null }
} catch (error) {
- newState.error = error
- throw(error)
+ throw error
}
}
async function getCurrentUserLocationReviewService(location_id: number): Promise {
- const newState = { ...initialState };
try {
- const response = await client({ method: 'GET', url: `${GET_CURRENT_USER_REVIEW_LOCATION_URI}/${location_id}`, withCredentials: true})
- newState.data = response.data
- newState.error = null
- return newState
- } catch (err) {
- let error = err as AxiosError;
- newState.error = error
- newState.status = error.response?.status;
- throw(newState)
+ const data = await fetchCurrentUserLocationReview(location_id)
+ return { data, error: null }
+ } catch (error: any) {
+ throw { data: null, error, status: error.status }
}
}
diff --git a/src/services/users.ts b/src/services/users.ts
index 5f75ee3..135dac5 100755
--- a/src/services/users.ts
+++ b/src/services/users.ts
@@ -1,67 +1,94 @@
-import { AxiosError } from "axios";
+import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from "@tanstack/react-query";
import { DELETE_USER_AVATAR, GET_CURRENT_USER_STATS, PATCH_USER_AVATAR, PATCH_USER_INFO } from "../constants/api";
import { IHttpResponse } from "../types/common";
import { client } from "./config";
import { UserInfo } from "../../src/domains/User";
+// API Functions
+const fetchUserStats = async () => {
+ const res = await client({ method: 'GET', url: GET_CURRENT_USER_STATS, withCredentials: true })
+ return res.data
+}
+const patchUserAvatar = async (form: FormData) => {
+ const res = await client({ method: "PATCH", url: PATCH_USER_AVATAR, data: form, withCredentials: true })
+ return res.data
+}
+
+const patchUserInfo = async (data: UserInfo) => {
+ const res = await client({ method: 'PATCH', url: PATCH_USER_INFO, data, withCredentials: true })
+ return res.data
+}
+
+const deleteUserAvatar = async () => {
+ const res = await client({ method: 'DELETE', url: DELETE_USER_AVATAR, withCredentials: true })
+ return res.data
+}
+
+// React Query Hooks
+export const useUserStats = (options?: Omit, 'queryKey' | 'queryFn'>) => {
+ return useQuery({
+ queryKey: ['user', 'stats'],
+ queryFn: fetchUserStats,
+ ...options
+ })
+}
+
+export const usePatchUserAvatar = (options?: UseMutationOptions) => {
+ return useMutation({
+ mutationFn: patchUserAvatar,
+ ...options
+ })
+}
+
+export const usePatchUserInfo = (options?: UseMutationOptions) => {
+ return useMutation({
+ mutationFn: patchUserInfo,
+ ...options
+ })
+}
+
+export const useDeleteUserAvatar = (options?: UseMutationOptions) => {
+ return useMutation({
+ mutationFn: deleteUserAvatar,
+ ...options
+ })
+}
+
+// Legacy service functions for backward compatibility
async function getUserStatsService(): Promise {
- const newState: IHttpResponse = { data: null, error: null };
try {
- const res = await client({ method: 'GET', url: GET_CURRENT_USER_STATS, withCredentials: true})
- newState.data = res.data
- newState.status = res.status
- return newState
- } catch(error) {
- let err = error as AxiosError
- newState.error = err
- newState.status = err.status
- throw(newState)
+ const data = await fetchUserStats()
+ return { data, error: null }
+ } catch(error: any) {
+ throw { data: null, error, status: error.status }
}
}
async function patchUserAvatarService(form: FormData): Promise {
- const newState: IHttpResponse = { data: null, error: null};
try {
- const res = await client({ method: "PATCH", url: PATCH_USER_AVATAR, data: form, withCredentials: true})
- newState.data = res.data;
- newState.status = res.status;
- return newState;
- } catch(error) {
- let err = error as AxiosError;
- newState.error = err
- newState.status = err.status
- throw(newState);
+ const data = await patchUserAvatar(form)
+ return { data, error: null }
+ } catch(error: any) {
+ throw { data: null, error, status: error.status }
}
}
async function patchUserInfoService(data: UserInfo): Promise {
- const newState: IHttpResponse = { data: null, error: null};
try {
- const res = await client({ method: 'PATCH', url: PATCH_USER_INFO, data: data, withCredentials: true})
- newState.data = res.data;
- newState.status = res.status;
- return newState;
- } catch(error) {
- let err = error as AxiosError;
- newState.error = err;
- newState.status = err.status;
- throw(newState);
+ const responseData = await patchUserInfo(data)
+ return { data: responseData, error: null }
+ } catch(error: any) {
+ throw { data: null, error, status: error.status }
}
}
async function deleteUserAvatarService(): Promise {
- const newState: IHttpResponse = { data: null, error: null};
try {
- const res = await client({ method: 'DELETE', url: DELETE_USER_AVATAR, withCredentials: true})
- newState.data = res.data;
- newState.status = res.status
- return newState
- } catch (error) {
- let err = error as AxiosError;
- newState.error = err;
- newState.status = err.status;
- throw(newState);
+ const data = await deleteUserAvatar()
+ return { data, error: null }
+ } catch (error: any) {
+ throw { data: null, error, status: error.status }
}
}
diff --git a/src/utils/common.ts b/src/utils/common.ts
index d7553b8..954df98 100755
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -1,4 +1,6 @@
import { AxiosError } from "axios";
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
export function handleAxiosError(error: AxiosError) {
return error.response?.data
@@ -11,4 +13,8 @@ export function enumKeys(obj: O):
export function isUrl(val: string): boolean {
var urlPattern = /^https:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
return urlPattern.test(val);
-}
\ No newline at end of file
+}
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/tests/example.spec.ts b/tests/example.spec.ts
new file mode 100644
index 0000000..54a906a
--- /dev/null
+++ b/tests/example.spec.ts
@@ -0,0 +1,18 @@
+import { test, expect } from '@playwright/test';
+
+test('has title', async ({ page }) => {
+ await page.goto('https://playwright.dev/');
+
+ // Expect a title "to contain" a substring.
+ await expect(page).toHaveTitle(/Playwright/);
+});
+
+test('get started link', async ({ page }) => {
+ await page.goto('https://playwright.dev/');
+
+ // Click the get started link.
+ await page.getByRole('link', { name: 'Get started' }).click();
+
+ // Expects page to have a heading with the name of Installation.
+ await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
+});