Very basic routing for login/logout.

This commit is contained in:
JesseBrault0709 2024-06-20 08:10:11 +02:00
parent 22c874b5ba
commit 011151227a
11 changed files with 229 additions and 59 deletions

31
package-lock.json generated
View File

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

View File

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

5
src/RouterContext.ts Normal file
View File

@ -0,0 +1,5 @@
import { AuthContextType } from './auth'
export default interface RouterContext {
auth: AuthContextType
}

35
src/auth.tsx Normal file
View File

@ -0,0 +1,35 @@
import React, { useContext, createContext, useState } from 'react'
export interface AuthContextType {
isAuthenticated: boolean
login(): void
logout(): void
}
const AuthContext = createContext<AuthContextType | null>(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 (
<AuthContext.Provider value={{ isAuthenticated, login, logout }}>
{children}
</AuthContext.Provider>
)
}
export const useAuth = () => {
const auth = useContext(AuthContext)
if (!auth) {
throw new Error('useAuth must be used in an AuthProvider context')
}
return auth
}

View File

@ -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 <RouterProvider router={router} context={{ auth }}></RouterProvider>
}
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<AuthProvider>
<InnerApp />
</AuthProvider>
</QueryClientProvider>
</React.StrictMode>
)

View File

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

View File

@ -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 (
<>
<h1>Hello, World.</h1>
<Outlet />
<div>
<h1>Hello, World.</h1>
<button onClick={onLogout}>Logout</button>
<Outlet />
</div>
<TanStackRouterDevtools position="bottom-right" />
</>
)
}
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootLayout
})

17
src/routes/_auth.tsx Normal file
View File

@ -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 <Outlet />
}
})

View File

@ -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 (
<div>
<h2>Index Page You are logged in.</h2>
{client.data ? <h3>{client.data}</h3> : null}
</div>
)
}
})

View File

@ -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 <div>{client.data ? <h2>{client.data}</h2> : null}</div>
}
})

39
src/routes/login.tsx Normal file
View File

@ -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 (
<div>
<h2>Login Page</h2>
<button onClick={onLogin}>Login</button>
</div>
)
}
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
})