Very basic routing for login/logout.
This commit is contained in:
parent
22c874b5ba
commit
011151227a
31
package-lock.json
generated
31
package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
@ -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
5
src/RouterContext.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { AuthContextType } from './auth'
|
||||
|
||||
export default interface RouterContext {
|
||||
auth: AuthContextType
|
||||
}
|
35
src/auth.tsx
Normal file
35
src/auth.tsx
Normal 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
|
||||
}
|
17
src/main.tsx
17
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 <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>
|
||||
)
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
17
src/routes/_auth.tsx
Normal 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 />
|
||||
}
|
||||
})
|
19
src/routes/_auth/index.tsx
Normal file
19
src/routes/_auth/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
})
|
@ -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
39
src/routes/login.tsx
Normal 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
|
||||
})
|
Loading…
Reference in New Issue
Block a user