feat: V2 with TypeScript
This commit is contained in:
@@ -1,121 +0,0 @@
|
||||
import {Fragment, useState} from 'react'
|
||||
import {Dialog, DialogTitle, TextField, Button, CircularProgress} from '@mui/material'
|
||||
import {useAuth} from '../contexts/AuthContext'
|
||||
|
||||
const textFieldSx = {mx: 2, my: 0.5}
|
||||
|
||||
export default function AuthModal({open, close, isRegisterMode, toggleRegister}) {
|
||||
const {login, register} = useAuth()
|
||||
|
||||
const [formData, setFormData] = useState({})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const handleChange = (e) => {
|
||||
const {name, value} = e.target
|
||||
setFormData((prev) => ({...prev, [name]: value}))
|
||||
}
|
||||
|
||||
const clickSubmit = async () => {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
try {
|
||||
isRegisterMode ? await register(formData) : await login(formData)
|
||||
close()
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const disabledLoginButton = !formData['username'] || !formData['password']
|
||||
const disabledRegisterButton = !formData['username'] || !formData['password']
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={close}>
|
||||
{isRegisterMode ? (
|
||||
<RegisterForm formData={formData} handleChange={handleChange} />
|
||||
) : (
|
||||
<LoginForm formData={formData} handleChange={handleChange} />
|
||||
)}
|
||||
|
||||
{error && <span className='error'>{error}</span>}
|
||||
|
||||
{loading ? (
|
||||
<center>
|
||||
<CircularProgress color='inherit' />
|
||||
</center>
|
||||
) : (
|
||||
<Button
|
||||
onClick={clickSubmit}
|
||||
disabled={isRegisterMode ? disabledRegisterButton : disabledLoginButton}>
|
||||
{isRegisterMode ? 'Register' : 'Login'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button onClick={toggleRegister}>
|
||||
{isRegisterMode ? 'I already have an account' : "I don't have an account"}
|
||||
</Button>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function LoginForm({formData, handleChange}) {
|
||||
return (
|
||||
<Fragment>
|
||||
<DialogTitle>Login to your account</DialogTitle>
|
||||
|
||||
<TextField
|
||||
label='Username'
|
||||
name='username'
|
||||
type='text'
|
||||
value={formData['username'] || ''}
|
||||
onChange={handleChange}
|
||||
variant='filled'
|
||||
sx={textFieldSx}
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label='Password'
|
||||
name='password'
|
||||
type='password'
|
||||
value={formData['password'] || ''}
|
||||
onChange={handleChange}
|
||||
variant='filled'
|
||||
sx={textFieldSx}
|
||||
required
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
function RegisterForm({formData, handleChange}) {
|
||||
return (
|
||||
<Fragment>
|
||||
<DialogTitle>Create a new account</DialogTitle>
|
||||
|
||||
<TextField
|
||||
label='Username'
|
||||
name='username'
|
||||
type='text'
|
||||
value={formData['username'] || ''}
|
||||
onChange={handleChange}
|
||||
variant='filled'
|
||||
sx={textFieldSx}
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label='Password'
|
||||
name='password'
|
||||
type='password'
|
||||
value={formData['password'] || ''}
|
||||
onChange={handleChange}
|
||||
variant='filled'
|
||||
sx={textFieldSx}
|
||||
required
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
92
client/src/components/AuthModal.tsx
Normal file
92
client/src/components/AuthModal.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import React, { type ChangeEventHandler, Fragment, useState } from 'react'
|
||||
import { useModalStore } from 'store/useModalStore'
|
||||
import { useAuth } from 'contexts/AuthContext'
|
||||
import { Dialog, DialogTitle, TextField, Button, CircularProgress } from '@mui/material'
|
||||
import { type FormData } from '@types'
|
||||
|
||||
interface Props {}
|
||||
|
||||
const AuthModal: React.FC<Props> = () => {
|
||||
const { login, register } = useAuth()
|
||||
const { currentModal, setCurrentModal } = useModalStore()
|
||||
|
||||
const isRegisterMode = currentModal === 'REGISTER'
|
||||
const isOpen = ['AUTH', 'LOGIN', 'REGISTER'].includes(currentModal)
|
||||
const onClose = () => setCurrentModal('')
|
||||
|
||||
const [formData, setFormData] = useState<FormData>({ username: '', password: '' })
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
const { name, value } = e.target
|
||||
setFormData((prev) => ({ ...prev, [name]: value }))
|
||||
}
|
||||
|
||||
const clickSubmit = async () => {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
try {
|
||||
isRegisterMode ? await register(formData) : await login(formData)
|
||||
onClose()
|
||||
} catch (error: any) {
|
||||
setError(typeof error === 'string' ? error : JSON.stringify(error))
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const isSubmitButtonDisabled = !formData['username'] || !formData['password']
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onClose={onClose}>
|
||||
{isRegisterMode ? <DialogTitle>Create a new account</DialogTitle> : <DialogTitle>Login to your account</DialogTitle>}
|
||||
|
||||
<TextField
|
||||
label='Username'
|
||||
name='username'
|
||||
type='text'
|
||||
value={formData['username']}
|
||||
onChange={handleChange}
|
||||
variant='filled'
|
||||
sx={{ mx: 2, my: 0.5 }}
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label='Password'
|
||||
name='password'
|
||||
type='password'
|
||||
value={formData['password']}
|
||||
onChange={handleChange}
|
||||
variant='filled'
|
||||
sx={{ mx: 2, my: 0.5 }}
|
||||
required
|
||||
/>
|
||||
|
||||
{error && <span className='error'>{error}</span>}
|
||||
|
||||
{loading ? (
|
||||
<center>
|
||||
<CircularProgress color='inherit' />
|
||||
</center>
|
||||
) : isRegisterMode ? (
|
||||
<Fragment>
|
||||
<Button onClick={clickSubmit} disabled={isSubmitButtonDisabled}>
|
||||
Register
|
||||
</Button>
|
||||
<Button onClick={() => setCurrentModal('LOGIN')}>I already have an account</Button>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Button onClick={clickSubmit} disabled={isSubmitButtonDisabled}>
|
||||
Login
|
||||
</Button>
|
||||
<Button onClick={() => setCurrentModal('REGISTER')}>I don't have an account</Button>
|
||||
</Fragment>
|
||||
)}
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthModal
|
||||
@@ -1,85 +0,0 @@
|
||||
import {Fragment, useState} from 'react'
|
||||
import {
|
||||
AppBar,
|
||||
IconButton,
|
||||
Avatar,
|
||||
Popover,
|
||||
List,
|
||||
ListSubheader,
|
||||
ListItemButton,
|
||||
} from '@mui/material'
|
||||
import OnlineIndicator from './OnlineIndicator'
|
||||
import AuthModal from './AuthModal'
|
||||
import {useAuth} from '../contexts/AuthContext'
|
||||
|
||||
export default function Header() {
|
||||
const {isLoggedIn, account, logout} = useAuth()
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
const [popover, setPopover] = useState(false)
|
||||
const [authModal, setAuthModal] = useState(false)
|
||||
const [register, setRegister] = useState(false)
|
||||
|
||||
const openPopover = (e) => {
|
||||
setPopover(true)
|
||||
setAnchorEl(e.currentTarget)
|
||||
}
|
||||
|
||||
const closePopover = () => {
|
||||
setPopover(false)
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const clickLogin = () => {
|
||||
setRegister(false)
|
||||
setAuthModal(true)
|
||||
closePopover()
|
||||
}
|
||||
|
||||
const clickRegister = () => {
|
||||
setRegister(true)
|
||||
setAuthModal(true)
|
||||
closePopover()
|
||||
}
|
||||
|
||||
return (
|
||||
<AppBar className='header' position='static'>
|
||||
<h1>Web App</h1>
|
||||
|
||||
<IconButton onClick={openPopover}>
|
||||
<OnlineIndicator online={isLoggedIn}>
|
||||
<Avatar src={account?.username || ''} alt={account?.username || ''} />
|
||||
</OnlineIndicator>
|
||||
</IconButton>
|
||||
|
||||
<Popover
|
||||
anchorEl={anchorEl}
|
||||
open={popover}
|
||||
onClose={closePopover}
|
||||
anchorOrigin={{vertical: 'bottom', horizontal: 'right'}}
|
||||
transformOrigin={{vertical: 'top', horizontal: 'right'}}>
|
||||
<List style={{minWidth: '100px'}}>
|
||||
<ListSubheader style={{textAlign: 'center'}}>
|
||||
Hello, {isLoggedIn ? account.username : 'Guest'}
|
||||
</ListSubheader>
|
||||
|
||||
{isLoggedIn ? (
|
||||
<ListItemButton onClick={logout}>Logout</ListItemButton>
|
||||
) : (
|
||||
<Fragment>
|
||||
<ListItemButton onClick={clickLogin}>Login</ListItemButton>
|
||||
<ListItemButton onClick={clickRegister}>Reigster</ListItemButton>
|
||||
</Fragment>
|
||||
)}
|
||||
</List>
|
||||
</Popover>
|
||||
|
||||
<AuthModal
|
||||
open={authModal}
|
||||
close={() => setAuthModal(false)}
|
||||
isRegisterMode={register}
|
||||
toggleRegister={() => setRegister((prev) => !prev)}
|
||||
/>
|
||||
</AppBar>
|
||||
)
|
||||
}
|
||||
70
client/src/components/Header.tsx
Normal file
70
client/src/components/Header.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { Fragment, type MouseEventHandler, useState } from 'react'
|
||||
import { useModalStore } from 'store/useModalStore'
|
||||
import { useAuth } from 'contexts/AuthContext'
|
||||
import OnlineIndicator from 'components/OnlineIndicator'
|
||||
import { AppBar, IconButton, Avatar, Popover, List, ListSubheader, ListItemButton } from '@mui/material'
|
||||
|
||||
interface Props {}
|
||||
|
||||
const Header: React.FC<Props> = () => {
|
||||
const { isLoggedIn, account, logout } = useAuth()
|
||||
const { setCurrentModal } = useModalStore()
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<(EventTarget & HTMLButtonElement) | null>(null)
|
||||
const [popover, setPopover] = useState(false)
|
||||
|
||||
const openPopover: MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||
setPopover(true)
|
||||
setAnchorEl(e.currentTarget)
|
||||
}
|
||||
|
||||
const closePopover = () => {
|
||||
setPopover(false)
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const clickLogin = () => {
|
||||
setCurrentModal('LOGIN')
|
||||
closePopover()
|
||||
}
|
||||
|
||||
const clickRegister = () => {
|
||||
setCurrentModal('REGISTER')
|
||||
closePopover()
|
||||
}
|
||||
|
||||
return (
|
||||
<AppBar className='header' position='static'>
|
||||
<h1>Web App</h1>
|
||||
|
||||
<IconButton onClick={openPopover}>
|
||||
<OnlineIndicator online={isLoggedIn}>
|
||||
<Avatar src={account?.username || ''} alt={account?.username || 'Guest'} />
|
||||
</OnlineIndicator>
|
||||
</IconButton>
|
||||
|
||||
<Popover
|
||||
anchorEl={anchorEl}
|
||||
open={popover}
|
||||
onClose={closePopover}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
>
|
||||
<List style={{ minWidth: '100px' }}>
|
||||
<ListSubheader style={{ textAlign: 'center' }}>Hello, {account?.username || 'Guest'}</ListSubheader>
|
||||
|
||||
{isLoggedIn ? (
|
||||
<ListItemButton onClick={logout}>Logout</ListItemButton>
|
||||
) : (
|
||||
<Fragment>
|
||||
<ListItemButton onClick={clickLogin}>Login</ListItemButton>
|
||||
<ListItemButton onClick={clickRegister}>Register</ListItemButton>
|
||||
</Fragment>
|
||||
)}
|
||||
</List>
|
||||
</Popover>
|
||||
</AppBar>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
@@ -1,7 +1,8 @@
|
||||
import {styled} from '@mui/material/styles'
|
||||
import {Badge, Avatar} from '@mui/material'
|
||||
import React from 'react'
|
||||
import { styled } from '@mui/material/styles'
|
||||
import { Badge, Avatar } from '@mui/material'
|
||||
|
||||
const StyledBadge = styled(Badge)(({theme}) => ({
|
||||
const StyledBadge = styled(Badge)(({ theme }) => ({
|
||||
'& .MuiBadge-badge': {
|
||||
backgroundColor: 'black',
|
||||
color: 'black',
|
||||
@@ -30,34 +31,34 @@ const StyledBadge = styled(Badge)(({theme}) => ({
|
||||
},
|
||||
}))
|
||||
|
||||
const OnlineBadge = styled(StyledBadge)(({theme}) => ({
|
||||
const OnlineBadge = styled(StyledBadge)({
|
||||
'& .MuiBadge-badge': {
|
||||
backgroundColor: 'var(--online)',
|
||||
color: 'var(--online)',
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
const OfflineBadge = styled(StyledBadge)(({theme}) => ({
|
||||
const OfflineBadge = styled(StyledBadge)({
|
||||
'& .MuiBadge-badge': {
|
||||
backgroundColor: 'var(--offline)',
|
||||
color: 'var(--offline)',
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
export default function OnlineIndicator({online = false, children = <Avatar src='' alt='' />}) {
|
||||
return online ? (
|
||||
<OnlineBadge
|
||||
variant='dot'
|
||||
overlap='circular'
|
||||
anchorOrigin={{vertical: 'bottom', horizontal: 'right'}}>
|
||||
{children}
|
||||
</OnlineBadge>
|
||||
) : (
|
||||
<OfflineBadge
|
||||
variant='dot'
|
||||
overlap='circular'
|
||||
anchorOrigin={{vertical: 'bottom', horizontal: 'right'}}>
|
||||
const OnlineIndicator = ({ online = false, children = <Avatar src='' alt='' /> }) => {
|
||||
if (online) {
|
||||
return (
|
||||
<OnlineBadge variant='dot' overlap='circular' anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}>
|
||||
{children}
|
||||
</OnlineBadge>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<OfflineBadge variant='dot' overlap='circular' anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}>
|
||||
{children}
|
||||
</OfflineBadge>
|
||||
)
|
||||
}
|
||||
|
||||
export default OnlineIndicator
|
||||
Reference in New Issue
Block a user