mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-03 23:55:44 +00:00 
			
		
		
		
	feat: user context
This commit is contained in:
		@@ -1,5 +1,12 @@
 | 
				
			|||||||
import { Navigate } from "react-router";
 | 
					import { Navigate } from "react-router";
 | 
				
			||||||
 | 
					import { useUserContext } from "./context/user-context";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const App = () => {
 | 
					export const App = () => {
 | 
				
			||||||
 | 
					  const { isLoggedIn } = useUserContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isLoggedIn) {
 | 
				
			||||||
 | 
					    return <Navigate to="/logout" />;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return <Navigate to="/login" />;
 | 
					  return <Navigate to="/login" />;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
import { twMerge } from "tailwind-merge";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface CodeProps extends React.ComponentPropsWithoutRef<"code"> {
 | 
					 | 
				
			||||||
  children?: React.ReactNode;
 | 
					 | 
				
			||||||
  className?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function Code({ children, className, ...props }: CodeProps) {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <code
 | 
					 | 
				
			||||||
      className={twMerge(
 | 
					 | 
				
			||||||
        "relative rounded bg-muted px-[0.2rem] py-[0.1rem] font-mono text-sm font-semibold",
 | 
					 | 
				
			||||||
        className,
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
      {...props}
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      {children}
 | 
					 | 
				
			||||||
    </code>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { Code };
 | 
					 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { AppContextSchema } from "@/schemas/app-context-schema";
 | 
					import { AppContextSchema } from "@/schemas/app-context-schema";
 | 
				
			||||||
import { createContext, useContext } from "react";
 | 
					import { createContext, useContext } from "react";
 | 
				
			||||||
import { useQuery } from "@tanstack/react-query";
 | 
					import { useSuspenseQuery } from "@tanstack/react-query";
 | 
				
			||||||
import axios from "axios";
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const AppContext = createContext<AppContextSchema | null>(null);
 | 
					const AppContext = createContext<AppContextSchema | null>(null);
 | 
				
			||||||
@@ -10,16 +10,12 @@ export const AppContextProvider = ({
 | 
				
			|||||||
}: {
 | 
					}: {
 | 
				
			||||||
  children: React.ReactNode;
 | 
					  children: React.ReactNode;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const { isPending, isError, data, error } = useQuery({
 | 
					  const { isFetching, data, error } = useSuspenseQuery({
 | 
				
			||||||
    queryKey: ["status"],
 | 
					    queryKey: ["app"],
 | 
				
			||||||
    queryFn: () => axios.get("/api/app").then((res) => res.data),
 | 
					    queryFn: () => axios.get("/api/app").then((res) => res.data),
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (isPending) {
 | 
					  if (error && !isFetching) {
 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (isError) {
 | 
					 | 
				
			||||||
    throw error;
 | 
					    throw error;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								frontend/src/context/user-context.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								frontend/src/context/user-context.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import { UserContextSchema } from "@/schemas/user-context-schema";
 | 
				
			||||||
 | 
					import { createContext, useContext } from "react";
 | 
				
			||||||
 | 
					import { useSuspenseQuery } from "@tanstack/react-query";
 | 
				
			||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const UserContext = createContext<UserContextSchema | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const UserContextProvider = ({
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  children: React.ReactNode;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const { isFetching, data, error } = useSuspenseQuery({
 | 
				
			||||||
 | 
					    queryKey: ["user"],
 | 
				
			||||||
 | 
					    queryFn: () => axios.get("/api/user").then((res) => res.data),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (error && !isFetching) {
 | 
				
			||||||
 | 
					    throw error;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return <UserContext.Provider value={data}>{children}</UserContext.Provider>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useUserContext = () => {
 | 
				
			||||||
 | 
					  const context = useContext(UserContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!context) {
 | 
				
			||||||
 | 
					    throw new Error(
 | 
				
			||||||
 | 
					      "useUserContext must be used within an UserContextProvider",
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return context;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -118,3 +118,59 @@
 | 
				
			|||||||
    @apply bg-background text-foreground;
 | 
					    @apply bg-background text-foreground;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 {
 | 
				
			||||||
 | 
					  @apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h2 {
 | 
				
			||||||
 | 
					  @apply scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h3 {
 | 
				
			||||||
 | 
					  @apply scroll-m-20 text-2xl font-semibold tracking-tight;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h4 {
 | 
				
			||||||
 | 
					  @apply scroll-m-20 text-xl font-semibold tracking-tight;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p {
 | 
				
			||||||
 | 
					  @apply leading-7 [&:not(:first-child)]:mt-6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					blockquote {
 | 
				
			||||||
 | 
					  @apply mt-6 border-l-2 pl-6 italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tr {
 | 
				
			||||||
 | 
					  @apply m-0 border-t p-0 even:bg-muted;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th {
 | 
				
			||||||
 | 
					  @apply border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul {
 | 
				
			||||||
 | 
					  @apply my-6 ml-6 list-disc [&>li]:mt-2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					code {
 | 
				
			||||||
 | 
					  @apply relative rounded bg-muted px-[0.2rem] py-[0.1rem] font-mono text-sm font-semibold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.lead {
 | 
				
			||||||
 | 
					  @apply text-xl text-muted-foreground;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.large {
 | 
				
			||||||
 | 
					  @apply text-lg font-semibold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					small {
 | 
				
			||||||
 | 
					  @apply text-sm font-medium leading-none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.muted {
 | 
				
			||||||
 | 
					  @apply text-sm text-muted-foreground;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ import { LogoutPage } from "./pages/logout-page.tsx";
 | 
				
			|||||||
import { UnauthorizedPage } from "./pages/unauthorized-page.tsx";
 | 
					import { UnauthorizedPage } from "./pages/unauthorized-page.tsx";
 | 
				
			||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 | 
					import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 | 
				
			||||||
import { AppContextProvider } from "./context/app-context.tsx";
 | 
					import { AppContextProvider } from "./context/app-context.tsx";
 | 
				
			||||||
 | 
					import { UserContextProvider } from "./context/user-context.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = createBrowserRouter([
 | 
					const router = createBrowserRouter([
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -64,9 +65,11 @@ createRoot(document.getElementById("root")!).render(
 | 
				
			|||||||
  <StrictMode>
 | 
					  <StrictMode>
 | 
				
			||||||
    <QueryClientProvider client={queryClient}>
 | 
					    <QueryClientProvider client={queryClient}>
 | 
				
			||||||
      <AppContextProvider>
 | 
					      <AppContextProvider>
 | 
				
			||||||
        <Layout>
 | 
					        <UserContextProvider>
 | 
				
			||||||
          <RouterProvider router={router} />
 | 
					          <Layout>
 | 
				
			||||||
        </Layout>
 | 
					            <RouterProvider router={router} />
 | 
				
			||||||
 | 
					          </Layout>
 | 
				
			||||||
 | 
					        </UserContextProvider>
 | 
				
			||||||
      </AppContextProvider>
 | 
					      </AppContextProvider>
 | 
				
			||||||
    </QueryClientProvider>
 | 
					    </QueryClientProvider>
 | 
				
			||||||
  </StrictMode>,
 | 
					  </StrictMode>,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,8 @@ import {
 | 
				
			|||||||
  CardHeader,
 | 
					  CardHeader,
 | 
				
			||||||
  CardTitle,
 | 
					  CardTitle,
 | 
				
			||||||
} from "@/components/ui/card";
 | 
					} from "@/components/ui/card";
 | 
				
			||||||
import { Code } from "@/components/ui/code";
 | 
					 | 
				
			||||||
import { useAppContext } from "@/context/app-context";
 | 
					import { useAppContext } from "@/context/app-context";
 | 
				
			||||||
 | 
					import { useUserContext } from "@/context/user-context";
 | 
				
			||||||
import { isValidUrl } from "@/lib/utils";
 | 
					import { isValidUrl } from "@/lib/utils";
 | 
				
			||||||
import { Trans, useTranslation } from "react-i18next";
 | 
					import { Trans, useTranslation } from "react-i18next";
 | 
				
			||||||
import { Navigate, useNavigate } from "react-router";
 | 
					import { Navigate, useNavigate } from "react-router";
 | 
				
			||||||
@@ -16,11 +16,16 @@ export const ContinuePage = () => {
 | 
				
			|||||||
  const params = new URLSearchParams(window.location.search);
 | 
					  const params = new URLSearchParams(window.location.search);
 | 
				
			||||||
  const redirectURI = params.get("redirect_uri");
 | 
					  const redirectURI = params.get("redirect_uri");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { isLoggedIn } = useUserContext();
 | 
				
			||||||
  const { domain, disableContinue } = useAppContext();
 | 
					  const { domain, disableContinue } = useAppContext();
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!isLoggedIn) {
 | 
				
			||||||
 | 
					    return <Navigate to="/login" />;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!redirectURI) {
 | 
					  if (!redirectURI) {
 | 
				
			||||||
    return <Navigate to="/" />;
 | 
					    return <Navigate to="/" />;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -37,7 +42,7 @@ export const ContinuePage = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  if (!url.hostname.includes(domain)) {
 | 
					  if (!url.hostname.includes(domain)) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Card className="min-w-xs md:max-w-sm">
 | 
					      <Card className="min-w-xs sm:min-w-sm">
 | 
				
			||||||
        <CardHeader>
 | 
					        <CardHeader>
 | 
				
			||||||
          <CardTitle className="text-3xl">
 | 
					          <CardTitle className="text-3xl">
 | 
				
			||||||
            {t("untrustedRedirectTitle")}
 | 
					            {t("untrustedRedirectTitle")}
 | 
				
			||||||
@@ -47,7 +52,7 @@ export const ContinuePage = () => {
 | 
				
			|||||||
              i18nKey="untrustedRedirectSubtitle"
 | 
					              i18nKey="untrustedRedirectSubtitle"
 | 
				
			||||||
              t={t}
 | 
					              t={t}
 | 
				
			||||||
              components={{
 | 
					              components={{
 | 
				
			||||||
                code: <Code />,
 | 
					                code: <code />,
 | 
				
			||||||
              }}
 | 
					              }}
 | 
				
			||||||
              values={{ domain }}
 | 
					              values={{ domain }}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
@@ -70,7 +75,7 @@ export const ContinuePage = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  if (url.protocol === "http:" && window.location.protocol === "https:") {
 | 
					  if (url.protocol === "http:" && window.location.protocol === "https:") {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Card className="min-w-xs md:max-w-sm">
 | 
					      <Card className="min-w-xs sm:min-w-sm">
 | 
				
			||||||
        <CardHeader>
 | 
					        <CardHeader>
 | 
				
			||||||
          <CardTitle className="text-3xl">
 | 
					          <CardTitle className="text-3xl">
 | 
				
			||||||
            {t("continueInsecureRedirectTitle")}
 | 
					            {t("continueInsecureRedirectTitle")}
 | 
				
			||||||
@@ -80,7 +85,7 @@ export const ContinuePage = () => {
 | 
				
			|||||||
              i18nKey="continueInsecureRedirectSubtitle"
 | 
					              i18nKey="continueInsecureRedirectSubtitle"
 | 
				
			||||||
              t={t}
 | 
					              t={t}
 | 
				
			||||||
              components={{
 | 
					              components={{
 | 
				
			||||||
                code: <Code />,
 | 
					                code: <code />,
 | 
				
			||||||
              }}
 | 
					              }}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          </CardDescription>
 | 
					          </CardDescription>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,8 @@ import { useTranslation } from "react-i18next";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const LoginPage = () => {
 | 
					export const LoginPage = () => {
 | 
				
			||||||
  const { configuredProviders, title } = useAppContext();
 | 
					  const { configuredProviders, title } = useAppContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log("Configured providers:", configuredProviders);
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const oauthConfigured =
 | 
					  const oauthConfigured =
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,18 +6,20 @@ import {
 | 
				
			|||||||
  CardHeader,
 | 
					  CardHeader,
 | 
				
			||||||
  CardTitle,
 | 
					  CardTitle,
 | 
				
			||||||
} from "@/components/ui/card";
 | 
					} from "@/components/ui/card";
 | 
				
			||||||
import { Code } from "@/components/ui/code";
 | 
					 | 
				
			||||||
import { useAppContext } from "@/context/app-context";
 | 
					import { useAppContext } from "@/context/app-context";
 | 
				
			||||||
 | 
					import { useUserContext } from "@/context/user-context";
 | 
				
			||||||
import { capitalize } from "@/lib/utils";
 | 
					import { capitalize } from "@/lib/utils";
 | 
				
			||||||
import { Trans, useTranslation } from "react-i18next";
 | 
					import { Trans, useTranslation } from "react-i18next";
 | 
				
			||||||
 | 
					import { Navigate } from "react-router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const LogoutPage = () => {
 | 
					export const LogoutPage = () => {
 | 
				
			||||||
 | 
					  const { provider, username, email, isLoggedIn } = useUserContext();
 | 
				
			||||||
  const { genericName } = useAppContext();
 | 
					  const { genericName } = useAppContext();
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const provider = "google";
 | 
					  if (!isLoggedIn) {
 | 
				
			||||||
  const username = "username";
 | 
					    return <Navigate to="/login" />;
 | 
				
			||||||
  const email = "smbd@example.com";
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Card className="min-w-xs sm:min-w-sm">
 | 
					    <Card className="min-w-xs sm:min-w-sm">
 | 
				
			||||||
@@ -29,7 +31,7 @@ export const LogoutPage = () => {
 | 
				
			|||||||
              i18nKey="logoutOauthSubtitle"
 | 
					              i18nKey="logoutOauthSubtitle"
 | 
				
			||||||
              t={t}
 | 
					              t={t}
 | 
				
			||||||
              components={{
 | 
					              components={{
 | 
				
			||||||
                code: <Code />,
 | 
					                code: <code />,
 | 
				
			||||||
              }}
 | 
					              }}
 | 
				
			||||||
              values={{
 | 
					              values={{
 | 
				
			||||||
                username: email,
 | 
					                username: email,
 | 
				
			||||||
@@ -42,7 +44,7 @@ export const LogoutPage = () => {
 | 
				
			|||||||
              i18nKey="logoutUsernameSubtitle"
 | 
					              i18nKey="logoutUsernameSubtitle"
 | 
				
			||||||
              t={t}
 | 
					              t={t}
 | 
				
			||||||
              components={{
 | 
					              components={{
 | 
				
			||||||
                code: <Code />,
 | 
					                code: <code />,
 | 
				
			||||||
              }}
 | 
					              }}
 | 
				
			||||||
              values={{
 | 
					              values={{
 | 
				
			||||||
                username: username,
 | 
					                username: username,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,6 @@ import {
 | 
				
			|||||||
  CardHeader,
 | 
					  CardHeader,
 | 
				
			||||||
  CardTitle,
 | 
					  CardTitle,
 | 
				
			||||||
} from "@/components/ui/card";
 | 
					} from "@/components/ui/card";
 | 
				
			||||||
import { Code } from "@/components/ui/code";
 | 
					 | 
				
			||||||
import { Trans, useTranslation } from "react-i18next";
 | 
					import { Trans, useTranslation } from "react-i18next";
 | 
				
			||||||
import { Navigate, useNavigate } from "react-router";
 | 
					import { Navigate, useNavigate } from "react-router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -42,7 +41,7 @@ export const UnauthorizedPage = () => {
 | 
				
			|||||||
            i18nKey={i18nKey}
 | 
					            i18nKey={i18nKey}
 | 
				
			||||||
            t={t}
 | 
					            t={t}
 | 
				
			||||||
            components={{
 | 
					            components={{
 | 
				
			||||||
              code: <Code />,
 | 
					              code: <code />,
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
            values={{
 | 
					            values={{
 | 
				
			||||||
              username: username,
 | 
					              username: username,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user