Move api client to a hook and allow .env api url config

This commit is contained in:
april 2024-01-05 11:42:05 -06:00
parent 05fefd40b1
commit 02c45bbc63
9 changed files with 99 additions and 50 deletions

View File

@ -12,6 +12,8 @@ import {
Scripts, Scripts,
ScrollRestoration, ScrollRestoration,
isRouteErrorResponse, isRouteErrorResponse,
json,
useLoaderData,
useNavigate, useNavigate,
useRouteError, useRouteError,
} from "@remix-run/react"; } from "@remix-run/react";
@ -40,6 +42,7 @@ import { IconRocket } from "@tabler/icons-react";
import { AuthProvider } from "./util/auth"; import { AuthProvider } from "./util/auth";
import { useState } from "react"; import { useState } from "react";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { ApiProvider } from "./util/api";
export const links: LinksFunction = () => [ export const links: LinksFunction = () => [
{ {
@ -104,6 +107,14 @@ export function ErrorBoundary() {
); );
} }
export async function loader() {
return json({
ENV: {
TAILFIN_API_URL: process.env.TAILFIN_API_URL ?? "http://localhost:8081",
},
});
}
export default function App() { export default function App() {
const navigate = useNavigate(); const navigate = useNavigate();
const [queryClient] = useState( const [queryClient] = useState(
@ -132,6 +143,8 @@ export default function App() {
const dehydratedState = useDehydratedState(); const dehydratedState = useDehydratedState();
const data = useLoaderData<typeof loader>();
return ( return (
<html lang="en"> <html lang="en">
<head> <head>
@ -144,14 +157,16 @@ export default function App() {
<body> <body>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<HydrationBoundary state={dehydratedState}> <HydrationBoundary state={dehydratedState}>
<MantineProvider theme={{ primaryColor: "violet" }}> <ApiProvider apiUrl={data.ENV.TAILFIN_API_URL}>
<AuthProvider> <MantineProvider theme={{ primaryColor: "violet" }}>
<Outlet /> <AuthProvider>
<ScrollRestoration /> <Outlet />
<Scripts /> <ScrollRestoration />
<LiveReload /> <Scripts />
</AuthProvider> <LiveReload />
</MantineProvider> </AuthProvider>
</MantineProvider>
</ApiProvider>
<ReactQueryDevtools initialIsOpen={false} /> <ReactQueryDevtools initialIsOpen={false} />
</HydrationBoundary> </HydrationBoundary>
</QueryClientProvider> </QueryClientProvider>

View File

@ -1,6 +1,6 @@
import { VerticalLogItem } from "@/ui/display/log-item"; import { VerticalLogItem } from "@/ui/display/log-item";
import ErrorDisplay from "@/ui/error-display"; import ErrorDisplay from "@/ui/error-display";
import { client } from "@/util/api"; import { useApi } from "@/util/api";
import { import {
Center, Center,
Container, Container,
@ -16,6 +16,8 @@ import { useQuery } from "@tanstack/react-query";
export default function Flight() { export default function Flight() {
const params = useParams(); const params = useParams();
const client = useApi();
const flight = useQuery({ const flight = useQuery({
queryKey: [params.id], queryKey: [params.id],
queryFn: async () => queryFn: async () =>

View File

@ -21,7 +21,7 @@ import { HourInput, ZeroHourInput } from "@/ui/form/hour-input";
import { ZeroIntInput } from "@/ui/form/int-input"; import { ZeroIntInput } from "@/ui/form/int-input";
import ListInput from "@/ui/form/list-input"; import ListInput from "@/ui/form/list-input";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { client } from "@/util/api"; import { useApi } from "@/util/api";
import { useNavigate } from "@remix-run/react"; import { useNavigate } from "@remix-run/react";
import { useAuth } from "@/util/auth"; import { useAuth } from "@/util/auth";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
@ -80,6 +80,8 @@ export default function NewFlight() {
const navigate = useNavigate(); const navigate = useNavigate();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const client = useApi();
const { clearUser } = useAuth(); const { clearUser } = useAuth();
const createFlight = useMutation({ const createFlight = useMutation({

View File

@ -1,4 +1,4 @@
import { client } from "@/util/api"; import { useApi } from "@/util/api";
import { FlightConciseSchema } from "@/util/types"; import { FlightConciseSchema } from "@/util/types";
import { import {
NavLink, NavLink,
@ -22,6 +22,8 @@ import {
import { UseQueryResult, useQuery } from "@tanstack/react-query"; import { UseQueryResult, useQuery } from "@tanstack/react-query";
function useFlights() { function useFlights() {
const client = useApi();
const flights = useQuery({ const flights = useQuery({
queryKey: ["flights-list"], queryKey: ["flights-list"],
queryFn: async () => queryFn: async () =>

View File

@ -1,9 +1,11 @@
import ErrorDisplay from "@/ui/error-display"; import ErrorDisplay from "@/ui/error-display";
import { client } from "@/util/api"; import { useApi } from "@/util/api";
import { Center, Container, Loader, Text, Title } from "@mantine/core"; import { Center, Container, Loader, Text, Title } from "@mantine/core";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
export default function Me() { export default function Me() {
const client = useApi();
const user = useQuery({ const user = useQuery({
queryKey: ["user"], queryKey: ["user"],
queryFn: async () => await client.get(`users/me`).then((res) => res.data), queryFn: async () => await client.get(`users/me`).then((res) => res.data),

View File

@ -1,36 +0,0 @@
import axios from "axios";
import { useAuth } from "./auth";
export const client = axios.create({
baseURL: "http://localhost:8081",
headers: { "Access-Control-Allow-Origin": "*" },
});
client.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
async (error) => {
const { clearUser } = useAuth();
console.log(error.response);
if (error.response && error.response.status === 401) {
try {
const refreshToken = localStorage.getItem("refresh-token");
const response = await client.post("/auth/refresh", { refreshToken });
const newAccessToken = response.data.access_token;
localStorage.setItem("token", newAccessToken);
error.config.headers.Authorization = `Bearer ${newAccessToken}`;
return client(error.config);
} catch (err) {
clearUser();
window.location.href = "/login";
}
}
return Promise.reject(error);
}
);

60
web/app/util/api.tsx Normal file
View File

@ -0,0 +1,60 @@
import axios, { AxiosInstance } from "axios";
import { createContext, useContext } from "react";
const ApiContext = createContext<AxiosInstance | null>(null);
export function ApiProvider({
children,
apiUrl,
}: {
children: React.ReactNode;
apiUrl: string;
}) {
const api = useProvideApi(apiUrl);
return <ApiContext.Provider value={api}>{children}</ApiContext.Provider>;
}
export function useApi(): AxiosInstance {
const api = useContext(ApiContext);
if (!api) throw new Error("Could not find API provider");
return api;
}
function useProvideApi(apiUrl: string) {
const client = axios.create({
baseURL: apiUrl,
headers: { "Access-Control-Allow-Origin": "*" },
});
client.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
async (error) => {
console.log(error.response);
if (error.response && error.response.status === 401) {
try {
const refreshToken = localStorage.getItem("refresh-token");
const response = await client.post("/auth/refresh", { refreshToken });
const newAccessToken = response.data.access_token;
localStorage.setItem("token", newAccessToken);
error.config.headers.Authorization = `Bearer ${newAccessToken}`;
return client(error.config);
} catch (err) {
localStorage.removeItem("token");
localStorage.removeItem("refresh-token");
window.location.href = "/login";
}
}
return Promise.reject(error);
}
);
return client;
}

View File

@ -1,4 +1,4 @@
import { client } from "./api"; import { useApi } from "./api";
import { useNavigate } from "@remix-run/react"; import { useNavigate } from "@remix-run/react";
import { createContext, useContext, useEffect, useState } from "react"; import { createContext, useContext, useEffect, useState } from "react";
@ -37,6 +37,8 @@ function useProvideAuth() {
const navigate = useNavigate(); const navigate = useNavigate();
const client = useApi();
const handleUser = (rawUser: string | null) => { const handleUser = (rawUser: string | null) => {
if (rawUser) { if (rawUser) {
setUser(rawUser); setUser(rawUser);

View File

@ -1,5 +1,5 @@
{ {
"name": "", "name": "tailfin-web",
"private": true, "private": true,
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",