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'
|
import React, { useContext, createContext, useState } from 'react'
|
||||||
|
|
||||||
export interface AuthContextType {
|
export interface AuthContextType {
|
||||||
isAuthenticated: boolean
|
token: string | null
|
||||||
login(): void
|
error: string | null
|
||||||
|
login(username: string, password: string): Promise<boolean>
|
||||||
logout(): void
|
logout(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LoginView {
|
||||||
|
username: string
|
||||||
|
accessToken: string
|
||||||
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | null>(null)
|
const AuthContext = createContext<AuthContextType | null>(null)
|
||||||
|
|
||||||
export const AuthProvider = ({ children }: React.PropsWithChildren) => {
|
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'] = () => {
|
const login: AuthContextType['login'] = async (username, password) => {
|
||||||
setIsAuthenticated(true)
|
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'] = () => {
|
const logout: AuthContextType['logout'] = () => {
|
||||||
setIsAuthenticated(false)
|
setToken(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ isAuthenticated, login, logout }}>
|
<AuthContext.Provider value={{ token, error, login, logout }}>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,7 @@ import { Outlet, createFileRoute, redirect } from '@tanstack/react-router'
|
|||||||
|
|
||||||
export const Route = createFileRoute('/_auth')({
|
export const Route = createFileRoute('/_auth')({
|
||||||
beforeLoad({ context, location }) {
|
beforeLoad({ context, location }) {
|
||||||
if (!context.auth.isAuthenticated) {
|
if (!context.auth.token) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: '/login',
|
to: '/login',
|
||||||
search: {
|
search: {
|
||||||
|
@ -5,23 +5,41 @@ import {
|
|||||||
useRouter,
|
useRouter,
|
||||||
useSearch
|
useSearch
|
||||||
} from '@tanstack/react-router'
|
} from '@tanstack/react-router'
|
||||||
|
import { FormEvent } from 'react'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const login = useRouteContext({ from: '/login', select: s => s.auth.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 router = useRouter()
|
||||||
const search = useSearch({ from: '/login' })
|
const search = useSearch({ from: '/login' })
|
||||||
|
|
||||||
const onLogin = async () => {
|
const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
||||||
login()
|
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()
|
await router.invalidate()
|
||||||
router.navigate({ to: search.redirect || '/' })
|
router.navigate({ to: search.redirect || '/' })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>Login Page</h2>
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -31,7 +49,7 @@ export const Route = createFileRoute('/login')({
|
|||||||
redirect: z.string().optional().catch('')
|
redirect: z.string().optional().catch('')
|
||||||
}),
|
}),
|
||||||
beforeLoad({ context, search }) {
|
beforeLoad({ context, search }) {
|
||||||
if (context.auth.isAuthenticated) {
|
if (context.auth.token) {
|
||||||
throw redirect({ to: search.redirect || '/' })
|
throw redirect({ to: search.redirect || '/' })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user