Created api hook to reduce boilerplate code
This commit is contained in:
88
app/hooks/api.tsx
Normal file
88
app/hooks/api.tsx
Normal file
@@ -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<any>;
|
||||
post: (arg0: string, arg1: any) => Promise<void>;
|
||||
};
|
||||
|
||||
const apiContext = createContext<ApiContextType>({
|
||||
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 (
|
||||
<apiContext.Provider value={{ get, post }}>
|
||||
{children}
|
||||
</apiContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
@@ -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,10 +85,8 @@ export function AuthProvider({ children }: PropsWithChildren) {
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
await fetch(import.meta.env.VITE_BASE_API_URL + "/logout", {
|
||||
credentials: "include",
|
||||
})
|
||||
.then(() => {
|
||||
const data = await get("logout");
|
||||
if (data === null) {
|
||||
setUser(null);
|
||||
notify(
|
||||
{
|
||||
@@ -112,36 +97,11 @@ export function AuthProvider({ children }: PropsWithChildren) {
|
||||
},
|
||||
2000,
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
notify(
|
||||
{
|
||||
type: "danger",
|
||||
loading: false,
|
||||
title: "An error occured.",
|
||||
message: `An error occured: ${e}`,
|
||||
},
|
||||
10000,
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@@ -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 }) {
|
||||
</head>
|
||||
<body className="bg-background text-foreground">
|
||||
<NotificationProvider>
|
||||
<ApiProvider>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</ApiProvider>
|
||||
</NotificationProvider>
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
|
||||
Reference in New Issue
Block a user