141 lines
4.2 KiB
TypeScript
141 lines
4.2 KiB
TypeScript
import { Box, Button, CssBaseline, Paper, Stack, TextField, Typography } from '@mui/material'
|
|
import { getName } from 'services/whiteLabel'
|
|
import { useState } from 'react'
|
|
import axios from 'axios'
|
|
|
|
export type LocalAuthPlaceholderReason = 'unconfigured' | 'insecure_origin'
|
|
|
|
interface Props {
|
|
reason?: LocalAuthPlaceholderReason
|
|
setToken?: (token: string) => void
|
|
}
|
|
|
|
export default function LocalAuthPlaceholder(props: Props) {
|
|
const { reason = 'unconfigured', setToken } = props
|
|
const productName = getName()
|
|
|
|
const [mode, setMode] = useState<'login' | 'signup'>('login')
|
|
const [email, setEmail] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [name, setName] = useState('')
|
|
const [error, setError] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const apiUrl = import.meta.env.VITE_APP_API_URL
|
|
|
|
const handleSignup = async () => {
|
|
setError('')
|
|
setLoading(true)
|
|
try {
|
|
const resp = await axios.post(`${apiUrl}/local-auth/signup`, { email, password, name })
|
|
const token = resp.data.token
|
|
if (token && setToken) {
|
|
localStorage.setItem('local_auth_token', token)
|
|
setToken(token)
|
|
}
|
|
} catch (err: any) {
|
|
setError(err.response?.data?.error || 'Signup failed')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleLogin = async () => {
|
|
setError('')
|
|
setLoading(true)
|
|
try {
|
|
const resp = await axios.post(`${apiUrl}/local-auth/login`, { email, password })
|
|
const token = resp.data.token
|
|
if (token && setToken) {
|
|
localStorage.setItem('local_auth_token', token)
|
|
setToken(token)
|
|
}
|
|
} catch (err: any) {
|
|
setError(err.response?.data?.error || 'Login failed')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (mode === 'signup') {
|
|
handleSignup()
|
|
} else {
|
|
handleLogin()
|
|
}
|
|
}
|
|
|
|
const explanation =
|
|
reason === 'insecure_origin'
|
|
? 'This URL is not a secure context for cloud sign-in. Use local account sign-in instead.'
|
|
: 'Local account sign-in for this deployment.'
|
|
|
|
return (
|
|
<>
|
|
<CssBaseline />
|
|
<Box
|
|
sx={{
|
|
minHeight: '100vh',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
bgcolor: 'background.default',
|
|
p: 2,
|
|
}}
|
|
>
|
|
<Paper elevation={2} sx={{ maxWidth: 420, width: '100%', p: 4 }}>
|
|
<form onSubmit={handleSubmit}>
|
|
<Stack spacing={2} alignItems="stretch">
|
|
<Typography variant="h5" component="h1">
|
|
{productName}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{explanation}
|
|
</Typography>
|
|
{mode === 'signup' && (
|
|
<TextField
|
|
label="Name"
|
|
value={name}
|
|
onChange={e => setName(e.target.value)}
|
|
fullWidth
|
|
/>
|
|
)}
|
|
<TextField
|
|
label="Username or Email"
|
|
type="text"
|
|
value={email}
|
|
onChange={e => setEmail(e.target.value)}
|
|
required
|
|
fullWidth
|
|
/>
|
|
<TextField
|
|
label="Password"
|
|
type="password"
|
|
value={password}
|
|
onChange={e => setPassword(e.target.value)}
|
|
required
|
|
fullWidth
|
|
/>
|
|
{error && (
|
|
<Typography variant="body2" color="error">
|
|
{error}
|
|
</Typography>
|
|
)}
|
|
<Button variant="contained" size="large" type="submit" disabled={loading}>
|
|
{mode === 'signup' ? 'Sign up' : 'Log in'}
|
|
</Button>
|
|
<Button
|
|
variant="text"
|
|
size="small"
|
|
onClick={() => { setMode(mode === 'login' ? 'signup' : 'login'); setError('') }}
|
|
>
|
|
{mode === 'login' ? 'Need an account? Sign up' : 'Already have an account? Log in'}
|
|
</Button>
|
|
</Stack>
|
|
</form>
|
|
</Paper>
|
|
</Box>
|
|
</>
|
|
)
|
|
}
|