refactor: split app context and user context

This commit is contained in:
Stavros
2025-03-14 17:36:22 +02:00
parent 7e39cb0dfe
commit d859f74a10
20 changed files with 290 additions and 134 deletions

View File

@@ -0,0 +1,42 @@
import { useQuery } from "@tanstack/react-query";
import React, { createContext, useContext } from "react";
import axios from "axios";
import { AppContextSchemaType } from "../schemas/app-context-schema";
const AppContext = createContext<AppContextSchemaType | null>(null);
export const AppContextProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const {
data: userContext,
isLoading,
error,
} = useQuery({
queryKey: ["appContext"],
queryFn: async () => {
const res = await axios.get("/api/app");
return res.data;
},
});
if (error && !isLoading) {
throw error;
}
return (
<AppContext.Provider value={userContext}>{children}</AppContext.Provider>
);
};
export const useAppContext = () => {
const context = useContext(AppContext);
if (context === null) {
throw new Error("useAppContext must be used within an AppContextProvider");
}
return context;
};

View File

@@ -17,7 +17,7 @@ export const UserContextProvider = ({
} = useQuery({
queryKey: ["userContext"],
queryFn: async () => {
const res = await axios.get("/api/status");
const res = await axios.get("/api/user");
return res.data;
},
});

View File

@@ -16,6 +16,7 @@ import { NotFoundPage } from "./pages/not-found-page.tsx";
import { UnauthorizedPage } from "./pages/unauthorized-page.tsx";
import { InternalServerError } from "./pages/internal-server-error.tsx";
import { TotpPage } from "./pages/totp-page.tsx";
import { AppContextProvider } from "./context/app-context.tsx";
const queryClient = new QueryClient({
defaultOptions: {
@@ -30,20 +31,22 @@ createRoot(document.getElementById("root")!).render(
<MantineProvider forceColorScheme="dark">
<QueryClientProvider client={queryClient}>
<Notifications />
<UserContextProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/totp" element={<TotpPage />} />
<Route path="/logout" element={<LogoutPage />} />
<Route path="/continue" element={<ContinuePage />} />
<Route path="/unauthorized" element={<UnauthorizedPage />} />
<Route path="/error" element={<InternalServerError />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
</UserContextProvider>
<AppContextProvider>
<UserContextProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/totp" element={<TotpPage />} />
<Route path="/logout" element={<LogoutPage />} />
<Route path="/continue" element={<ContinuePage />} />
<Route path="/unauthorized" element={<UnauthorizedPage />} />
<Route path="/error" element={<InternalServerError />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
</UserContextProvider>
</AppContextProvider>
</QueryClientProvider>
</MantineProvider>
</StrictMode>,

View File

@@ -5,13 +5,15 @@ import { useUserContext } from "../context/user-context";
import { Layout } from "../components/layouts/layout";
import { ReactNode } from "react";
import { isQueryValid } from "../utils/utils";
import { useAppContext } from "../context/app-context";
export const ContinuePage = () => {
const queryString = window.location.search;
const params = new URLSearchParams(queryString);
const redirectUri = params.get("redirect_uri") ?? "";
const { isLoggedIn, disableContinue } = useUserContext();
const { isLoggedIn } = useUserContext();
const { disableContinue } = useAppContext();
if (!isLoggedIn) {
return <Navigate to={`/login?redirect_uri=${redirectUri}`} />;

View File

@@ -9,14 +9,15 @@ import { OAuthButtons } from "../components/auth/oauth-buttons";
import { LoginFormValues } from "../schemas/login-schema";
import { LoginForm } from "../components/auth/login-forn";
import { isQueryValid } from "../utils/utils";
import { useAppContext } from "../context/app-context";
export const LoginPage = () => {
const queryString = window.location.search;
const params = new URLSearchParams(queryString);
const redirectUri = params.get("redirect_uri") ?? "";
const { isLoggedIn, configuredProviders, title, genericName } =
useUserContext();
const { isLoggedIn } = useUserContext();
const { configuredProviders, title, genericName } = useAppContext();
const oauthProviders = configuredProviders.filter(
(value) => value !== "username",

View File

@@ -6,9 +6,11 @@ import { useUserContext } from "../context/user-context";
import { Navigate } from "react-router";
import { Layout } from "../components/layouts/layout";
import { capitalize } from "../utils/utils";
import { useAppContext } from "../context/app-context";
export const LogoutPage = () => {
const { isLoggedIn, username, oauth, provider, genericName } = useUserContext();
const { isLoggedIn, username, oauth, provider } = useUserContext();
const { genericName } = useAppContext();
if (!isLoggedIn) {
return <Navigate to="/login" />;
@@ -45,8 +47,9 @@ export const LogoutPage = () => {
</Text>
<Text>
You are currently logged in as <Code>{username}</Code>
{oauth && ` using ${capitalize(provider === "generic" ? genericName : provider)} OAuth`}. Click the button
below to log out.
{oauth &&
` using ${capitalize(provider === "generic" ? genericName : provider)} OAuth`}
. Click the button below to log out.
</Text>
<Button
fullWidth

View File

@@ -6,13 +6,15 @@ import { TotpForm } from "../components/auth/totp-form";
import { useMutation } from "@tanstack/react-query";
import axios from "axios";
import { notifications } from "@mantine/notifications";
import { useAppContext } from "../context/app-context";
export const TotpPage = () => {
const queryString = window.location.search;
const params = new URLSearchParams(queryString);
const redirectUri = params.get("redirect_uri") ?? "";
const { totpPending, isLoggedIn, title } = useUserContext();
const { totpPending, isLoggedIn } = useUserContext();
const { title } = useAppContext();
if (isLoggedIn) {
return <Navigate to={`/logout`} />;

View File

@@ -0,0 +1,10 @@
import { z } from "zod";
export const appContextSchema = z.object({
configuredProviders: z.array(z.string()),
disableContinue: z.boolean(),
title: z.string(),
genericName: z.string(),
});
export type AppContextSchemaType = z.infer<typeof appContextSchema>;

View File

@@ -5,10 +5,6 @@ export const userContextSchema = z.object({
username: z.string(),
oauth: z.boolean(),
provider: z.string(),
configuredProviders: z.array(z.string()),
disableContinue: z.boolean(),
title: z.string(),
genericName: z.string(),
totpPending: z.boolean(),
});

View File

@@ -4,4 +4,14 @@ import react from "@vitejs/plugin-react-swc";
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: "0.0.0.0",
proxy: {
"/api": {
target: "http://tinyauth-backend:3000/api",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
}
}
});