mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-11-01 14:45:47 +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