RC-2-auth-pages #1

Merged
evgeniywas merged 8 commits from RC-2-auth-pages into dev 2023-06-11 13:39:34 +03:00
17 changed files with 484 additions and 355 deletions

View File

@@ -11,7 +11,6 @@ import { memo, useCallback, useContext, useMemo, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { matchRoutes, useLocation } from 'react-router-dom';
import GlobalStyles from '@mui/material/GlobalStyles';
import { alpha } from '@mui/material/styles';
const inputGlobalStyles = (
<GlobalStyles
@@ -38,20 +37,23 @@ const inputGlobalStyles = (
'table.simple thead tr th': {
borderColor: theme.palette.divider,
},
'a:not([role=button]):not(.MuiButtonBase-root)': {
color: theme.palette.secondary.main,
textDecoration: 'underline',
'&:hover': {},
},
'a.link, a:not([role=button])[target=_blank]': {
background: alpha(theme.palette.secondary.main, 0.2),
color: 'inherit',
borderBottom: `1px solid ${theme.palette.divider}`,
textDecoration: 'none',
'&:hover': {
background: alpha(theme.palette.secondary.main, 0.3),
textDecoration: 'none',
},
// 'a:not([role=button]):not(.MuiButtonBase-root)': {
// color: theme.palette.secondary.main,
// textDecoration: 'underline',
// '&:hover': {},
// },
// 'a.link, a:not([role=button])[target=_blank]': {
// background: alpha(theme.palette.secondary.main, 0.2),
// color: 'inherit',
// borderBottom: `1px solid ${theme.palette.divider}`,
// textDecoration: 'none',
// '&:hover': {
// background: alpha(theme.palette.secondary.main, 0.3),
// textDecoration: 'none',
// },
// },
'[class*="MuiOutlinedInput-root"]': {
borderRadius: theme.size?.inputRadius,
},
'[class^="border"]': {
borderColor: theme.palette.divider,

View File

@@ -37,6 +37,7 @@ const themesConfig = {
},
background: {
paper: '#FFFFFF',
authPaper: '#F1F5F9',
default: '#f1f5f9',
},
error: {
@@ -44,10 +45,16 @@ const themesConfig = {
main: '#f44336',
dark: '#b71c1c',
},
border: {
light: '#D9D9D9',
},
},
status: {
danger: 'orange',
},
size: {
inputRadius: '10px',
},
},
defaultDark: {
palette: {

View File

@@ -1,7 +1,8 @@
import ForgotPasswordConfig from './forgot-password/ForgotPasswordConfig';
import SignInConfig from './sign-in/SignInConfig';
import SignOutConfig from './sign-out/SignOutConfig';
import SignUpConfig from './sign-up/SignUpConfig';
const authPagesConfigs = [SignInConfig, SignOutConfig, SignUpConfig];
const authPagesConfigs = [SignInConfig, SignOutConfig, SignUpConfig, ForgotPasswordConfig];
export default authPagesConfigs;

View File

@@ -0,0 +1,28 @@
import i18next from 'i18next';
import ForgotPasswordPage from './ForgotPasswordPage';
import authRoles from '../../../auth/authRoles';
import en from './i18n/en';
i18next.addResourceBundle('en', 'forgotPasswordPage', en);
const ForgotPasswordConfig = {
settings: {
layout: {
config: {
navbar: {
display: false,
},
},
},
},
auth: authRoles.onlyGuest,
routes: [
{
path: 'forgot-password',
element: <ForgotPasswordPage />,
},
],
};
export default ForgotPasswordConfig;

View File

@@ -0,0 +1,105 @@
import { yupResolver } from '@hookform/resolvers/yup';
import _ from '@lodash';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { Controller, useForm } from 'react-hook-form';
import { withTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import * as yup from 'yup';
import LeftSideCanvas from '../shared-components/LeftSideCanvas';
const defaultValues = {
email: '',
};
function ForgotPasswordPage({ t }) {
const schema = yup.object().shape({
email: yup.string().email(t('email_error')).required(t('email_error')),
});
const { control, formState, handleSubmit, reset } = useForm({
mode: 'onChange',
defaultValues,
resolver: yupResolver(schema),
});
const { isValid, dirtyFields, errors } = formState;
function onSubmit() {
reset(defaultValues);
}
return (
<div className="h-full flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<LeftSideCanvas title={t('title')} subtitle={t('subtitle')} text={t('text')} />
<Paper
className="h-full w-full sm:h-auto md:flex md:h-full py-32 px-16 sm:p-48 md:p-40 md:px-96 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none rtl:border-r-1 ltr:border-l-1"
sx={{ background: (theme) => theme.palette.background?.authPaper }}
>
<div className="w-full mx-auto sm:mx-0">
<Typography className="text-4xl font-extrabold tracking-tight leading-tight">
{t('forgot_password')}
</Typography>
<div className="flex items-baseline mt-10 font-medium">
<Typography>{t('suggestion')}</Typography>
</div>
<form
name="forgotPasswordForm"
noValidate
className="flex flex-col justify-center w-full mt-48"
onSubmit={handleSubmit(onSubmit)}
>
<Controller
name="email"
control={control}
render={({ field }) => (
<TextField
{...field}
label={t('email')}
type="email"
error={!!errors.email}
helperText={errors?.email?.message}
variant="outlined"
required
fullWidth
InputProps={{
sx: {
background: (theme) => theme.palette.background.paper,
},
}}
/>
)}
/>
<div className="flex justify-center w-full">
<Button
variant="contained"
color="secondary"
className="w-[300px] mt-32 text-base uppercase rounded-xl"
aria-label="Register"
disabled={_.isEmpty(dirtyFields) || !isValid}
type="submit"
size="large"
>
{t('forgot_password_btn')}
</Button>
</div>
<Typography className="mt-32 font-medium" color="text.secondary">
<span>{t('return')}</span>
<Link className="ml-4 text-indigo-400 underline" to="/sign-in">
{t('sign_in')}
</Link>
</Typography>
</form>
</div>
</Paper>
</div>
);
}
export default withTranslation('forgotPasswordPage')(ForgotPasswordPage);

View File

@@ -0,0 +1,16 @@
const locale = {
title: 'Lorem ipsum dolor sit amet!',
subtitle:
'Lorem ipsum dolor sit amet consectetur. Scelerisque blandit sit sagittis justo viverra. Morbi accumsaniam elementum enim commodo sed mauris vel. Scelerisque rhoncus in metus non arcu cursus non rhoncus.',
text: 'Lorem ipsum dolor sit amet consectetur. Scelerisque blandit sit.',
forgot_password: 'Forgot password?',
suggestion: 'Fill the form to reset your password',
email: 'Email',
email_error:
'Lorem ipsum dolor sit amet consectetur. Eget pellentesque id consequat consectetur eu quis.',
forgot_password_btn: 'send reset link',
return: 'Return to',
sign_in: 'Sign in',
};
export default locale;

View File

@@ -0,0 +1,40 @@
import FuseSvgIcon from '@fuse/core/FuseSvgIcon/FuseSvgIcon';
import { Typography } from '@mui/material';
import Button from '@mui/material/Button';
import { memo } from 'react';
function AdditionalSignWays({ divider, text }) {
return (
<>
{divider && (
<div className="flex items-center mt-28">
<div className="flex-auto mt-px border-t" />
<Typography className="mx-8" color="text.secondary">
{text}
</Typography>
<div className="flex-auto mt-px border-t" />
</div>
)}
<div className="flex items-center justify-center w-full mt-28 space-x-16">
<Button variant="outlined" className="flex-auto max-w-fit">
<FuseSvgIcon size={20} color="action">
feather:facebook
</FuseSvgIcon>
</Button>
<Button variant="outlined" className="flex-auto max-w-fit">
<FuseSvgIcon size={20} color="action">
feather:twitter
</FuseSvgIcon>
</Button>
<Button variant="outlined" className="flex-auto max-w-fit">
<FuseSvgIcon size={20} color="action">
feather:github
</FuseSvgIcon>
</Button>
</div>
</>
);
}
export default memo(AdditionalSignWays);

View File

@@ -0,0 +1,58 @@
import Avatar from '@mui/material/Avatar';
import AvatarGroup from '@mui/material/AvatarGroup';
import Box from '@mui/material/Box';
function LeftSideCanvas({ title, subtitle, text }) {
return (
<Box
className="h-full min-h-screen relative hidden md:flex flex-auto items-center justify-center p-64 lg:px-112 overflow-hidden max-w-[45vw] w-full"
sx={{ backgroundColor: 'primary.main' }}
>
<svg
className="absolute inset-0 pointer-events-none"
viewBox="0 0 960 540"
width="100%"
height="100%"
preserveAspectRatio="xMidYMax slice"
xmlns="http://www.w3.org/2000/svg"
>
<Box
component="g"
sx={{ color: 'primary.light' }}
className="opacity-20"
fill="none"
stroke="currentColor"
strokeWidth="100"
>
<circle r="234" cx="196" cy="23" />
<circle r="234" cx="790" cy="491" />
</Box>
</svg>
<div className="z-10 relative w-full max-w-2xl">
{title && <div className="text-7xl font-bold leading-none text-gray-100">{title}</div>}
{subtitle && (
<div className="mt-24 text-lg tracking-tight leading-6 text-gray-400">{subtitle}</div>
)}
<div className="flex items-center mt-32">
<AvatarGroup
sx={{
'& .MuiAvatar-root': {
borderColor: 'primary.main',
},
}}
>
<Avatar src="assets/images/avatars/female-18.jpg" />
<Avatar src="assets/images/avatars/female-11.jpg" />
<Avatar src="assets/images/avatars/male-09.jpg" />
<Avatar src="assets/images/avatars/male-16.jpg" />
</AvatarGroup>
{text && <div className="ml-16 font-medium tracking-tight text-gray-400">{text}</div>}
</div>
</div>
</Box>
);
}
export default LeftSideCanvas;

View File

@@ -1,32 +1,18 @@
import { yupResolver } from '@hookform/resolvers/yup';
import { Controller, useForm } from 'react-hook-form';
import _ from '@lodash';
import Button from '@mui/material/Button';
import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import Paper from '@mui/material/Paper';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { Controller, useForm } from 'react-hook-form';
import { withTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import * as yup from 'yup';
import _ from '@lodash';
import FuseSvgIcon from '@fuse/core/FuseSvgIcon';
import AvatarGroup from '@mui/material/AvatarGroup';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import { useEffect } from 'react';
import jwtService from '../../../auth/services/jwtService';
/**
* Form Validation Schema
*/
const schema = yup.object().shape({
email: yup.string().email('You must enter a valid email').required('You must enter a email'),
password: yup
.string()
.required('Please enter your password.')
.min(4, 'Password is too short - must be at least 4 chars.'),
});
import AdditionalSignWays from '../shared-components/AdditionalSignWays';
import LeftSideCanvas from '../shared-components/LeftSideCanvas';
const defaultValues = {
email: '',
@@ -34,8 +20,13 @@ const defaultValues = {
remember: true,
};
function SignInPage() {
const { control, formState, handleSubmit, setError, setValue } = useForm({
function SignInPage({ t }) {
const schema = yup.object().shape({
email: yup.string().email(t('email_error')).required(t('email_error')),
password: yup.string().required(t('password_error')).min(8, t('password_error')),
});
const { control, formState, handleSubmit, reset } = useForm({
mode: 'onChange',
defaultValues,
resolver: yupResolver(schema),
@@ -43,47 +34,33 @@ function SignInPage() {
const { isValid, dirtyFields, errors } = formState;
useEffect(() => {
setValue('email', 'admin@fusetheme.com', { shouldDirty: true, shouldValidate: true });
setValue('password', 'admin', { shouldDirty: true, shouldValidate: true });
}, [setValue]);
function onSubmit({ email, password }) {
jwtService
.signInWithEmailAndPassword(email, password)
.then((user) => {
// No need to do anything, user data will be set at app/auth/AuthContext
})
.catch((_errors) => {
_errors.forEach((error) => {
setError(error.type, {
type: 'manual',
message: error.message,
});
});
});
function onSubmit() {
reset(defaultValues);
}
return (
<div className="flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-1 min-w-0">
<Paper className="h-full sm:h-auto md:flex md:items-center md:justify-end w-full sm:w-auto md:h-full md:w-1/2 py-8 px-16 sm:p-48 md:p-64 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none ltr:border-r-1 rtl:border-l-1">
<div className="w-full max-w-320 sm:w-320 mx-auto sm:mx-0">
<img className="w-48" src="assets/images/logo/logo.svg" alt="logo" />
<div className="h-full flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<LeftSideCanvas title={t('title')} subtitle={t('subtitle')} text={t('text')} />
<Typography className="mt-32 text-4xl font-extrabold tracking-tight leading-tight">
<Paper
className="h-full w-full sm:h-auto md:flex md:h-full py-32 px-16 sm:p-48 md:p-40 md:px-96 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none rtl:border-r-1 ltr:border-l-1"
sx={{ background: (theme) => theme.palette.background?.authPaper }}
>
<div className="w-full mx-auto sm:mx-0">
<Typography className="text-4xl font-extrabold tracking-tight leading-tight">
Sign in
</Typography>
<div className="flex items-baseline mt-2 font-medium">
<Typography>Don't have an account?</Typography>
<Link className="ml-4" to="/sign-up">
Sign up
<div className="flex items-baseline mt-10 font-medium">
<Typography>{t('have_account')}</Typography>
<Link className="ml-4 text-indigo-400 underline" to="/sign-up">
{t('sign_up')}
</Link>
</div>
<form
name="loginForm"
name="signinForm"
noValidate
className="flex flex-col justify-center w-full mt-32"
className="flex flex-col justify-center w-full mt-48"
onSubmit={handleSubmit(onSubmit)}
>
<Controller
@@ -92,8 +69,8 @@ function SignInPage() {
render={({ field }) => (
<TextField
{...field}
className="mb-24"
label="Email"
className="mb-28"
label={t('email')}
autoFocus
type="email"
error={!!errors.email}
@@ -101,6 +78,11 @@ function SignInPage() {
variant="outlined"
required
fullWidth
InputProps={{
sx: {
background: (theme) => theme.palette.background.paper,
},
}}
/>
)}
/>
@@ -111,14 +93,19 @@ function SignInPage() {
render={({ field }) => (
<TextField
{...field}
className="mb-24"
label="Password"
className="mb-28"
label={t('password')}
type="password"
error={!!errors.password}
helperText={errors?.password?.message}
variant="outlined"
required
fullWidth
InputProps={{
sx: {
background: (theme) => theme.palette.background.paper,
},
}}
/>
)}
/>
@@ -130,138 +117,43 @@ function SignInPage() {
render={({ field }) => (
<FormControl>
<FormControlLabel
label="Remember me"
control={<Checkbox size="small" {...field} />}
label={t('remember')}
control={
<Checkbox
{...field}
sx={{ color: (theme) => theme.palette.border.light }}
/>
}
/>
</FormControl>
)}
/>
<Link className="text-md font-medium" to="/pages/auth/forgot-password">
Forgot password?
<Link className="text-indigo-400 underline font-medium" to="/forgot-password">
{t('forgot_password')}
</Link>
</div>
<Button
variant="contained"
color="secondary"
className=" w-full mt-16"
aria-label="Sign in"
disabled={_.isEmpty(dirtyFields) || !isValid}
type="submit"
size="large"
>
Sign in
</Button>
<div className="flex items-center mt-32">
<div className="flex-auto mt-px border-t" />
<Typography className="mx-8" color="text.secondary">
Or continue with
</Typography>
<div className="flex-auto mt-px border-t" />
</div>
<div className="flex items-center mt-32 space-x-16">
<Button variant="outlined" className="flex-auto">
<FuseSvgIcon size={20} color="action">
feather:facebook
</FuseSvgIcon>
</Button>
<Button variant="outlined" className="flex-auto">
<FuseSvgIcon size={20} color="action">
feather:twitter
</FuseSvgIcon>
</Button>
<Button variant="outlined" className="flex-auto">
<FuseSvgIcon size={20} color="action">
feather:github
</FuseSvgIcon>
<div className="flex justify-center w-full">
<Button
variant="contained"
color="secondary"
className="w-[220px] mt-32 text-base uppercase rounded-xl"
aria-label="Sign in"
disabled={_.isEmpty(dirtyFields) || !isValid}
type="submit"
size="large"
>
{t('sign_in_btn')}
</Button>
</div>
<AdditionalSignWays divider text={t('additional_ways')} />
</form>
</div>
</Paper>
<Box
className="relative hidden md:flex flex-auto items-center justify-center h-full p-64 lg:px-112 overflow-hidden"
sx={{ backgroundColor: 'primary.main' }}
>
<svg
className="absolute inset-0 pointer-events-none"
viewBox="0 0 960 540"
width="100%"
height="100%"
preserveAspectRatio="xMidYMax slice"
xmlns="http://www.w3.org/2000/svg"
>
<Box
component="g"
sx={{ color: 'primary.light' }}
className="opacity-20"
fill="none"
stroke="currentColor"
strokeWidth="100"
>
<circle r="234" cx="196" cy="23" />
<circle r="234" cx="790" cy="491" />
</Box>
</svg>
<Box
component="svg"
className="absolute -top-64 -right-64 opacity-20"
sx={{ color: 'primary.light' }}
viewBox="0 0 220 192"
width="220px"
height="192px"
fill="none"
>
<defs>
<pattern
id="837c3e70-6c3a-44e6-8854-cc48c737b659"
x="0"
y="0"
width="20"
height="20"
patternUnits="userSpaceOnUse"
>
<rect x="0" y="0" width="4" height="4" fill="currentColor" />
</pattern>
</defs>
<rect width="220" height="192" fill="url(#837c3e70-6c3a-44e6-8854-cc48c737b659)" />
</Box>
<div className="z-10 relative w-full max-w-2xl">
<div className="text-7xl font-bold leading-none text-gray-100">
<div>Welcome to</div>
<div>our community</div>
</div>
<div className="mt-24 text-lg tracking-tight leading-6 text-gray-400">
Fuse helps developers to build organized and well coded dashboards full of beautiful and
rich modules. Join us and start building your application today.
</div>
<div className="flex items-center mt-32">
<AvatarGroup
sx={{
'& .MuiAvatar-root': {
borderColor: 'primary.main',
},
}}
>
<Avatar src="assets/images/avatars/female-18.jpg" />
<Avatar src="assets/images/avatars/female-11.jpg" />
<Avatar src="assets/images/avatars/male-09.jpg" />
<Avatar src="assets/images/avatars/male-16.jpg" />
</AvatarGroup>
<div className="ml-16 font-medium tracking-tight text-gray-400">
More than 17k people joined us, it's your turn
</div>
</div>
</div>
</Box>
</div>
);
}
export default SignInPage;
export default withTranslation('signInPage')(SignInPage);

View File

@@ -1,3 +1,24 @@
const locale = {};
const locale = {
title: 'Lorem ipsum dolor sit amet!',
subtitle:
'Lorem ipsum dolor sit amet consectetur. Scelerisque blandit sit sagittis justo viverra. Morbi accumsaniam elementum enim commodo sed mauris vel. Scelerisque rhoncus in metus non arcu cursus non rhoncus.',
text: 'Lorem ipsum dolor sit amet consectetur. Scelerisque blandit sit.',
sign_in: 'Sign In',
have_account: 'Don`t have an account?',
sign_up: 'Sign up',
name: 'Name',
name_error:
'Lorem ipsum dolor sit amet consectetur. Eget pellentesque id consequat consectetur eu quis.',
email: 'Email',
email_error:
'Lorem ipsum dolor sit amet consectetur. Eget pellentesque id consequat consectetur eu quis.',
password: 'Password',
password_error:
'Lorem ipsum dolor sit amet consectetur. Eget pellentesque id consequat consectetur eu quis.',
remember: 'Remember me',
forgot_password: 'Forgot password?',
sign_in_btn: 'sign in',
additional_ways: 'Or continue with',
};
export default locale;

View File

@@ -1,109 +1,94 @@
import { yupResolver } from '@hookform/resolvers/yup';
import { Controller, useForm } from 'react-hook-form';
import _ from '@lodash';
import Button from '@mui/material/Button';
import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import Paper from '@mui/material/Paper';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { Controller, useForm } from 'react-hook-form';
import { withTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import * as yup from 'yup';
import _ from '@lodash';
import AvatarGroup from '@mui/material/AvatarGroup';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import FormHelperText from '@mui/material/FormHelperText';
import jwtService from '../../../auth/services/jwtService';
/**
* Form Validation Schema
*/
const schema = yup.object().shape({
displayName: yup.string().required('You must enter display name'),
email: yup.string().email('You must enter a valid email').required('You must enter a email'),
password: yup
.string()
.required('Please enter your password.')
.min(8, 'Password is too short - should be 8 chars minimum.'),
passwordConfirm: yup.string().oneOf([yup.ref('password'), null], 'Passwords must match'),
acceptTermsConditions: yup.boolean().oneOf([true], 'The terms and conditions must be accepted.'),
});
import AdditionalSignWays from '../shared-components/AdditionalSignWays';
import LeftSideCanvas from '../shared-components/LeftSideCanvas';
const defaultValues = {
displayName: '',
name: '',
email: '',
password: '',
passwordConfirm: '',
acceptTermsConditions: false,
};
function SignUpPage() {
function SignUpPage({ t }) {
const schema = yup.object().shape({
name: yup.string().required(t('name_error')),
email: yup.string().email(t('email_error')).required(t('email_error')),
password: yup.string().required(t('password_error')).min(8, t('password_error')),
passwordConfirm: yup.string().oneOf([yup.ref('password'), null], t('password_confirm_error')),
acceptTermsConditions: yup.boolean().oneOf([true], t('accept_terms_error')),
});
const { control, formState, handleSubmit, reset } = useForm({
mode: 'onChange',
defaultValues,
resolver: yupResolver(schema),
});
const { isValid, dirtyFields, errors, setError } = formState;
const { isValid, dirtyFields, errors } = formState;
function onSubmit({ displayName, password, email }) {
jwtService
.createUser({
displayName,
password,
email,
})
.then((user) => {
// No need to do anything, registered user data will be set at app/auth/AuthContext
})
.catch((_errors) => {
_errors.forEach((error) => {
setError(error.type, {
type: 'manual',
message: error.message,
});
});
});
function onSubmit() {
reset(defaultValues);
}
return (
<div className="flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-1 min-w-0">
<Paper className="h-full sm:h-auto md:flex md:items-center md:justify-end w-full sm:w-auto md:h-full md:w-1/2 py-8 px-16 sm:p-48 md:p-64 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none ltr:border-r-1 rtl:border-l-1">
<div className="w-full max-w-320 sm:w-320 mx-auto sm:mx-0">
<img className="w-48" src="assets/images/logo/logo.svg" alt="logo" />
<div className="h-full flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<LeftSideCanvas title={t('title')} subtitle={t('subtitle')} text={t('text')} />
<Typography className="mt-32 text-4xl font-extrabold tracking-tight leading-tight">
Sign up
<Paper
className="h-full w-full sm:h-auto md:flex md:h-full py-32 px-16 sm:p-48 md:p-40 md:px-96 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none rtl:border-r-1 ltr:border-l-1"
sx={{ background: (theme) => theme.palette.background?.authPaper }}
>
<div className="w-full mx-auto sm:mx-0">
<Typography className="text-4xl font-extrabold tracking-tight leading-tight">
{t('sign_up')}
</Typography>
<div className="flex items-baseline mt-2 font-medium">
<Typography>Already have an account?</Typography>
<Link className="ml-4" to="/sign-in">
Sign in
<div className="flex items-baseline mt-10 font-medium">
<Typography>{t('have_account')}</Typography>
<Link className="ml-4 text-indigo-400 underline" to="/sign-in">
{t('sign_in')}
</Link>
</div>
<form
name="registerForm"
name="signupForm"
noValidate
className="flex flex-col justify-center w-full mt-32"
className="flex flex-col justify-center w-full mt-48"
onSubmit={handleSubmit(onSubmit)}
>
<Controller
name="displayName"
name="name"
control={control}
render={({ field }) => (
<TextField
{...field}
className="mb-24"
label="Display name"
className="mb-28"
label={t('name')}
autoFocus
type="name"
error={!!errors.displayName}
helperText={errors?.displayName?.message}
error={!!errors.name}
helperText={errors?.name?.message}
variant="outlined"
required
fullWidth
InputProps={{
sx: {
background: (theme) => theme.palette.background.paper,
},
}}
/>
)}
/>
@@ -114,14 +99,19 @@ function SignUpPage() {
render={({ field }) => (
<TextField
{...field}
className="mb-24"
label="Email"
className="mb-28"
label={t('email')}
type="email"
error={!!errors.email}
helperText={errors?.email?.message}
variant="outlined"
required
fullWidth
InputProps={{
sx: {
background: (theme) => theme.palette.background.paper,
},
}}
/>
)}
/>
@@ -132,14 +122,19 @@ function SignUpPage() {
render={({ field }) => (
<TextField
{...field}
className="mb-24"
label="Password"
className="mb-28"
label={t('password')}
type="password"
error={!!errors.password}
helperText={errors?.password?.message}
variant="outlined"
required
fullWidth
InputProps={{
sx: {
background: (theme) => theme.palette.background.paper,
},
}}
/>
)}
/>
@@ -150,14 +145,19 @@ function SignUpPage() {
render={({ field }) => (
<TextField
{...field}
className="mb-24"
label="Password (Confirm)"
className="mb-28"
label={t('password_confirm')}
type="password"
error={!!errors.passwordConfirm}
helperText={errors?.passwordConfirm?.message}
variant="outlined"
required
fullWidth
InputProps={{
sx: {
background: (theme) => theme.palette.background.paper,
},
}}
/>
)}
/>
@@ -166,110 +166,38 @@ function SignUpPage() {
name="acceptTermsConditions"
control={control}
render={({ field }) => (
<FormControl className="items-center" error={!!errors.acceptTermsConditions}>
<FormControl className="items-start" error={!!errors.acceptTermsConditions}>
<FormControlLabel
label="I agree to the Terms of Service and Privacy Policy"
control={<Checkbox size="small" {...field} />}
label={t('accept_terms')}
control={
<Checkbox {...field} sx={{ color: (theme) => theme.palette.border.light }} />
}
/>
<FormHelperText>{errors?.acceptTermsConditions?.message}</FormHelperText>
</FormControl>
)}
/>
<Button
variant="contained"
color="secondary"
className="w-full mt-24"
aria-label="Register"
disabled={_.isEmpty(dirtyFields) || !isValid}
type="submit"
size="large"
>
Create your free account
</Button>
<div className="flex justify-center w-full">
<Button
variant="contained"
color="secondary"
className="max-w-320 mt-32 text-base uppercase rounded-xl"
aria-label={t('sign_up_btn')}
disabled={_.isEmpty(dirtyFields) || !isValid}
type="submit"
size="large"
>
{t('sign_up_btn')}
</Button>
</div>
<AdditionalSignWays divider text={t('additional_ways')} />
</form>
</div>
</Paper>
<Box
className="relative hidden md:flex flex-auto items-center justify-center h-full p-64 lg:px-112 overflow-hidden"
sx={{ backgroundColor: 'primary.main' }}
>
<svg
className="absolute inset-0 pointer-events-none"
viewBox="0 0 960 540"
width="100%"
height="100%"
preserveAspectRatio="xMidYMax slice"
xmlns="http://www.w3.org/2000/svg"
>
<Box
component="g"
sx={{ color: 'primary.light' }}
className="opacity-20"
fill="none"
stroke="currentColor"
strokeWidth="100"
>
<circle r="234" cx="196" cy="23" />
<circle r="234" cx="790" cy="491" />
</Box>
</svg>
<Box
component="svg"
className="absolute -top-64 -right-64 opacity-20"
sx={{ color: 'primary.light' }}
viewBox="0 0 220 192"
width="220px"
height="192px"
fill="none"
>
<defs>
<pattern
id="837c3e70-6c3a-44e6-8854-cc48c737b659"
x="0"
y="0"
width="20"
height="20"
patternUnits="userSpaceOnUse"
>
<rect x="0" y="0" width="4" height="4" fill="currentColor" />
</pattern>
</defs>
<rect width="220" height="192" fill="url(#837c3e70-6c3a-44e6-8854-cc48c737b659)" />
</Box>
<div className="z-10 relative w-full max-w-2xl">
<div className="text-7xl font-bold leading-none text-gray-100">
<div>Welcome to</div>
<div>our community</div>
</div>
<div className="mt-24 text-lg tracking-tight leading-6 text-gray-400">
Fuse helps developers to build organized and well coded dashboards full of beautiful and
rich modules. Join us and start building your application today.
</div>
<div className="flex items-center mt-32">
<AvatarGroup
sx={{
'& .MuiAvatar-root': {
borderColor: 'primary.main',
},
}}
>
<Avatar src="assets/images/avatars/female-18.jpg" />
<Avatar src="assets/images/avatars/female-11.jpg" />
<Avatar src="assets/images/avatars/male-09.jpg" />
<Avatar src="assets/images/avatars/male-16.jpg" />
</AvatarGroup>
<div className="ml-16 font-medium tracking-tight text-gray-400">
More than 17k people joined us, it's your turn
</div>
</div>
</div>
</Box>
</div>
);
}
export default SignUpPage;
export default withTranslation('signUpPage')(SignUpPage);

View File

@@ -1,3 +1,28 @@
const locale = {};
const locale = {
title: 'Lorem ipsum dolor sit amet!',
subtitle:
'Lorem ipsum dolor sit amet consectetur. Scelerisque blandit sit sagittis justo viverra. Morbi accumsaniam elementum enim commodo sed mauris vel. Scelerisque rhoncus in metus non arcu cursus non rhoncus.',
text: 'Lorem ipsum dolor sit amet consectetur. Scelerisque blandit sit.',
sign_up: 'Sign Up',
have_account: 'Already have an account?',
sign_in: 'Sign in',
name: 'Name',
name_error:
'Lorem ipsum dolor sit amet consectetur. Eget pellentesque id consequat consectetur eu quis.',
email: 'Email',
email_error:
'Lorem ipsum dolor sit amet consectetur. Eget pellentesque id consequat consectetur eu quis.',
password: 'Password',
password_error:
'Lorem ipsum dolor sit amet consectetur. Eget pellentesque id consequat consectetur eu quis.',
password_confirm: 'Password (Confirm)',
password_confirm_error:
'Lorem ipsum dolor sit amet consectetur. Eget pellentesque id consequat consectetur eu quis.',
accept_terms: 'I agree to the Terms of Service and Privacy Policy',
accept_terms_error:
'Lorem ipsum dolor sit amet consectetur. Eget pellentesque id consequat consectetur eu quis.',
sign_up_btn: 'create your account',
additional_ways: 'Or register with',
};
export default locale;

View File

@@ -1,5 +1,6 @@
import i18next from 'i18next';
import { authRoles } from 'src/app/auth';
import Dashboard from './Dashboard';
import en from './i18n/en';
@@ -11,6 +12,7 @@ const DashboardConfig = {
config: {},
},
},
auth: authRoles.user,
routes: [
{
path: 'dashboard',

View File

@@ -1,7 +1,8 @@
import i18next from 'i18next';
import en from './i18n/en';
import { authRoles } from 'src/app/auth';
import Favorites from './Favorites';
import en from './i18n/en';
i18next.addResourceBundle('en', 'favoritesPage', en);
@@ -11,6 +12,7 @@ const FavoritesConfig = {
config: {},
},
},
auth: authRoles.user,
routes: [
{
path: 'favorites',

View File

@@ -1,7 +1,8 @@
import i18next from 'i18next';
import en from './i18n/en';
import { authRoles } from 'src/app/auth';
import History from './History';
import en from './i18n/en';
i18next.addResourceBundle('en', 'historyPage', en);
@@ -11,6 +12,7 @@ const HistoryConfig = {
config: {},
},
},
auth: authRoles.user,
routes: [
{
path: 'history',

View File

@@ -1,5 +1,6 @@
import i18next from 'i18next';
import { authRoles } from 'src/app/auth';
import en from './i18n/en';
import Profile from './Profile';
@@ -11,6 +12,7 @@ const ProfileConfig = {
config: {},
},
},
auth: authRoles.user,
routes: [
{
path: 'profile',

View File

@@ -1,6 +1,4 @@
@tailwind base;
/**
* Custom base styles
*/