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 { useUserContext } from "./context/user-context";
export const App = () => {
const { isLoggedIn } = useUserContext();
if (isLoggedIn) {
return <Navigate to="/logout" />;
}
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 { createContext, useContext } from "react";
import { useQuery } from "@tanstack/react-query";
import { useSuspenseQuery } from "@tanstack/react-query";
import axios from "axios";
const AppContext = createContext<AppContextSchema | null>(null);
@@ -10,16 +10,12 @@ export const AppContextProvider = ({
}: {
children: React.ReactNode;
}) => {
const { isPending, isError, data, error } = useQuery({
queryKey: ["status"],
const { isFetching, data, error } = useSuspenseQuery({
queryKey: ["app"],
queryFn: () => axios.get("/api/app").then((res) => res.data),
});
if (isPending) {
return;
}
if (isError) {
if (error && !isFetching) {
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;
}
}
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 { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AppContextProvider } from "./context/app-context.tsx";
import { UserContextProvider } from "./context/user-context.tsx";
const router = createBrowserRouter([
{
@@ -64,9 +65,11 @@ createRoot(document.getElementById("root")!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<AppContextProvider>
<Layout>
<RouterProvider router={router} />
</Layout>
<UserContextProvider>
<Layout>
<RouterProvider router={router} />
</Layout>
</UserContextProvider>
</AppContextProvider>
</QueryClientProvider>
</StrictMode>,

View File

@@ -6,8 +6,8 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Code } from "@/components/ui/code";
import { useAppContext } from "@/context/app-context";
import { useUserContext } from "@/context/user-context";
import { isValidUrl } from "@/lib/utils";
import { Trans, useTranslation } from "react-i18next";
import { Navigate, useNavigate } from "react-router";
@@ -16,11 +16,16 @@ export const ContinuePage = () => {
const params = new URLSearchParams(window.location.search);
const redirectURI = params.get("redirect_uri");
const { isLoggedIn } = useUserContext();
const { domain, disableContinue } = useAppContext();
const { t } = useTranslation();
const navigate = useNavigate();
if (!isLoggedIn) {
return <Navigate to="/login" />;
}
if (!redirectURI) {
return <Navigate to="/" />;
}
@@ -37,7 +42,7 @@ export const ContinuePage = () => {
if (!url.hostname.includes(domain)) {
return (
<Card className="min-w-xs md:max-w-sm">
<Card className="min-w-xs sm:min-w-sm">
<CardHeader>
<CardTitle className="text-3xl">
{t("untrustedRedirectTitle")}
@@ -47,7 +52,7 @@ export const ContinuePage = () => {
i18nKey="untrustedRedirectSubtitle"
t={t}
components={{
code: <Code />,
code: <code />,
}}
values={{ domain }}
/>
@@ -70,7 +75,7 @@ export const ContinuePage = () => {
if (url.protocol === "http:" && window.location.protocol === "https:") {
return (
<Card className="min-w-xs md:max-w-sm">
<Card className="min-w-xs sm:min-w-sm">
<CardHeader>
<CardTitle className="text-3xl">
{t("continueInsecureRedirectTitle")}
@@ -80,7 +85,7 @@ export const ContinuePage = () => {
i18nKey="continueInsecureRedirectSubtitle"
t={t}
components={{
code: <Code />,
code: <code />,
}}
/>
</CardDescription>

View File

@@ -16,6 +16,8 @@ import { useTranslation } from "react-i18next";
export const LoginPage = () => {
const { configuredProviders, title } = useAppContext();
console.log("Configured providers:", configuredProviders);
const { t } = useTranslation();
const oauthConfigured =

View File

@@ -6,18 +6,20 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Code } from "@/components/ui/code";
import { useAppContext } from "@/context/app-context";
import { useUserContext } from "@/context/user-context";
import { capitalize } from "@/lib/utils";
import { Trans, useTranslation } from "react-i18next";
import { Navigate } from "react-router";
export const LogoutPage = () => {
const { provider, username, email, isLoggedIn } = useUserContext();
const { genericName } = useAppContext();
const { t } = useTranslation();
const provider = "google";
const username = "username";
const email = "smbd@example.com";
if (!isLoggedIn) {
return <Navigate to="/login" />;
}
return (
<Card className="min-w-xs sm:min-w-sm">
@@ -29,7 +31,7 @@ export const LogoutPage = () => {
i18nKey="logoutOauthSubtitle"
t={t}
components={{
code: <Code />,
code: <code />,
}}
values={{
username: email,
@@ -42,7 +44,7 @@ export const LogoutPage = () => {
i18nKey="logoutUsernameSubtitle"
t={t}
components={{
code: <Code />,
code: <code />,
}}
values={{
username: username,

View File

@@ -6,7 +6,6 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Code } from "@/components/ui/code";
import { Trans, useTranslation } from "react-i18next";
import { Navigate, useNavigate } from "react-router";
@@ -42,7 +41,7 @@ export const UnauthorizedPage = () => {
i18nKey={i18nKey}
t={t}
components={{
code: <Code />,
code: <code />,
}}
values={{
username: username,