diff --git a/web/app/root.tsx b/web/app/root.tsx index 12280d9..d8438f7 100644 --- a/web/app/root.tsx +++ b/web/app/root.tsx @@ -12,10 +12,16 @@ import { Scripts, ScrollRestoration, isRouteErrorResponse, + redirect, + useNavigate, useRouteError, } from "@remix-run/react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + QueryCache, + QueryClient, + QueryClientProvider, +} from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { diff --git a/web/app/routes/logbook.flights.$id/route.tsx b/web/app/routes/logbook.flights.$id/route.tsx index d08d7d4..d4de9cf 100644 --- a/web/app/routes/logbook.flights.$id/route.tsx +++ b/web/app/routes/logbook.flights.$id/route.tsx @@ -1,7 +1,10 @@ import { client } from "@/util/api"; +import { useAuth } from "@/util/auth"; import { Center, Container, List, Loader, Stack, Text } from "@mantine/core"; -import { useParams } from "@remix-run/react"; +import { useNavigate, useParams } from "@remix-run/react"; +import { IconAlertTriangle } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; +import { useEffect } from "react"; export default function Flight() { const params = useParams(); @@ -10,8 +13,21 @@ export default function Flight() { queryKey: [params.id], queryFn: async () => await client.get(`/flights/${params.id}`).then((res) => res.data), + retry: (failureCount, error) => { + return !error || error.response?.status !== 401; + }, }); + const navigate = useNavigate(); + const { clearUser } = useAuth(); + + useEffect(() => { + if (flight.isError && flight.error.response.status === 401) { + clearUser(); + navigate("/login"); + } + }, [flight]); + return ( diff --git a/web/app/routes/logbook.flights.new/route.tsx b/web/app/routes/logbook.flights.new/route.tsx index c7cedb5..2754797 100644 --- a/web/app/routes/logbook.flights.new/route.tsx +++ b/web/app/routes/logbook.flights.new/route.tsx @@ -5,7 +5,6 @@ import { Fieldset, Group, NumberInput, - ScrollArea, ScrollAreaAutosize, Stack, TextInput, @@ -86,6 +85,9 @@ export default function NewFlight() { console.log(res); return res.data; }, + retry: (failureCount, error) => { + return !error || error.response?.status !== 401; + }, onError: (error) => { console.log(error); }, diff --git a/web/app/routes/logbook.flights/flights-list.tsx b/web/app/routes/logbook.flights/flights-list.tsx index b1ace34..316a1d0 100644 --- a/web/app/routes/logbook.flights/flights-list.tsx +++ b/web/app/routes/logbook.flights/flights-list.tsx @@ -1,4 +1,5 @@ import { client } from "@/util/api"; +import { useAuth } from "@/util/auth"; import { FlightConciseSchema } from "@/util/types"; import { NavLink, @@ -8,22 +9,38 @@ import { Stack, Loader, Center, - Container, } from "@mantine/core"; import { Link, useLocation, useNavigate } from "@remix-run/react"; import { IconPlus } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; +import { useEffect } from "react"; export function FlightsList() { - const flights = useQuery({ - queryKey: ["flights-list"], - queryFn: () => client.get(`/flights`).then((res) => res.data), - }); + // const flights = useQuery({ + // queryKey: ["flights-list"], + // queryFn: () => client.get(`/flights`).then((res) => res.data), + // }); const location = useLocation(); const page = location.pathname.split("/")[3]; + const flights = useQuery({ + queryKey: ["flights-list"], + queryFn: async () => await client.get(`/flights`).then((res) => res.data), + retry: (failureCount, error) => { + return !error || error.response?.status !== 401; + }, + }); + const navigate = useNavigate(); + const { clearUser } = useAuth(); + + useEffect(() => { + if (flights.isError && flights.error.response.status === 401) { + clearUser(); + navigate("/login"); + } + }, [flights]); return ( diff --git a/web/app/routes/logbook.me/route.tsx b/web/app/routes/logbook.me/route.tsx index ce9dc7e..80f0d3c 100644 --- a/web/app/routes/logbook.me/route.tsx +++ b/web/app/routes/logbook.me/route.tsx @@ -1,12 +1,33 @@ +import { client } from "@/util/api"; import { useAuth } from "@/util/auth"; -import { Container, Title } from "@mantine/core"; +import { Container, Text, Title } from "@mantine/core"; +import { useNavigate } from "@remix-run/react"; +import { useQuery } from "@tanstack/react-query"; +import { useEffect } from "react"; export default function Me() { - const { user } = useAuth(); + const user = useQuery({ + queryKey: ["user"], + queryFn: async () => await client.get(`users/me`).then((res) => res.data), + retry: (failureCount, error) => { + return !error || error.response?.status !== 401; + }, + }); + + const { clearUser } = useAuth(); + const navigate = useNavigate(); + + useEffect(() => { + if (user.isError && user.error.response?.status === 401) { + clearUser(); + navigate("/login"); + } + }, [user]); return ( - {user} + {user.data.username} + Level {user.data.level} ); } diff --git a/web/app/routes/login/route.tsx b/web/app/routes/login/route.tsx index 3f2b7ba..23203b4 100644 --- a/web/app/routes/login/route.tsx +++ b/web/app/routes/login/route.tsx @@ -12,6 +12,7 @@ import { Title, } from "@mantine/core"; import { useForm } from "@mantine/form"; +import { useEffect } from "react"; export default function Login() { const form = useForm({ @@ -23,6 +24,10 @@ export default function Login() { const { signin } = useAuth(); + useEffect(() => { + document.title = "Log In - Tailfin"; + }); + return ( @@ -50,7 +55,9 @@ export default function Login() { mt="md" /> - + diff --git a/web/app/util/api.ts b/web/app/util/api.ts index 32f381a..8d8e16a 100644 --- a/web/app/util/api.ts +++ b/web/app/util/api.ts @@ -1,5 +1,5 @@ -import { useNavigate } from "@remix-run/react"; import axios from "axios"; +import { useAuth } from "./auth"; export const client = axios.create({ baseURL: "http://localhost:8081", @@ -15,27 +15,21 @@ client.interceptors.request.use( return config; }, async (error) => { - const originalRequest = error.config; - const navigate = useNavigate(); - if (error.response.status == 401 && !originalRequest._retry) { - originalRequest._retry = true; - const refreshToken = localStorage.getItem("refresh-token"); - if (refreshToken) { - try { - const { data } = await client.post("/auth/refresh", { - refresh: refreshToken, - }); - localStorage.setItem("token", data.refreshToken); - client.defaults.headers.common[ - "Authorization" - ] = `Bearer ${data.refreshToken}`; - return client(originalRequest); - } catch (_error) { - localStorage.removeItem("token"); - localStorage.removeItem("refresh-token"); - console.log("Oh no!!!"); - navigate("/login"); - } + 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) { + console.log("ERRORRRRRRRRRRRRRR"); + clearUser(); + window.location.href = "/login"; } } return Promise.reject(error); diff --git a/web/app/util/auth.tsx b/web/app/util/auth.tsx index 00a7898..28b13ff 100644 --- a/web/app/util/auth.tsx +++ b/web/app/util/auth.tsx @@ -13,6 +13,7 @@ interface AuthContextValues { password: string; }) => void; signout: () => void; + clearUser: () => void; } const AuthContext = createContext(null); @@ -86,6 +87,10 @@ function useProvideAuth() { return await client.post("/auth/logout").then(() => handleUser(null)); }; + const clearUser = () => { + handleUser(null); + }; + useEffect(() => { client .get("/users/me") @@ -99,5 +104,6 @@ function useProvideAuth() { loading, signin, signout, + clearUser, }; } diff --git a/web/package-lock.json b/web/package-lock.json index df21cde..a49ef5d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -10,6 +10,7 @@ "@mantine/dropzone": "^7.4.0", "@mantine/form": "^7.4.0", "@mantine/hooks": "^7.4.0", + "@mantine/modals": "^7.4.0", "@mantine/notifications": "^7.4.0", "@remix-run/css-bundle": "^2.4.1", "@remix-run/node": "^2.4.1", @@ -1493,6 +1494,17 @@ "react": "^18.2.0" } }, + "node_modules/@mantine/modals": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-7.4.0.tgz", + "integrity": "sha512-uXZuN5vCx0Wdu0gOmoDaGD8/GVpx7qCeyAAFCH94WPHl/aK3fzKSk4K63deWY5Ml9a5ktic/i5pYil3MUBEj5w==", + "peerDependencies": { + "@mantine/core": "7.4.0", + "@mantine/hooks": "7.4.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "node_modules/@mantine/notifications": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.4.0.tgz", diff --git a/web/package.json b/web/package.json index e6bb5a3..1dc8620 100644 --- a/web/package.json +++ b/web/package.json @@ -16,6 +16,7 @@ "@mantine/dropzone": "^7.4.0", "@mantine/form": "^7.4.0", "@mantine/hooks": "^7.4.0", + "@mantine/modals": "^7.4.0", "@mantine/notifications": "^7.4.0", "@remix-run/css-bundle": "^2.4.1", "@remix-run/node": "^2.4.1",