login template
This commit is contained in:
parent
8ac6ff6d14
commit
a7b6e15818
@ -13,10 +13,11 @@ export const metadata: Metadata = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LoginPage = async () => {
|
const LoginPage = async () => {
|
||||||
// Vars
|
return (
|
||||||
const mode = await getServerMode()
|
<div className='flex flex-col justify-center items-center min-bs-[100dvh] p-6'>
|
||||||
|
<Login />
|
||||||
return <Login mode={mode} />
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoginPage
|
export default LoginPage
|
||||||
|
|||||||
43
src/views/AuthIllustrationWrapper.tsx
Normal file
43
src/views/AuthIllustrationWrapper.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
// MUI Imports
|
||||||
|
import { styled } from '@mui/material/styles'
|
||||||
|
|
||||||
|
// Styled Component
|
||||||
|
const AuthIllustrationWrapper = styled('div')(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: 450,
|
||||||
|
position: 'relative',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
'&:before': {
|
||||||
|
zIndex: -1,
|
||||||
|
position: 'absolute',
|
||||||
|
height: '234px',
|
||||||
|
width: '238px',
|
||||||
|
content: '""',
|
||||||
|
top: '-80px',
|
||||||
|
left: '-45px',
|
||||||
|
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='238' height='234' viewBox='0 0 238 234' fill='none'%3E%3Crect x='87.9395' y='0.5' width='149' height='149' rx='19.5' stroke='%23${theme.palette.primary.main.slice(
|
||||||
|
1
|
||||||
|
)}' stroke-opacity='0.16'/%3E%3Crect y='33.5608' width='200' height='200' rx='10' fill='%23${theme.palette.primary.main.slice(
|
||||||
|
1
|
||||||
|
)}' fill-opacity='0.08'/%3E%3C/svg%3E")`
|
||||||
|
},
|
||||||
|
'&:after': {
|
||||||
|
zIndex: -1,
|
||||||
|
position: 'absolute',
|
||||||
|
height: '180px',
|
||||||
|
width: '180px',
|
||||||
|
content: '""',
|
||||||
|
right: '-57px',
|
||||||
|
bottom: '-64px',
|
||||||
|
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180' viewBox='0 0 180 180' fill='none'%3E%3Crect x='1' y='1' width='178' height='178' rx='19' stroke='%23${theme.palette.primary.main.slice(
|
||||||
|
1
|
||||||
|
)}' stroke-opacity='0.16' stroke-width='2' stroke-dasharray='8 8'/%3E%3Crect x='22.5' y='22.5' width='135' height='135' rx='10' fill='%23${theme.palette.primary.main.slice(
|
||||||
|
1
|
||||||
|
)}' fill-opacity='0.08'/%3E%3C/svg%3E")`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
export default AuthIllustrationWrapper
|
||||||
@ -8,8 +8,8 @@ import Link from 'next/link'
|
|||||||
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||||
|
|
||||||
// MUI Imports
|
// MUI Imports
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
import Card from '@mui/material/Card'
|
||||||
import { styled, useTheme } from '@mui/material/styles'
|
import CardContent from '@mui/material/CardContent'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import IconButton from '@mui/material/IconButton'
|
import IconButton from '@mui/material/IconButton'
|
||||||
import InputAdornment from '@mui/material/InputAdornment'
|
import InputAdornment from '@mui/material/InputAdornment'
|
||||||
@ -17,7 +17,7 @@ import Checkbox from '@mui/material/Checkbox'
|
|||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import FormControlLabel from '@mui/material/FormControlLabel'
|
import FormControlLabel from '@mui/material/FormControlLabel'
|
||||||
import Divider from '@mui/material/Divider'
|
import Divider from '@mui/material/Divider'
|
||||||
import Alert from '@mui/material/Alert'
|
import { CircularProgress } from '@mui/material'
|
||||||
|
|
||||||
// Third-party Imports
|
// Third-party Imports
|
||||||
import { Controller, useForm } from 'react-hook-form'
|
import { Controller, useForm } from 'react-hook-form'
|
||||||
@ -25,11 +25,10 @@ import { valibotResolver } from '@hookform/resolvers/valibot'
|
|||||||
import { email, object, minLength, string, pipe, nonEmpty } from 'valibot'
|
import { email, object, minLength, string, pipe, nonEmpty } from 'valibot'
|
||||||
import type { SubmitHandler } from 'react-hook-form'
|
import type { SubmitHandler } from 'react-hook-form'
|
||||||
import type { InferInput } from 'valibot'
|
import type { InferInput } from 'valibot'
|
||||||
import classnames from 'classnames'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
// Type Imports
|
// Type Imports
|
||||||
import type { SystemMode } from '@core/types'
|
import type { Locale } from '@configs/i18n'
|
||||||
import type { Locale } from '@/configs/i18n'
|
|
||||||
|
|
||||||
// Component Imports
|
// Component Imports
|
||||||
import Logo from '@components/layout/shared/Logo'
|
import Logo from '@components/layout/shared/Logo'
|
||||||
@ -38,39 +37,12 @@ import CustomTextField from '@core/components/mui/TextField'
|
|||||||
// Config Imports
|
// Config Imports
|
||||||
import themeConfig from '@configs/themeConfig'
|
import themeConfig from '@configs/themeConfig'
|
||||||
|
|
||||||
// Hook Imports
|
|
||||||
import { useImageVariant } from '@core/hooks/useImageVariant'
|
|
||||||
import { useSettings } from '@core/hooks/useSettings'
|
|
||||||
|
|
||||||
// Util Imports
|
// Util Imports
|
||||||
import { getLocalizedUrl } from '@/utils/i18n'
|
import { getLocalizedUrl } from '@/utils/i18n'
|
||||||
import { useAuthMutation } from '../services/mutations/auth'
|
import { useAuthMutation } from '../services/mutations/auth'
|
||||||
import { CircularProgress } from '@mui/material'
|
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
|
|
||||||
// Styled Custom Components
|
// Styled Component Imports
|
||||||
const LoginIllustration = styled('img')(({ theme }) => ({
|
import AuthIllustrationWrapper from './AuthIllustrationWrapper'
|
||||||
zIndex: 2,
|
|
||||||
blockSize: 'auto',
|
|
||||||
maxBlockSize: 680,
|
|
||||||
maxInlineSize: '100%',
|
|
||||||
margin: theme.spacing(12),
|
|
||||||
[theme.breakpoints.down(1536)]: {
|
|
||||||
maxBlockSize: 550
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down('lg')]: {
|
|
||||||
maxBlockSize: 450
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const MaskImg = styled('img')({
|
|
||||||
blockSize: 'auto',
|
|
||||||
maxBlockSize: 355,
|
|
||||||
inlineSize: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
insetBlockEnd: 0,
|
|
||||||
zIndex: -1
|
|
||||||
})
|
|
||||||
|
|
||||||
type ErrorType = {
|
type ErrorType = {
|
||||||
message: string[]
|
message: string[]
|
||||||
@ -87,29 +59,17 @@ const schema = object({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const Login = ({ mode }: { mode: SystemMode }) => {
|
const Login = () => {
|
||||||
// States
|
// States
|
||||||
const [isPasswordShown, setIsPasswordShown] = useState(false)
|
const [isPasswordShown, setIsPasswordShown] = useState(false)
|
||||||
const [errorState, setErrorState] = useState<ErrorType | null>(null)
|
const [errorState, setErrorState] = useState<ErrorType | null>(null)
|
||||||
|
|
||||||
const { login } = useAuthMutation()
|
const { login } = useAuthMutation()
|
||||||
|
|
||||||
// Vars
|
|
||||||
const darkImg = '/images/pages/auth-mask-dark.png'
|
|
||||||
const lightImg = '/images/pages/auth-mask-light.png'
|
|
||||||
const darkIllustration = '/images/illustrations/auth/v2-login-dark.png'
|
|
||||||
const lightIllustration = '/images/illustrations/auth/v2-login-light.png'
|
|
||||||
const borderedDarkIllustration = '/images/illustrations/auth/v2-login-dark-border.png'
|
|
||||||
const borderedLightIllustration = '/images/illustrations/auth/v2-login-light-border.png'
|
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const { lang: locale } = useParams()
|
const { lang: locale } = useParams()
|
||||||
const { settings } = useSettings()
|
|
||||||
const theme = useTheme()
|
|
||||||
const hidden = useMediaQuery(theme.breakpoints.down('md'))
|
|
||||||
const authBackground = useImageVariant(mode, lightImg, darkImg)
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@ -123,14 +83,6 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const characterIllustration = useImageVariant(
|
|
||||||
mode,
|
|
||||||
lightIllustration,
|
|
||||||
darkIllustration,
|
|
||||||
borderedLightIllustration,
|
|
||||||
borderedDarkIllustration
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleClickShowPassword = () => setIsPasswordShown(show => !show)
|
const handleClickShowPassword = () => setIsPasswordShown(show => !show)
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<FormData> = async (data: FormData) => {
|
const onSubmit: SubmitHandler<FormData> = async (data: FormData) => {
|
||||||
@ -138,11 +90,9 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
|||||||
onSuccess: (data: any) => {
|
onSuccess: (data: any) => {
|
||||||
if (data?.user?.role === 'admin') {
|
if (data?.user?.role === 'admin') {
|
||||||
const redirectURL = searchParams.get('redirectTo') ?? '/dashboards/overview'
|
const redirectURL = searchParams.get('redirectTo') ?? '/dashboards/overview'
|
||||||
|
|
||||||
router.replace(getLocalizedUrl(redirectURL, locale as Locale))
|
router.replace(getLocalizedUrl(redirectURL, locale as Locale))
|
||||||
} else {
|
} else {
|
||||||
const redirectURL = searchParams.get('redirectTo') ?? '/sa/organizations/list'
|
const redirectURL = searchParams.get('redirectTo') ?? '/sa/organizations/list'
|
||||||
|
|
||||||
router.replace(getLocalizedUrl(redirectURL, locale as Locale))
|
router.replace(getLocalizedUrl(redirectURL, locale as Locale))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -153,34 +103,17 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex bs-full justify-center'>
|
<AuthIllustrationWrapper>
|
||||||
<div
|
<Card className='flex flex-col sm:is-[450px]'>
|
||||||
className={classnames(
|
<CardContent className='sm:!p-12'>
|
||||||
'flex bs-full items-center justify-center flex-1 min-bs-[100dvh] relative p-6 max-md:hidden',
|
<Link href={getLocalizedUrl('/', locale as Locale)} className='flex justify-center mbe-6'>
|
||||||
{
|
|
||||||
'border-ie': settings.skin === 'bordered'
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<LoginIllustration src={characterIllustration} alt='character-illustration' />
|
|
||||||
{!hidden && <MaskImg alt='mask' src={authBackground} />}
|
|
||||||
</div>
|
|
||||||
<div className='flex justify-center items-center bs-full bg-backgroundPaper !min-is-full p-6 md:!min-is-[unset] md:p-12 md:is-[480px]'>
|
|
||||||
<div className='absolute block-start-5 sm:block-start-[33px] inline-start-6 sm:inline-start-[38px]'>
|
|
||||||
<Logo />
|
<Logo />
|
||||||
</div>
|
</Link>
|
||||||
<div className='flex flex-col gap-6 is-full sm:is-auto md:is-full sm:max-is-[400px] md:max-is-[unset] mbs-8 sm:mbs-11 md:mbs-0'>
|
<div className='flex flex-col gap-1 mbe-6'>
|
||||||
<div className='flex flex-col gap-1'>
|
|
||||||
<Typography variant='h4'>{`Welcome to ${themeConfig.templateName}! 👋🏻`}</Typography>
|
<Typography variant='h4'>{`Welcome to ${themeConfig.templateName}! 👋🏻`}</Typography>
|
||||||
<Typography>Please sign-in to your account and start the adventure</Typography>
|
<Typography>Please sign-in to your account and start the adventure</Typography>
|
||||||
</div>
|
</div>
|
||||||
<form
|
<form noValidate autoComplete='off' onSubmit={handleSubmit(onSubmit)} className='flex flex-col gap-6'>
|
||||||
noValidate
|
|
||||||
autoComplete='off'
|
|
||||||
action={() => {}}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
className='flex flex-col gap-6'
|
|
||||||
>
|
|
||||||
<Controller
|
<Controller
|
||||||
name='email'
|
name='email'
|
||||||
control={control}
|
control={control}
|
||||||
@ -214,7 +147,7 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
label='Password'
|
label='Password'
|
||||||
placeholder='············'
|
placeholder='············'
|
||||||
id='login-password'
|
id='outlined-adornment-password'
|
||||||
type={isPasswordShown ? 'text' : 'password'}
|
type={isPasswordShown ? 'text' : 'password'}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
field.onChange(e.target.value)
|
field.onChange(e.target.value)
|
||||||
@ -263,10 +196,25 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
|||||||
Create an account
|
Create an account
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
</AuthIllustrationWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user