diff --git a/app/hooks/api.tsx b/app/hooks/api.tsx new file mode 100644 index 0000000..5c052f5 --- /dev/null +++ b/app/hooks/api.tsx @@ -0,0 +1,88 @@ +import { + useState, + createContext, + useContext, + type PropsWithChildren, +} from "react"; +import { useNotification } from "./notification"; +import type { NotificationType } from "~/components/notification"; + +type ApiContextType = { + get: (arg0: string) => Promise; + post: (arg0: string, arg1: any) => Promise; +}; + +const apiContext = createContext({ + get: async (_: string) => {}, + post: async (_a: string, _b: any) => {}, +}); + +export function ApiProvider({ children }: PropsWithChildren) { + const { notify } = useNotification(); + + const get = async (endpoint: string) => { + let data = undefined; + + let response = await fetch( + `${import.meta.env.VITE_BASE_API_URL}/${endpoint}`, + { credentials: "include" }, + ).catch((e) => notifyError(notify, e.code, e.message)); + + if (!response) { + notifyError(notify, 1, "No data in response from server."); + } else if (!response.ok) { + notifyError(notify, response.status, await response.text()); + } else { + const text = await response.text(); + data = text ? JSON.parse(text): null; + } + + return data; + }; + + const post = async (endpoint: string, data: any) => { + let response = await fetch( + `${import.meta.env.VITE_BASE_API_URL}/${endpoint}`, + { + credentials: "include", + method: "POST", + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json", + }, + }, + ).catch((e) => notifyError(notify, e.code, e.message)); + + if (!response) { + notifyError(notify, 1, "No data in response from server."); + } else if (!response.ok) { + notifyError(notify, response.status, await response.text()); + } + }; + + return ( + + {children} + + ); +} + +export function useApi() { + return useContext(apiContext); +} + +function notifyError( + notify: (arg0: NotificationType, arg1: number) => void, + code: number, + message: string, +) { + notify( + { + type: "danger", + loading: false, + title: "Oops, something went wrong.", + message: `There was an issue on our end: ${message} (Error ${code})`, + }, + 10000, + ); +} diff --git a/app/hooks/auth.tsx b/app/hooks/auth.tsx index 4c225f2..3d7eb60 100644 --- a/app/hooks/auth.tsx +++ b/app/hooks/auth.tsx @@ -7,6 +7,7 @@ import { } from "react"; import { useLocation, useSearchParams } from "react-router"; import { useNotification } from "./notification"; +import { useApi } from "./api"; type User = { id: string; @@ -39,6 +40,7 @@ export function AuthProvider({ children }: PropsWithChildren) { const [searchParams, setSeachParams] = useSearchParams(); const { notify } = useNotification(); const location = useLocation(); + const { get } = useApi(); useEffect(() => { const load = async () => { @@ -56,22 +58,7 @@ export function AuthProvider({ children }: PropsWithChildren) { ); let query = searchParams.toString(); setSeachParams({}, { replace: true }); - let response = await fetch( - `${import.meta.env.VITE_BASE_API_URL}/login?${query}`, - { credentials: "include" }, - ) - .then((r) => r.text()) - .catch((e) => { - notify( - { - type: "danger", - loading: false, - title: "An error occured.", - message: `An error occured: ${e}`, - }, - 10000, - ); - }); + await get(`login?${query}`); } if (await update()) { @@ -98,50 +85,23 @@ export function AuthProvider({ children }: PropsWithChildren) { }; const logout = async () => { - await fetch(import.meta.env.VITE_BASE_API_URL + "/logout", { - credentials: "include", - }) - .then(() => { - setUser(null); - notify( - { - type: "success", - loading: false, - title: "Logout successful!", - message: "", - }, - 2000, - ); - }) - .catch((e) => { - notify( - { - type: "danger", - loading: false, - title: "An error occured.", - message: `An error occured: ${e}`, - }, - 10000, - ); - }); + const data = await get("logout"); + if (data === null) { + setUser(null); + notify( + { + type: "success", + loading: false, + title: "Logout successful!", + message: "", + }, + 2000, + ); + } }; const update = async () => { - let data = await fetch(`${import.meta.env.VITE_BASE_API_URL}/me`, { - credentials: "include", - }) - .then((r) => r.json()) - .catch((e) => { - notify( - { - type: "danger", - loading: false, - title: "An error occured.", - message: `An error occured: ${e}`, - }, - 10000, - ); - }); + let data = await get("me"); if (data !== undefined) { setUser(data); diff --git a/app/root.tsx b/app/root.tsx index 4dc0adb..51c238c 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -11,6 +11,7 @@ import type { Route } from "./+types/root"; import "./app.css"; import { AuthProvider } from "./hooks/auth"; import { NotificationProvider } from "./hooks/notification"; +import { ApiProvider } from "./hooks/api"; export const links: Route.LinksFunction = () => [ { rel: "preconnect", href: "https://fonts.googleapis.com" }, @@ -39,7 +40,9 @@ export function Layout({ children }: { children: React.ReactNode }) { - {children} + + {children} +