222 lines
7.8 KiB
TypeScript
Raw Normal View History

2025-08-05 12:35:40 +07:00
'use client'
// React Imports
import { useState } from 'react'
// Next Imports
import Link from 'next/link'
import { useParams, useRouter, useSearchParams } from 'next/navigation'
// MUI Imports
2025-09-26 12:47:43 +07:00
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
2025-08-05 12:35:40 +07:00
import Typography from '@mui/material/Typography'
import IconButton from '@mui/material/IconButton'
import InputAdornment from '@mui/material/InputAdornment'
import Checkbox from '@mui/material/Checkbox'
import Button from '@mui/material/Button'
import FormControlLabel from '@mui/material/FormControlLabel'
import Divider from '@mui/material/Divider'
2025-09-26 12:47:43 +07:00
import { CircularProgress } from '@mui/material'
2025-08-05 12:35:40 +07:00
// Third-party Imports
import { Controller, useForm } from 'react-hook-form'
import { valibotResolver } from '@hookform/resolvers/valibot'
import { email, object, minLength, string, pipe, nonEmpty } from 'valibot'
import type { SubmitHandler } from 'react-hook-form'
import type { InferInput } from 'valibot'
2025-09-26 12:47:43 +07:00
import { toast } from 'react-toastify'
2025-08-05 12:35:40 +07:00
// Type Imports
2025-09-26 12:47:43 +07:00
import type { Locale } from '@configs/i18n'
2025-08-05 12:35:40 +07:00
// Component Imports
import Logo from '@components/layout/shared/Logo'
import CustomTextField from '@core/components/mui/TextField'
// Config Imports
import themeConfig from '@configs/themeConfig'
// Util Imports
import { getLocalizedUrl } from '@/utils/i18n'
import { useAuthMutation } from '../services/mutations/auth'
2025-09-26 12:47:43 +07:00
// Styled Component Imports
import AuthIllustrationWrapper from './AuthIllustrationWrapper'
2025-08-05 12:35:40 +07:00
type ErrorType = {
message: string[]
}
type FormData = InferInput<typeof schema>
const schema = object({
email: pipe(string(), minLength(1, 'This field is required'), email('Email is invalid')),
password: pipe(
string(),
nonEmpty('This field is required'),
minLength(5, 'Password must be at least 5 characters long')
)
})
2025-09-26 12:47:43 +07:00
const Login = () => {
2025-08-05 12:35:40 +07:00
// States
const [isPasswordShown, setIsPasswordShown] = useState(false)
const [errorState, setErrorState] = useState<ErrorType | null>(null)
2025-08-06 14:41:23 +07:00
const { login } = useAuthMutation()
2025-08-05 12:35:40 +07:00
// Hooks
const router = useRouter()
const searchParams = useSearchParams()
const { lang: locale } = useParams()
const {
control,
handleSubmit,
formState: { errors }
} = useForm<FormData>({
resolver: valibotResolver(schema),
defaultValues: {
2025-08-14 00:29:19 +07:00
email: '',
password: ''
2025-08-05 12:35:40 +07:00
}
})
const handleClickShowPassword = () => setIsPasswordShown(show => !show)
const onSubmit: SubmitHandler<FormData> = async (data: FormData) => {
2025-08-13 23:53:03 +07:00
login.mutate(data, {
onSuccess: (data: any) => {
if (data?.user?.role === 'admin') {
const redirectURL = searchParams.get('redirectTo') ?? '/dashboards/overview'
router.replace(getLocalizedUrl(redirectURL, locale as Locale))
} else {
const redirectURL = searchParams.get('redirectTo') ?? '/sa/organizations/list'
router.replace(getLocalizedUrl(redirectURL, locale as Locale))
}
},
onError: (error: any) => {
2025-08-14 00:29:19 +07:00
toast.error(error.response.data.message)
2025-08-13 23:53:03 +07:00
}
})
2025-08-05 12:35:40 +07:00
}
return (
2025-09-26 12:47:43 +07:00
<AuthIllustrationWrapper>
<Card className='flex flex-col sm:is-[450px]'>
<CardContent className='sm:!p-12'>
<Link href={getLocalizedUrl('/', locale as Locale)} className='flex justify-center mbe-6'>
<Logo />
</Link>
<div className='flex flex-col gap-1 mbe-6'>
2025-08-05 12:35:40 +07:00
<Typography variant='h4'>{`Welcome to ${themeConfig.templateName}! 👋🏻`}</Typography>
<Typography>Please sign-in to your account and start the adventure</Typography>
</div>
2025-09-26 12:47:43 +07:00
<form noValidate autoComplete='off' onSubmit={handleSubmit(onSubmit)} className='flex flex-col gap-6'>
2025-08-05 12:35:40 +07:00
<Controller
name='email'
control={control}
rules={{ required: true }}
render={({ field }) => (
<CustomTextField
{...field}
autoFocus
fullWidth
type='email'
label='Email'
placeholder='Enter your email'
onChange={e => {
field.onChange(e.target.value)
errorState !== null && setErrorState(null)
}}
{...((errors.email || errorState !== null) && {
error: true,
helperText: errors?.email?.message || errorState?.message[0]
})}
/>
)}
/>
<Controller
name='password'
control={control}
rules={{ required: true }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
label='Password'
placeholder='············'
2025-09-26 12:47:43 +07:00
id='outlined-adornment-password'
2025-08-05 12:35:40 +07:00
type={isPasswordShown ? 'text' : 'password'}
onChange={e => {
field.onChange(e.target.value)
errorState !== null && setErrorState(null)
}}
slotProps={{
input: {
endAdornment: (
<InputAdornment position='end'>
<IconButton
edge='end'
onClick={handleClickShowPassword}
onMouseDown={e => e.preventDefault()}
>
<i className={isPasswordShown ? 'tabler-eye' : 'tabler-eye-off'} />
</IconButton>
</InputAdornment>
)
}
}}
{...(errors.password && { error: true, helperText: errors.password.message })}
/>
)}
/>
<div className='flex justify-between items-center gap-x-3 gap-y-1 flex-wrap'>
<FormControlLabel control={<Checkbox defaultChecked />} label='Remember me' />
<Typography
className='text-end'
color='primary.main'
component={Link}
href={getLocalizedUrl('/forgot-password', locale as Locale)}
>
Forgot password?
</Typography>
</div>
2025-08-07 23:48:31 +07:00
<Button fullWidth variant='contained' type='submit' disabled={login.isPending}>
2025-08-09 22:38:12 +07:00
{login.isPending ? <CircularProgress size={16} /> : 'Login'}
2025-08-05 12:35:40 +07:00
</Button>
<div className='flex justify-center items-center flex-wrap gap-2'>
<Typography>New on our platform?</Typography>
2025-08-13 23:53:03 +07:00
<Typography
component={Link}
href={getLocalizedUrl('/organization', locale as Locale)}
color='primary.main'
>
2025-08-05 12:35:40 +07:00
Create an account
</Typography>
</div>
2025-09-26 12:47:43 +07:00
<Divider className='gap-2 text-textPrimary'>or</Divider>
<div className='flex justify-center items-center gap-1.5'>
<IconButton className='text-facebook' size='small'>
<i className='tabler-brand-facebook-filled' />
</IconButton>
<IconButton className='text-twitter' size='small'>
<i className='tabler-brand-twitter-filled' />
</IconButton>
<IconButton className='text-textPrimary' size='small'>
<i className='tabler-brand-github-filled' />
</IconButton>
<IconButton className='text-error' size='small'>
<i className='tabler-brand-google-filled' />
</IconButton>
</div>
2025-08-05 12:35:40 +07:00
</form>
2025-09-26 12:47:43 +07:00
</CardContent>
</Card>
</AuthIllustrationWrapper>
2025-08-05 12:35:40 +07:00
)
}
export default Login