feat: user context

This commit is contained in:
Stavros
2025-05-09 17:39:14 +03:00
parent 31a7b0ff06
commit 41c63e5b49
10 changed files with 129 additions and 46 deletions

View File

@@ -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" />;
}; };

View File

@@ -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 };

View File

@@ -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;
} }

View 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;
};

View File

@@ -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;
}

View File

@@ -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>,

View File

@@ -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>

View File

@@ -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 =

View File

@@ -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,

View File

@@ -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,