Basic login working with backend.
This commit is contained in:
parent
011151227a
commit
c97fdc76e5
58
src/auth.tsx
58
src/auth.tsx
@ -1,26 +1,70 @@
|
||||
import React, { useContext, createContext, useState } from 'react'
|
||||
|
||||
export interface AuthContextType {
|
||||
isAuthenticated: boolean
|
||||
login(): void
|
||||
token: string | null
|
||||
error: string | null
|
||||
login(username: string, password: string): Promise<boolean>
|
||||
logout(): void
|
||||
}
|
||||
|
||||
interface LoginView {
|
||||
username: string
|
||||
accessToken: string
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | null>(null)
|
||||
|
||||
export const AuthProvider = ({ children }: React.PropsWithChildren) => {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
const [token, setToken] = useState<string | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const login: AuthContextType['login'] = () => {
|
||||
setIsAuthenticated(true)
|
||||
const login: AuthContextType['login'] = async (username, password) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
import.meta.env.VITE_MME_API_URL + '/auth/login',
|
||||
{
|
||||
body: JSON.stringify({ username, password }),
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors'
|
||||
}
|
||||
)
|
||||
if (response.ok) {
|
||||
const body = (await response.json()) as LoginView
|
||||
setToken(body.accessToken)
|
||||
setError(null)
|
||||
return true
|
||||
} else {
|
||||
setToken(null)
|
||||
if (response.status === 401) {
|
||||
setError('Invalid username or password.')
|
||||
} else if (response.status === 500) {
|
||||
setError(
|
||||
'There was an internal server error. Please try again later.'
|
||||
)
|
||||
} else {
|
||||
setError('Unknown error.')
|
||||
console.error(
|
||||
`Unknown error: ${response.status} ${response.statusText}`
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
} catch (fetchError) {
|
||||
setError('Network error. Please try again later.')
|
||||
console.error(`Unknown error: ${fetchError}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout: AuthContextType['logout'] = () => {
|
||||
setIsAuthenticated(false)
|
||||
setToken(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ isAuthenticated, login, logout }}>
|
||||
<AuthContext.Provider value={{ token, error, login, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ import { Outlet, createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_auth')({
|
||||
beforeLoad({ context, location }) {
|
||||
if (!context.auth.isAuthenticated) {
|
||||
if (!context.auth.token) {
|
||||
throw redirect({
|
||||
to: '/login',
|
||||
search: {
|
||||
|
@ -5,23 +5,41 @@ import {
|
||||
useRouter,
|
||||
useSearch
|
||||
} from '@tanstack/react-router'
|
||||
import { FormEvent } from 'react'
|
||||
import { z } from 'zod'
|
||||
|
||||
const Login = () => {
|
||||
const login = useRouteContext({ from: '/login', select: s => s.auth.login })
|
||||
const error = useRouteContext({ from: '/login', select: s => s.auth.error })
|
||||
const router = useRouter()
|
||||
const search = useSearch({ from: '/login' })
|
||||
|
||||
const onLogin = async () => {
|
||||
login()
|
||||
await router.invalidate()
|
||||
router.navigate({ to: search.redirect || '/' })
|
||||
const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
const formData = new FormData(event.currentTarget)
|
||||
const username = (formData.get('username') as string | null) ?? ''
|
||||
const password = (formData.get('password') as string | null) ?? ''
|
||||
const success = await login(username, password)
|
||||
if (success) {
|
||||
await router.invalidate()
|
||||
router.navigate({ to: search.redirect || '/' })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Login Page</h2>
|
||||
<button onClick={onLogin}>Login</button>
|
||||
<form onSubmit={onSubmit}>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input id="username" name="username" type="text" />
|
||||
|
||||
<label htmlFor="password">Password</label>
|
||||
<input id="password" name="password" type="password" />
|
||||
|
||||
<input type="submit" />
|
||||
|
||||
{error ? <p>{error}</p> : <p> </p>}
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -31,7 +49,7 @@ export const Route = createFileRoute('/login')({
|
||||
redirect: z.string().optional().catch('')
|
||||
}),
|
||||
beforeLoad({ context, search }) {
|
||||
if (context.auth.isAuthenticated) {
|
||||
if (context.auth.token) {
|
||||
throw redirect({ to: search.redirect || '/' })
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user