From 011151227a6c5a0bc612cf281fad12e36166fa2d Mon Sep 17 00:00:00 2001 From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com> Date: Thu, 20 Jun 2024 08:10:11 +0200 Subject: [PATCH] Very basic routing for login/logout. --- package-lock.json | 31 +++++++---------- package.json | 5 +-- src/RouterContext.ts | 5 +++ src/auth.tsx | 35 +++++++++++++++++++ src/main.tsx | 17 ++++++++-- src/routeTree.gen.ts | 69 ++++++++++++++++++++++++++++---------- src/routes/__root.tsx | 37 +++++++++++++++++--- src/routes/_auth.tsx | 17 ++++++++++ src/routes/_auth/index.tsx | 19 +++++++++++ src/routes/index.lazy.tsx | 14 -------- src/routes/login.tsx | 39 +++++++++++++++++++++ 11 files changed, 229 insertions(+), 59 deletions(-) create mode 100644 src/RouterContext.ts create mode 100644 src/auth.tsx create mode 100644 src/routes/_auth.tsx create mode 100644 src/routes/_auth/index.tsx delete mode 100644 src/routes/index.lazy.tsx create mode 100644 src/routes/login.tsx diff --git a/package-lock.json b/package-lock.json index 0bae022..809dae7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,12 @@ "dependencies": { "@tanstack/react-query": "^5.45.1", "@tanstack/react-router": "^1.38.1", + "@tanstack/router-devtools": "^1.39.4", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "zod": "^3.23.8" }, "devDependencies": { - "@tanstack/router-devtools": "^1.38.1", "@tanstack/router-vite-plugin": "^1.39.1", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", @@ -513,7 +514,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1416,9 +1416,9 @@ } }, "node_modules/@tanstack/react-router": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.38.1.tgz", - "integrity": "sha512-ET/dJeNNUOYIRgfuadjXA3tL1Kqy8RF5P2+SNiIH+JU5ckibO/K4ZY3RIZ5O9jOXR5jg3vSgQd96EG8k87lgzg==", + "version": "1.39.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.39.4.tgz", + "integrity": "sha512-Pby5MI1NREiXcX532HqQIa9bzwqxFypnqDiPIbywNP1BXufvCuzRh1ZQX/IxxYLeqjANE2gjTmAGHeyrEoh1kg==", "dependencies": { "@tanstack/history": "1.31.16", "@tanstack/react-store": "^0.2.1", @@ -1455,10 +1455,9 @@ } }, "node_modules/@tanstack/router-devtools": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/@tanstack/router-devtools/-/router-devtools-1.38.1.tgz", - "integrity": "sha512-HvSmQtvyRs+JMySJe+cLtYwE3xsdqNQlE7G0Erty+3mfXLH5HXyQkxL+tfj9fuiLx8PtbNrMJ6oskfB9ctXxTQ==", - "dev": true, + "version": "1.39.4", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools/-/router-devtools-1.39.4.tgz", + "integrity": "sha512-wOCx6CUaF/YEbKJXE/9iyT8kDmKb4528XtNaAm6nmCByi30+WPA6dVlqg2X8PNlbIBzeguz6DEg9X8YlyKf7qA==", "dependencies": { "clsx": "^2.1.0", "date-fns": "^2.29.1", @@ -1472,7 +1471,7 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-router": "^1.38.1", + "@tanstack/react-router": "^1.39.4", "react": ">=16.8", "react-dom": ">=16.8" } @@ -1995,7 +1994,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, "engines": { "node": ">=6" } @@ -2044,14 +2042,12 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -2686,7 +2682,6 @@ "version": "2.1.14", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", - "dev": true, "peerDependencies": { "csstype": "^3.0.10" } @@ -3235,8 +3230,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/resolve-from": { "version": "4.0.0", @@ -3666,7 +3660,6 @@ "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", - "dev": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index fe6a26c..b631a4f 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,14 @@ "preview": "vite preview" }, "dependencies": { + "@tanstack/router-devtools": "^1.39.4", "@tanstack/react-query": "^5.45.1", "@tanstack/react-router": "^1.38.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "zod": "^3.23.8" }, "devDependencies": { - "@tanstack/router-devtools": "^1.38.1", "@tanstack/router-vite-plugin": "^1.39.1", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", diff --git a/src/RouterContext.ts b/src/RouterContext.ts new file mode 100644 index 0000000..7d71095 --- /dev/null +++ b/src/RouterContext.ts @@ -0,0 +1,5 @@ +import { AuthContextType } from './auth' + +export default interface RouterContext { + auth: AuthContextType +} diff --git a/src/auth.tsx b/src/auth.tsx new file mode 100644 index 0000000..101468e --- /dev/null +++ b/src/auth.tsx @@ -0,0 +1,35 @@ +import React, { useContext, createContext, useState } from 'react' + +export interface AuthContextType { + isAuthenticated: boolean + login(): void + logout(): void +} + +const AuthContext = createContext(null) + +export const AuthProvider = ({ children }: React.PropsWithChildren) => { + const [isAuthenticated, setIsAuthenticated] = useState(false) + + const login: AuthContextType['login'] = () => { + setIsAuthenticated(true) + } + + const logout: AuthContextType['logout'] = () => { + setIsAuthenticated(false) + } + + return ( + + {children} + + ) +} + +export const useAuth = () => { + const auth = useContext(AuthContext) + if (!auth) { + throw new Error('useAuth must be used in an AuthProvider context') + } + return auth +} diff --git a/src/main.tsx b/src/main.tsx index fbacfcc..fd9c265 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,9 +3,15 @@ import ReactDOM from 'react-dom/client' import { routeTree } from './routeTree.gen' import { RouterProvider, createRouter } from '@tanstack/react-router' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { AuthProvider, useAuth } from './auth' // Create router -const router = createRouter({ routeTree }) +const router = createRouter({ + context: { + auth: undefined! + }, + routeTree +}) // Create queryClient const queryClient = new QueryClient() @@ -17,10 +23,17 @@ declare module '@tanstack/react-router' { } } +const InnerApp = () => { + const auth = useAuth() + return +} + ReactDOM.createRoot(document.getElementById('root')!).render( - + + + ) diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index e8428b4..a06436f 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -8,40 +8,64 @@ // This file is auto-generated by TanStack Router -import { createFileRoute } from '@tanstack/react-router' - // Import Routes import { Route as rootRoute } from './routes/__root' - -// Create Virtual Routes - -const IndexLazyImport = createFileRoute('/')() +import { Route as LoginImport } from './routes/login' +import { Route as AuthImport } from './routes/_auth' +import { Route as AuthIndexImport } from './routes/_auth/index' // Create/Update Routes -const IndexLazyRoute = IndexLazyImport.update({ - path: '/', +const LoginRoute = LoginImport.update({ + path: '/login', getParentRoute: () => rootRoute, -} as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route)) +} as any) + +const AuthRoute = AuthImport.update({ + id: '/_auth', + getParentRoute: () => rootRoute, +} as any) + +const AuthIndexRoute = AuthIndexImport.update({ + path: '/', + getParentRoute: () => AuthRoute, +} as any) // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' + '/_auth': { + id: '/_auth' + path: '' + fullPath: '' + preLoaderRoute: typeof AuthImport + parentRoute: typeof rootRoute + } + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginImport + parentRoute: typeof rootRoute + } + '/_auth/': { + id: '/_auth/' path: '/' fullPath: '/' - preLoaderRoute: typeof IndexLazyImport - parentRoute: typeof rootRoute + preLoaderRoute: typeof AuthIndexImport + parentRoute: typeof AuthImport } } } // Create and export the route tree -export const routeTree = rootRoute.addChildren({ IndexLazyRoute }) +export const routeTree = rootRoute.addChildren({ + AuthRoute: AuthRoute.addChildren({ AuthIndexRoute }), + LoginRoute, +}) /* prettier-ignore-end */ @@ -51,11 +75,22 @@ export const routeTree = rootRoute.addChildren({ IndexLazyRoute }) "__root__": { "filePath": "__root.tsx", "children": [ - "/" + "/_auth", + "/login" ] }, - "/": { - "filePath": "index.lazy.tsx" + "/_auth": { + "filePath": "_auth.tsx", + "children": [ + "/_auth/" + ] + }, + "/login": { + "filePath": "login.tsx" + }, + "/_auth/": { + "filePath": "_auth/index.tsx", + "parent": "/_auth" } } } diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index beb184c..86917c3 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,10 +1,37 @@ -import { Outlet, createRootRoute } from '@tanstack/react-router' +import { + Outlet, + createRootRouteWithContext, + useRouteContext, + useRouter +} from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/router-devtools' +import RouterContext from '../RouterContext' -export const Route = createRootRoute({ - component: () => ( +const RootLayout = () => { + const logout = useRouteContext({ + from: '__root__', + select: s => s.auth.logout + }) + const router = useRouter() + + const onLogout = async () => { + logout() + await router.invalidate() + router.navigate({ to: '/login' }) + } + + return ( <> -

Hello, World.

- +
+

Hello, World.

+ + +
+ ) +} + +export const Route = createRootRouteWithContext()({ + component: RootLayout }) diff --git a/src/routes/_auth.tsx b/src/routes/_auth.tsx new file mode 100644 index 0000000..e5a27c0 --- /dev/null +++ b/src/routes/_auth.tsx @@ -0,0 +1,17 @@ +import { Outlet, createFileRoute, redirect } from '@tanstack/react-router' + +export const Route = createFileRoute('/_auth')({ + beforeLoad({ context, location }) { + if (!context.auth.isAuthenticated) { + throw redirect({ + to: '/login', + search: { + redirect: location.href + } + }) + } + }, + component: () => { + return + } +}) diff --git a/src/routes/_auth/index.tsx b/src/routes/_auth/index.tsx new file mode 100644 index 0000000..74b996d --- /dev/null +++ b/src/routes/_auth/index.tsx @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_auth/')({ + component: () => { + const client = useQuery({ + queryKey: ['index'], + queryFn() { + return 'Hello, Jesse!' + } + }) + return ( +
+

