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";
|
} from "react";
|
||||||
import { useLocation, useSearchParams } from "react-router";
|
import { useLocation, useSearchParams } from "react-router";
|
||||||
import { useNotification } from "./notification";
|
import { useNotification } from "./notification";
|
||||||
|
import { useApi } from "./api";
|
||||||
|
|
||||||
type User = {
|
type User = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -39,6 +40,7 @@ export function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
const [searchParams, setSeachParams] = useSearchParams();
|
const [searchParams, setSeachParams] = useSearchParams();
|
||||||
const { notify } = useNotification();
|
const { notify } = useNotification();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { get } = useApi();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
@@ -56,22 +58,7 @@ export function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
);
|
);
|
||||||
let query = searchParams.toString();
|
let query = searchParams.toString();
|
||||||
setSeachParams({}, { replace: true });
|
setSeachParams({}, { replace: true });
|
||||||
let response = await fetch(
|
await get(`login?${query}`);
|
||||||
`${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,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await update()) {
|
if (await update()) {
|
||||||
@@ -98,50 +85,23 @@ export function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
await fetch(import.meta.env.VITE_BASE_API_URL + "/logout", {
|
const data = await get("logout");
|
||||||
credentials: "include",
|
if (data === null) {
|
||||||
})
|
setUser(null);
|
||||||
.then(() => {
|
notify(
|
||||||
setUser(null);
|
{
|
||||||
notify(
|
type: "success",
|
||||||
{
|
loading: false,
|
||||||
type: "success",
|
title: "Logout successful!",
|
||||||
loading: false,
|
message: "",
|
||||||
title: "Logout successful!",
|
},
|
||||||
message: "",
|
2000,
|
||||||
},
|
);
|
||||||
2000,
|
}
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
notify(
|
|
||||||
{
|
|
||||||
type: "danger",
|
|
||||||
loading: false,
|
|
||||||
title: "An error occured.",
|
|
||||||
message: `An error occured: ${e}`,
|
|
||||||
},
|
|
||||||
10000,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = async () => {
|
const update = async () => {
|
||||||
let data = await fetch(`${import.meta.env.VITE_BASE_API_URL}/me`, {
|
let data = await get("me");
|
||||||
credentials: "include",
|
|
||||||
})
|
|
||||||
.then((r) => r.json())
|
|
||||||
.catch((e) => {
|
|
||||||
notify(
|
|
||||||
{
|
|
||||||
type: "danger",
|
|
||||||
loading: false,
|
|
||||||
title: "An error occured.",
|
|
||||||
message: `An error occured: ${e}`,
|
|
||||||
},
|
|
||||||
10000,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data !== undefined) {
|
if (data !== undefined) {
|
||||||
setUser(data);
|
setUser(data);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { Route } from "./+types/root";
|
|||||||
import "./app.css";
|
import "./app.css";
|
||||||
import { AuthProvider } from "./hooks/auth";
|
import { AuthProvider } from "./hooks/auth";
|
||||||
import { NotificationProvider } from "./hooks/notification";
|
import { NotificationProvider } from "./hooks/notification";
|
||||||
|
import { ApiProvider } from "./hooks/api";
|
||||||
|
|
||||||
export const links: Route.LinksFunction = () => [
|
export const links: Route.LinksFunction = () => [
|
||||||
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
|
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
|
||||||
@@ -39,7 +40,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
</head>
|
</head>
|
||||||
<body className="bg-background text-foreground">
|
<body className="bg-background text-foreground">
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<AuthProvider>{children}</AuthProvider>
|
<ApiProvider>
|
||||||
|
<AuthProvider>{children}</AuthProvider>
|
||||||
|
</ApiProvider>
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
|
|||||||
Reference in New Issue
Block a user