From a11d551a998c3a62f729f164d8d02c724d7acd60 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 23 Jan 2026 17:38:30 +0000 Subject: [PATCH 1/2] add SWR hook and API client --- client/src/Hooks/UseApi.ts | 41 ++++++++++++++++++++++++ client/src/Utils/ApiClient.ts | 59 +++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 client/src/Hooks/UseApi.ts create mode 100644 client/src/Utils/ApiClient.ts diff --git a/client/src/Hooks/UseApi.ts b/client/src/Hooks/UseApi.ts new file mode 100644 index 000000000..58580e4f3 --- /dev/null +++ b/client/src/Hooks/UseApi.ts @@ -0,0 +1,41 @@ +import useSWR from "swr"; +import type { SWRConfiguration } from "swr"; +import type { AxiosRequestConfig } from "axios"; +import { get, post, patch, deleteOp } from "@/Utils/ApiClient"; + +interface ApiResponse { + success: boolean; + msg: string; + data: T; +} + +interface UseGetReturn { + data: T | undefined; + isLoading: boolean; + error: Error | undefined; + refetch: () => Promise | undefined>; +} + +const fetcher = async (url: string, config?: AxiosRequestConfig) => { + const res = await get(url, config); + return res.data; +}; + +export const useGet = ( + url: string | null, + axiosConfig?: AxiosRequestConfig, + swrConfig?: SWRConfiguration +): UseGetReturn => { + const { data, error, isLoading, mutate } = useSWR>( + url, + (url: string) => fetcher(url, axiosConfig), + swrConfig + ); + + return { + data: data?.data, + isLoading, + error, + refetch: mutate, + }; +}; diff --git a/client/src/Utils/ApiClient.ts b/client/src/Utils/ApiClient.ts new file mode 100644 index 000000000..e375b07e7 --- /dev/null +++ b/client/src/Utils/ApiClient.ts @@ -0,0 +1,59 @@ +import axios from "axios"; +import type { AxiosError } from "axios"; +import type { AxiosRequestConfig, AxiosResponse } from "axios"; +import { store } from "@/store"; +const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; + +const api = axios.create({ + baseURL: BASE_URL, +}); + +api.interceptors.request.use( + (config) => { + const authToken = store.getState().auth?.authToken ?? ""; + + config.headers.set("Authorization", `Bearer ${authToken}`); + if (!config.headers.has("Accept-Language")) { + config.headers.set("Accept-Language", "en"); + } + + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +const onSuccess = (response: AxiosResponse) => response; +const onError = (error: AxiosError) => { + if (error.response?.status === 401) { + window.location.href = "/login"; + } + return Promise.reject(error); +}; + +api.interceptors.response.use(onSuccess, onError); + +export const get = ( + url: string, + config: AxiosRequestConfig = {} +): Promise> => api.get(url, config); + +export const post = ( + url: string, + data: unknown, + config: AxiosRequestConfig = {} +): Promise> => api.post(url, data, config); + +export const patch = ( + url: string, + data: unknown, + config: AxiosRequestConfig = {} +): Promise> => api.patch(url, data, config); + +export const deleteOp = ( + url: string, + config: AxiosRequestConfig = {} +): Promise> => api.delete(url, config); + +export default api; From 00cf0b65217d1da559486d6ee06ebaf48d41eca9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 23 Jan 2026 18:14:16 +0000 Subject: [PATCH 2/2] fix get return type --- client/package.json | 2 +- client/src/Hooks/UseApi.ts | 15 ++++----------- client/src/Types/Monitor.ts | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/client/package.json b/client/package.json index 50fafb35c..7c5acb441 100644 --- a/client/package.json +++ b/client/package.json @@ -43,7 +43,7 @@ "react-toastify": "^10.0.5", "recharts": "2.15.2", "redux-persist": "6.0.0", - "swr": "2.3.6", + "swr": "^2.3.6", "vite-plugin-svgr": "^4.2.0", "zod": "4.1.11" }, diff --git a/client/src/Hooks/UseApi.ts b/client/src/Hooks/UseApi.ts index 58580e4f3..06845d6c1 100644 --- a/client/src/Hooks/UseApi.ts +++ b/client/src/Hooks/UseApi.ts @@ -1,7 +1,7 @@ import useSWR from "swr"; import type { SWRConfiguration } from "swr"; import type { AxiosRequestConfig } from "axios"; -import { get, post, patch, deleteOp } from "@/Utils/ApiClient"; +import { get } from "@/Utils/ApiClient"; interface ApiResponse { success: boolean; @@ -9,15 +9,8 @@ interface ApiResponse { data: T; } -interface UseGetReturn { - data: T | undefined; - isLoading: boolean; - error: Error | undefined; - refetch: () => Promise | undefined>; -} - const fetcher = async (url: string, config?: AxiosRequestConfig) => { - const res = await get(url, config); + const res = await get>(url, config); return res.data; }; @@ -25,7 +18,7 @@ export const useGet = ( url: string | null, axiosConfig?: AxiosRequestConfig, swrConfig?: SWRConfiguration -): UseGetReturn => { +) => { const { data, error, isLoading, mutate } = useSWR>( url, (url: string) => fetcher(url, axiosConfig), @@ -33,7 +26,7 @@ export const useGet = ( ); return { - data: data?.data, + data: data?.data ?? null, isLoading, error, refetch: mutate, diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts index 05f7d7d6f..e01c9f844 100644 --- a/client/src/Types/Monitor.ts +++ b/client/src/Types/Monitor.ts @@ -1,7 +1,7 @@ import type { Check } from "@/Types/Check"; export type MonitorStatus = "up" | "down" | "initializing"; -export interface IMonitor { +export interface Monitor { checks: Check[]; createdAt: string; createdBy: string;