Index Page – You are logged in.

+ {client.data ?

{client.data}

: null} +
+ ) + } +}) diff --git a/src/routes/index.lazy.tsx b/src/routes/index.lazy.tsx deleted file mode 100644 index 3abb837..0000000 --- a/src/routes/index.lazy.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { createLazyFileRoute } from '@tanstack/react-router' - -export const Route = createLazyFileRoute('/')({ - component: () => { - const client = useQuery({ - queryKey: ['index'], - queryFn() { - return 'Hello, Jesse!' - } - }) - return
{client.data ?

{client.data}

: null}
- } -}) diff --git a/src/routes/login.tsx b/src/routes/login.tsx new file mode 100644 index 0000000..44d4fc3 --- /dev/null +++ b/src/routes/login.tsx @@ -0,0 +1,39 @@ +import { + createFileRoute, + redirect, + useRouteContext, + useRouter, + useSearch +} from '@tanstack/react-router' +import { z } from 'zod' + +const Login = () => { + const login = useRouteContext({ from: '/login', select: s => s.auth.login }) + const router = useRouter() + const search = useSearch({ from: '/login' }) + + const onLogin = async () => { + login() + await router.invalidate() + router.navigate({ to: search.redirect || '/' }) + } + + return ( +
+

Login Page

+ +
+ ) +} + +export const Route = createFileRoute('/login')({ + validateSearch: z.object({ + redirect: z.string().optional().catch('') + }), + beforeLoad({ context, search }) { + if (context.auth.isAuthenticated) { + throw redirect({ to: search.redirect || '/' }) + } + }, + component: Login +})