mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-27 01:19:00 +02:00
Makestyles 7-1 (#2813)
This commit is contained in:
parent
0af162a8e6
commit
45652f6bf9
@ -6,9 +6,9 @@ import { modalStyles } from 'component/admin/users/util';
|
|||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
import PasswordChecker, {
|
import PasswordChecker, {
|
||||||
PASSWORD_FORMAT_MESSAGE,
|
PASSWORD_FORMAT_MESSAGE,
|
||||||
} from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker';
|
} from 'component/user/common/ResetPasswordForm/PasswordChecker';
|
||||||
import { useThemeStyles } from 'themes/themeStyles';
|
import { useThemeStyles } from 'themes/themeStyles';
|
||||||
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
|
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher';
|
||||||
import { IUser } from 'interfaces/user';
|
import { IUser } from 'interfaces/user';
|
||||||
import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
|
import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
|
||||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import SimpleAuth from '../SimpleAuth/SimpleAuth';
|
import SimpleAuth from '../SimpleAuth/SimpleAuth';
|
||||||
import { AuthenticationCustomComponent } from 'component/user/AuthenticationCustomComponent';
|
import { AuthenticationCustomComponent } from 'component/user/AuthenticationCustomComponent';
|
||||||
import PasswordAuth from '../PasswordAuth/PasswordAuth';
|
import PasswordAuth from '../PasswordAuth';
|
||||||
import HostedAuth from '../HostedAuth/HostedAuth';
|
import HostedAuth from '../HostedAuth';
|
||||||
import DemoAuth from '../DemoAuth/DemoAuth';
|
import DemoAuth from '../DemoAuth/DemoAuth';
|
||||||
import {
|
import {
|
||||||
SIMPLE_TYPE,
|
SIMPLE_TYPE,
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
PASSWORD_TYPE,
|
PASSWORD_TYPE,
|
||||||
HOSTED_TYPE,
|
HOSTED_TYPE,
|
||||||
} from 'constants/authTypes';
|
} from 'constants/authTypes';
|
||||||
import SecondaryLoginActions from '../common/SecondaryLoginActions/SecondaryLoginActions';
|
import SecondaryLoginActions from '../common/SecondaryLoginActions';
|
||||||
import useQueryParams from 'hooks/useQueryParams';
|
import useQueryParams from 'hooks/useQueryParams';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { Alert } from '@mui/material';
|
import { Alert } from '@mui/material';
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
forgottenPassword: {
|
|
||||||
width: '350px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
width: '150px',
|
|
||||||
margin: '1rem auto',
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
display: 'block',
|
|
||||||
margin: '0.5rem 0',
|
|
||||||
},
|
|
||||||
loginText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,24 +1,58 @@
|
|||||||
import { Button, TextField, Typography } from '@mui/material';
|
import { Button, styled, TextField, Typography } from '@mui/material';
|
||||||
import { AlertTitle, Alert } from '@mui/material';
|
import { AlertTitle, Alert } from '@mui/material';
|
||||||
import classnames from 'classnames';
|
|
||||||
import { SyntheticEvent, useState } from 'react';
|
import { SyntheticEvent, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useThemeStyles } from 'themes/themeStyles';
|
|
||||||
import useLoading from 'hooks/useLoading';
|
import useLoading from 'hooks/useLoading';
|
||||||
import { FORGOTTEN_PASSWORD_FIELD } from 'utils/testIds';
|
import { FORGOTTEN_PASSWORD_FIELD } from 'utils/testIds';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import DividerText from 'component/common/DividerText/DividerText';
|
import DividerText from 'component/common/DividerText/DividerText';
|
||||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
import StandaloneLayout from '../common/StandaloneLayout';
|
||||||
import { useStyles } from './ForgottenPassword.styles';
|
import {
|
||||||
|
contentSpacingY,
|
||||||
|
flexColumn,
|
||||||
|
textCenter,
|
||||||
|
title,
|
||||||
|
} from 'themes/themeStyles';
|
||||||
|
|
||||||
|
const StyledDiv = styled('div')(({ theme }) => ({
|
||||||
|
...contentSpacingY,
|
||||||
|
...flexColumn,
|
||||||
|
width: '350px',
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledStrong = styled('strong')(({ theme }) => ({
|
||||||
|
display: 'block',
|
||||||
|
margin: theme.spacing(1, 0),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
|
width: '150px',
|
||||||
|
margin: theme.spacing(2, 'auto'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitle = styled('h2')(({ theme }) => ({
|
||||||
|
...title(theme),
|
||||||
|
...textCenter,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledForm = styled('form')(({ theme }) => ({
|
||||||
|
...contentSpacingY(theme),
|
||||||
|
...flexColumn,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||||
|
...textCenter,
|
||||||
|
}));
|
||||||
|
|
||||||
const ForgottenPassword = () => {
|
const ForgottenPassword = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [attempted, setAttempted] = useState(false);
|
const [attempted, setAttempted] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [attemptedEmail, setAttemptedEmail] = useState('');
|
const [attemptedEmail, setAttemptedEmail] = useState('');
|
||||||
const { classes: themeStyles } = useThemeStyles();
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const ref = useLoading(loading);
|
const ref = useLoading(loading);
|
||||||
|
|
||||||
const onClick = async (e: SyntheticEvent) => {
|
const onClick = async (e: SyntheticEvent) => {
|
||||||
@ -41,32 +75,15 @@ const ForgottenPassword = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StandaloneLayout>
|
<StandaloneLayout>
|
||||||
<div
|
<StyledDiv ref={ref}>
|
||||||
className={classnames(
|
<StyledTitle data-loading>Forgotten password</StyledTitle>
|
||||||
themeStyles.contentSpacingY,
|
|
||||||
themeStyles.flexColumn,
|
|
||||||
styles.forgottenPassword
|
|
||||||
)}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
<h2
|
|
||||||
className={classnames(
|
|
||||||
themeStyles.title,
|
|
||||||
themeStyles.textCenter
|
|
||||||
)}
|
|
||||||
data-loading
|
|
||||||
>
|
|
||||||
Forgotten password
|
|
||||||
</h2>
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={attempted}
|
condition={attempted}
|
||||||
show={
|
show={
|
||||||
<Alert severity="success" data-loading>
|
<Alert severity="success" data-loading>
|
||||||
<AlertTitle>Attempted to send email</AlertTitle>
|
<AlertTitle>Attempted to send email</AlertTitle>
|
||||||
We've attempted to send a reset password email to:
|
We've attempted to send a reset password email to:
|
||||||
<strong className={styles.email}>
|
<StyledStrong>{attemptedEmail}</StyledStrong>
|
||||||
{attemptedEmail}
|
|
||||||
</strong>
|
|
||||||
If you did not receive an email, please verify that
|
If you did not receive an email, please verify that
|
||||||
you typed in the correct email, and contact your
|
you typed in the correct email, and contact your
|
||||||
administrator to make sure that you are in the
|
administrator to make sure that you are in the
|
||||||
@ -74,21 +91,11 @@ const ForgottenPassword = () => {
|
|||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<form
|
<StyledForm onSubmit={onClick}>
|
||||||
onSubmit={onClick}
|
<StyledTypography variant="body1" data-loading>
|
||||||
className={classnames(
|
|
||||||
themeStyles.contentSpacingY,
|
|
||||||
themeStyles.flexColumn
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
data-loading
|
|
||||||
className={themeStyles.textCenter}
|
|
||||||
>
|
|
||||||
Please provide your email address. If it exists in the
|
Please provide your email address. If it exists in the
|
||||||
system we'll send a new reset link.
|
system we'll send a new reset link.
|
||||||
</Typography>
|
</StyledTypography>
|
||||||
<TextField
|
<TextField
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
@ -101,12 +108,11 @@ const ForgottenPassword = () => {
|
|||||||
setEmail(e.target.value);
|
setEmail(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<StyledButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
type="submit"
|
type="submit"
|
||||||
data-loading
|
data-loading
|
||||||
color="primary"
|
color="primary"
|
||||||
className={styles.button}
|
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -114,21 +120,24 @@ const ForgottenPassword = () => {
|
|||||||
show={<span>Submit</span>}
|
show={<span>Submit</span>}
|
||||||
elseShow={<span>Try again</span>}
|
elseShow={<span>Try again</span>}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</StyledButton>
|
||||||
<DividerText text="Or log in" />
|
<DividerText text="Or log in" />
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
data-loading
|
data-loading
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className={styles.button}
|
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/login"
|
to="/login"
|
||||||
|
sx={theme => ({
|
||||||
|
width: '150px',
|
||||||
|
margin: theme.spacing(2, 'auto'),
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
Log in
|
Log in
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</StyledForm>
|
||||||
</div>
|
</StyledDiv>
|
||||||
</StandaloneLayout>
|
</StandaloneLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import { FormEventHandler, useState, VFC } from 'react';
|
import { FormEventHandler, useState, VFC } from 'react';
|
||||||
import classnames from 'classnames';
|
import { Button, Grid, styled, TextField, Typography } from '@mui/material';
|
||||||
import { Button, Grid, TextField, Typography } from '@mui/material';
|
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { useThemeStyles } from 'themes/themeStyles';
|
|
||||||
import { useStyles } from './HostedAuth.styles';
|
|
||||||
import useQueryParams from 'hooks/useQueryParams';
|
import useQueryParams from 'hooks/useQueryParams';
|
||||||
import AuthOptions from '../common/AuthOptions/AuthOptions';
|
import AuthOptions from './common/AuthOptions/AuthOptions';
|
||||||
import DividerText from 'component/common/DividerText/DividerText';
|
import DividerText from 'component/common/DividerText/DividerText';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import PasswordField from 'component/common/PasswordField/PasswordField';
|
import PasswordField from 'component/common/PasswordField/PasswordField';
|
||||||
@ -14,15 +11,31 @@ import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
|||||||
import { LOGIN_BUTTON, LOGIN_EMAIL_ID, LOGIN_PASSWORD_ID } from 'utils/testIds';
|
import { LOGIN_BUTTON, LOGIN_EMAIL_ID, LOGIN_PASSWORD_ID } from 'utils/testIds';
|
||||||
import { IAuthEndpointDetailsResponse } from 'hooks/api/getters/useAuth/useAuthEndpoint';
|
import { IAuthEndpointDetailsResponse } from 'hooks/api/getters/useAuth/useAuthEndpoint';
|
||||||
import { BadRequestError, NotFoundError } from 'utils/apiUtils';
|
import { BadRequestError, NotFoundError } from 'utils/apiUtils';
|
||||||
|
import { contentSpacingY } from 'themes/themeStyles';
|
||||||
|
|
||||||
interface IHostedAuthProps {
|
interface IHostedAuthProps {
|
||||||
authDetails: IAuthEndpointDetailsResponse;
|
authDetails: IAuthEndpointDetailsResponse;
|
||||||
redirect: string;
|
redirect: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDiv = styled('div')(({ theme }) => ({
|
||||||
|
...contentSpacingY(theme),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
|
width: '150px',
|
||||||
|
margin: theme.spacing(2, 'auto', 0, 'auto'),
|
||||||
|
display: 'block',
|
||||||
|
textAlign: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
const HostedAuth: VFC<IHostedAuthProps> = ({ authDetails, redirect }) => {
|
const HostedAuth: VFC<IHostedAuthProps> = ({ authDetails, redirect }) => {
|
||||||
const { classes: themeStyles } = useThemeStyles();
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { refetchUser } = useAuthUser();
|
const { refetchUser } = useAuthUser();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const params = useQueryParams();
|
const params = useQueryParams();
|
||||||
@ -97,18 +110,10 @@ const HostedAuth: VFC<IHostedAuthProps> = ({ authDetails, redirect }) => {
|
|||||||
condition={!authDetails.defaultHidden}
|
condition={!authDetails.defaultHidden}
|
||||||
show={
|
show={
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<Typography
|
<StyledTypography variant="subtitle2">
|
||||||
variant="subtitle2"
|
|
||||||
className={styles.apiError}
|
|
||||||
>
|
|
||||||
{apiError}
|
{apiError}
|
||||||
</Typography>
|
</StyledTypography>
|
||||||
<div
|
<StyledDiv>
|
||||||
className={classnames(
|
|
||||||
styles.contentContainer,
|
|
||||||
themeStyles.contentSpacingY
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Username or email"
|
label="Username or email"
|
||||||
name="username"
|
name="username"
|
||||||
@ -134,17 +139,16 @@ const HostedAuth: VFC<IHostedAuthProps> = ({ authDetails, redirect }) => {
|
|||||||
data-testid={LOGIN_PASSWORD_ID}
|
data-testid={LOGIN_PASSWORD_ID}
|
||||||
/>
|
/>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Button
|
<StyledButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
className={styles.button}
|
|
||||||
data-testid={LOGIN_BUTTON}
|
data-testid={LOGIN_BUTTON}
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</StyledButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</StyledDiv>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
@ -1,24 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
loginContainer: {
|
|
||||||
minWidth: '350px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
width: '100%',
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contentContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
apiError: {
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
width: '150px',
|
|
||||||
margin: '1rem auto 0 auto',
|
|
||||||
display: 'block',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,51 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
login: {
|
|
||||||
width: '350px',
|
|
||||||
maxWidth: '350px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
loginContainer: {
|
|
||||||
minHeight: '100vh',
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
display: 'flex',
|
|
||||||
height: '100%',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
},
|
|
||||||
contentContainer: {
|
|
||||||
width: '50%',
|
|
||||||
padding: '4rem 3rem',
|
|
||||||
minHeight: '100vh',
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
width: '100%',
|
|
||||||
minHeight: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: theme.fontSizes.mainHeader,
|
|
||||||
marginBottom: '1rem',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
logo: {
|
|
||||||
marginRight: '10px',
|
|
||||||
width: '40px',
|
|
||||||
height: '30px',
|
|
||||||
},
|
|
||||||
subTitle: {
|
|
||||||
fontSize: '1.25rem',
|
|
||||||
},
|
|
||||||
loginFormContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
imageContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
marginTop: '8rem',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,17 +1,26 @@
|
|||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
import { Alert, AlertTitle } from '@mui/material';
|
import { Alert, AlertTitle, styled } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { useStyles } from 'component/user/Login/Login.styles';
|
|
||||||
import useQueryParams from 'hooks/useQueryParams';
|
import useQueryParams from 'hooks/useQueryParams';
|
||||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
import StandaloneLayout from '../common/StandaloneLayout';
|
||||||
import { DEMO_TYPE } from 'constants/authTypes';
|
import { DEMO_TYPE } from 'constants/authTypes';
|
||||||
import Authentication from '../Authentication/Authentication';
|
import Authentication from '../Authentication/Authentication';
|
||||||
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
|
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
|
||||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||||
import { parseRedirectParam } from 'component/user/Login/parseRedirectParam';
|
import { parseRedirectParam } from 'component/user/Login/parseRedirectParam';
|
||||||
|
|
||||||
|
const StyledDiv = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeader = styled('h2')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
textAlign: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { authDetails } = useAuthDetails();
|
const { authDetails } = useAuthDetails();
|
||||||
const { user } = useAuthUser();
|
const { user } = useAuthUser();
|
||||||
const query = useQueryParams();
|
const query = useQueryParams();
|
||||||
@ -25,7 +34,7 @@ const Login = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StandaloneLayout>
|
<StandaloneLayout>
|
||||||
<div className={styles.loginFormContainer}>
|
<StyledDiv>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={resetPassword}
|
condition={resetPassword}
|
||||||
show={
|
show={
|
||||||
@ -47,13 +56,13 @@ const Login = () => {
|
|||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={authDetails?.type !== DEMO_TYPE}
|
condition={authDetails?.type !== DEMO_TYPE}
|
||||||
show={
|
show={
|
||||||
<h2 className={styles.title}>
|
<StyledHeader>
|
||||||
Log in to continue the great work
|
Log in to continue the great work
|
||||||
</h2>
|
</StyledHeader>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Authentication redirect={redirect} />
|
<Authentication redirect={redirect} />
|
||||||
</div>
|
</StyledDiv>
|
||||||
</StandaloneLayout>
|
</StandaloneLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Box, Typography } from '@mui/material';
|
import { Box, Typography } from '@mui/material';
|
||||||
import StandaloneLayout from 'component/user/common/StandaloneLayout/StandaloneLayout';
|
import StandaloneLayout from 'component/user/common/StandaloneLayout';
|
||||||
import StandaloneBanner from 'component/user/StandaloneBanner/StandaloneBanner';
|
import StandaloneBanner from 'component/user/StandaloneBanner';
|
||||||
import useLoading from 'hooks/useLoading';
|
import useLoading from 'hooks/useLoading';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { FormEventHandler, useState, VFC } from 'react';
|
import { FormEventHandler, useState, VFC } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Button, TextField } from '@mui/material';
|
import { Button, styled, TextField } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { useThemeStyles } from 'themes/themeStyles';
|
|
||||||
import { useStyles } from './PasswordAuth.styles';
|
|
||||||
import useQueryParams from 'hooks/useQueryParams';
|
import useQueryParams from 'hooks/useQueryParams';
|
||||||
import AuthOptions from '../common/AuthOptions/AuthOptions';
|
import AuthOptions from './common/AuthOptions/AuthOptions';
|
||||||
import DividerText from 'component/common/DividerText/DividerText';
|
import DividerText from 'component/common/DividerText/DividerText';
|
||||||
import { Alert } from '@mui/material';
|
import { Alert } from '@mui/material';
|
||||||
import { LOGIN_BUTTON, LOGIN_EMAIL_ID, LOGIN_PASSWORD_ID } from 'utils/testIds';
|
import { LOGIN_BUTTON, LOGIN_EMAIL_ID, LOGIN_PASSWORD_ID } from 'utils/testIds';
|
||||||
@ -19,15 +17,25 @@ import {
|
|||||||
BadRequestError,
|
BadRequestError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
} from 'utils/apiUtils';
|
} from 'utils/apiUtils';
|
||||||
|
import { contentSpacingY } from 'themes/themeStyles';
|
||||||
|
|
||||||
interface IPasswordAuthProps {
|
interface IPasswordAuthProps {
|
||||||
authDetails: IAuthEndpointDetailsResponse;
|
authDetails: IAuthEndpointDetailsResponse;
|
||||||
redirect: string;
|
redirect: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDiv = styled('div')(({ theme }) => ({
|
||||||
|
...contentSpacingY(theme),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}));
|
||||||
|
|
||||||
const PasswordAuth: VFC<IPasswordAuthProps> = ({ authDetails, redirect }) => {
|
const PasswordAuth: VFC<IPasswordAuthProps> = ({ authDetails, redirect }) => {
|
||||||
const { classes: themeStyles } = useThemeStyles();
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { refetchUser } = useAuthUser();
|
const { refetchUser } = useAuthUser();
|
||||||
const params = useQueryParams();
|
const params = useQueryParams();
|
||||||
@ -98,21 +106,13 @@ const PasswordAuth: VFC<IPasswordAuthProps> = ({ authDetails, redirect }) => {
|
|||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(apiError)}
|
condition={Boolean(apiError)}
|
||||||
show={
|
show={
|
||||||
<Alert
|
<StyledAlert severity="error">
|
||||||
severity="error"
|
|
||||||
className={styles.apiError}
|
|
||||||
>
|
|
||||||
{apiError}
|
{apiError}
|
||||||
</Alert>
|
</StyledAlert>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<StyledDiv>
|
||||||
className={classnames(
|
|
||||||
styles.contentContainer,
|
|
||||||
themeStyles.contentSpacingY
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Username or email"
|
label="Username or email"
|
||||||
name="username"
|
name="username"
|
||||||
@ -148,7 +148,7 @@ const PasswordAuth: VFC<IPasswordAuthProps> = ({ authDetails, redirect }) => {
|
|||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</StyledDiv>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
@ -1,19 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
loginContainer: {
|
|
||||||
minWidth: '350px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
width: '100%',
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contentContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
apiError: {
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -3,8 +3,8 @@ import { PageContent } from 'component/common/PageContent/PageContent';
|
|||||||
import PasswordField from 'component/common/PasswordField/PasswordField';
|
import PasswordField from 'component/common/PasswordField/PasswordField';
|
||||||
import PasswordChecker, {
|
import PasswordChecker, {
|
||||||
PASSWORD_FORMAT_MESSAGE,
|
PASSWORD_FORMAT_MESSAGE,
|
||||||
} from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker';
|
} from 'component/user/common/ResetPasswordForm/PasswordChecker';
|
||||||
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
|
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher';
|
||||||
import { usePasswordApi } from 'hooks/api/actions/usePasswordApi/usePasswordApi';
|
import { usePasswordApi } from 'hooks/api/actions/usePasswordApi/usePasswordApi';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { SyntheticEvent, useState } from 'react';
|
import { SyntheticEvent, useState } from 'react';
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
resetPassword: {
|
|
||||||
width: '350px',
|
|
||||||
maxWidth: '350px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
display: 'flex',
|
|
||||||
},
|
|
||||||
innerContainer: { width: '40%', minHeight: '100vh' },
|
|
||||||
title: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: '1.2rem',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -2,19 +2,31 @@ import { useState } from 'react';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { OK } from 'constants/statusCodes';
|
import { OK } from 'constants/statusCodes';
|
||||||
import useLoading from 'hooks/useLoading';
|
import useLoading from 'hooks/useLoading';
|
||||||
import { useStyles } from './ResetPassword.styles';
|
import { styled, Typography } from '@mui/material';
|
||||||
import { Typography } from '@mui/material';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import InvalidToken from '../common/InvalidToken/InvalidToken';
|
import InvalidToken from '../common/InvalidToken/InvalidToken';
|
||||||
import useResetPassword from 'hooks/api/getters/useResetPassword/useResetPassword';
|
import useResetPassword from 'hooks/api/getters/useResetPassword/useResetPassword';
|
||||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
import StandaloneLayout from '../common/StandaloneLayout';
|
||||||
import ResetPasswordForm from '../common/ResetPasswordForm/ResetPasswordForm';
|
import ResetPasswordForm from '../common/ResetPasswordForm/ResetPasswordForm';
|
||||||
import ResetPasswordError from '../common/ResetPasswordError/ResetPasswordError';
|
import ResetPasswordError from '../common/ResetPasswordError/ResetPasswordError';
|
||||||
import { useAuthResetPasswordApi } from 'hooks/api/actions/useAuthResetPasswordApi/useAuthResetPasswordApi';
|
import { useAuthResetPasswordApi } from 'hooks/api/actions/useAuthResetPasswordApi/useAuthResetPasswordApi';
|
||||||
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
|
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
|
||||||
|
|
||||||
|
const StyledDiv = styled('div')(({ theme }) => ({
|
||||||
|
width: '350px',
|
||||||
|
maxWidth: '350px',
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: theme.spacing(2.5),
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
const ResetPassword = () => {
|
const ResetPassword = () => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { token, loading, isValidToken } = useResetPassword();
|
const { token, loading, isValidToken } = useResetPassword();
|
||||||
const { resetPassword, loading: actionLoading } = useAuthResetPasswordApi();
|
const { resetPassword, loading: actionLoading } = useAuthResetPasswordApi();
|
||||||
const { authDetails } = useAuthDetails();
|
const { authDetails } = useAuthDetails();
|
||||||
@ -40,19 +52,15 @@ const ResetPassword = () => {
|
|||||||
return (
|
return (
|
||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
<StandaloneLayout>
|
<StandaloneLayout>
|
||||||
<div className={styles.resetPassword}>
|
<StyledDiv>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!isValidToken || passwordDisabled}
|
condition={!isValidToken || passwordDisabled}
|
||||||
show={<InvalidToken />}
|
show={<InvalidToken />}
|
||||||
elseShow={
|
elseShow={
|
||||||
<>
|
<>
|
||||||
<Typography
|
<StyledTypography variant="h2" data-loading>
|
||||||
variant="h2"
|
|
||||||
className={styles.title}
|
|
||||||
data-loading
|
|
||||||
>
|
|
||||||
Reset password
|
Reset password
|
||||||
</Typography>
|
</StyledTypography>
|
||||||
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={hasApiError}
|
condition={hasApiError}
|
||||||
@ -62,7 +70,7 @@ const ResetPassword = () => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledDiv>
|
||||||
</StandaloneLayout>
|
</StandaloneLayout>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
110
frontend/src/component/user/StandaloneBanner.tsx
Normal file
110
frontend/src/component/user/StandaloneBanner.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Typography, useTheme, useMediaQuery, styled } from '@mui/material';
|
||||||
|
import Gradient from 'component/common/Gradient/Gradient';
|
||||||
|
import { ReactComponent as Logo } from 'assets/icons/logoWhiteBg.svg';
|
||||||
|
import { ReactComponent as LogoWithText } from 'assets/img/logoWhiteTransparentHorizontal.svg';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { Theme } from '@mui/material';
|
||||||
|
|
||||||
|
interface IStandaloneBannerProps {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledGradient = styled(Gradient)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
borderBottomLeftRadius: '3px',
|
||||||
|
borderTopLeftRadius: '3px',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(12, 8),
|
||||||
|
color: '#fff',
|
||||||
|
position: 'relative',
|
||||||
|
borderTopLeftRadius: '3px',
|
||||||
|
borderBottomLeftRadius: '3px',
|
||||||
|
textAlign: 'right',
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
padding: theme.spacing(6, 4),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
padding: theme.spacing(6, 2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitle = styled(Typography)(({ theme }) => ({
|
||||||
|
color: '#fff',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
fontSize: theme.spacing(4),
|
||||||
|
marginTop: theme.spacing(10),
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledSubtitle = styled(Typography)(({ theme }) => ({
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
fontSize: theme.spacing(4),
|
||||||
|
fontWeight: 'normal',
|
||||||
|
}));
|
||||||
|
const StyledLogoContainer = styled('div')(({ theme }) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
bottom: '-50px',
|
||||||
|
left: '-50px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const logoStyles = (theme: Theme) => ({
|
||||||
|
width: '200px',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
width: '240px',
|
||||||
|
height: '200px',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledLogoWithText = styled(LogoWithText)(({ theme }) => ({
|
||||||
|
...logoStyles(theme),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledLogo = styled(Logo)(({ theme }) => ({
|
||||||
|
...logoStyles(theme),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StandaloneBanner: FC<IStandaloneBannerProps> = ({ title }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const smallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledGradient
|
||||||
|
from={theme.palette.standaloneBannerGradient.from}
|
||||||
|
to={theme.palette.standaloneBannerGradient.to}
|
||||||
|
>
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledTitle variant="h1">{title}</StyledTitle>
|
||||||
|
<StyledSubtitle>
|
||||||
|
Committed to creating new ways of developing software
|
||||||
|
</StyledSubtitle>
|
||||||
|
</StyledContainer>
|
||||||
|
|
||||||
|
<StyledLogoContainer>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={smallScreen}
|
||||||
|
show={<StyledLogoWithText aria-label="Unleash logo" />}
|
||||||
|
elseShow={<StyledLogo aria-label="Unleash logo" />}
|
||||||
|
/>
|
||||||
|
</StyledLogoContainer>
|
||||||
|
</StyledGradient>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StandaloneBanner;
|
@ -1,61 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
gradient: {
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
[theme.breakpoints.up('sm')]: {
|
|
||||||
borderBottomLeftRadius: '3px',
|
|
||||||
borderTopLeftRadius: '3px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
color: '#fff',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
fontSize: '2rem',
|
|
||||||
marginTop: '5rem',
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
padding: '6rem 4rem',
|
|
||||||
color: '#fff',
|
|
||||||
position: 'relative',
|
|
||||||
borderTopLeftRadius: '3px',
|
|
||||||
borderBottomLeftRadius: '3px',
|
|
||||||
textAlign: 'right',
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
padding: '3rem 2rem',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
padding: '3rem 1rem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bannerSubtitle: {
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
fontSize: '2rem',
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
|
||||||
logoContainer: {
|
|
||||||
position: 'absolute',
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
bottom: '-50px',
|
|
||||||
left: '-50px',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logo: {
|
|
||||||
width: '200px',
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
width: '240px',
|
|
||||||
height: '200px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,54 +0,0 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import { Typography, useTheme, useMediaQuery } from '@mui/material';
|
|
||||||
import Gradient from 'component/common/Gradient/Gradient';
|
|
||||||
import { ReactComponent as Logo } from 'assets/icons/logoWhiteBg.svg';
|
|
||||||
import { ReactComponent as LogoWithText } from 'assets/img/logoWhiteTransparentHorizontal.svg';
|
|
||||||
import { useStyles } from './StandaloneBanner.styles';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
|
|
||||||
interface IStandaloneBannerProps {
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StandaloneBanner: FC<IStandaloneBannerProps> = ({ title, children }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const smallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Gradient
|
|
||||||
from={theme.palette.standaloneBannerGradient.from}
|
|
||||||
to={theme.palette.standaloneBannerGradient.to}
|
|
||||||
className={styles.gradient}
|
|
||||||
>
|
|
||||||
<div className={styles.container}>
|
|
||||||
<Typography variant="h1" className={styles.title}>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
<Typography className={styles.bannerSubtitle}>
|
|
||||||
Committed to creating new ways of developing software
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.logoContainer}>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={smallScreen}
|
|
||||||
show={
|
|
||||||
<LogoWithText
|
|
||||||
className={styles.logo}
|
|
||||||
aria-label="Unleash logo"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
elseShow={
|
|
||||||
<Logo
|
|
||||||
className={styles.logo}
|
|
||||||
aria-label="Unleash logo"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Gradient>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StandaloneBanner;
|
|
@ -1,20 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
userProfileMenu: {
|
|
||||||
display: 'flex',
|
|
||||||
},
|
|
||||||
profileContainer: {
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
color: 'inherit',
|
|
||||||
padding: '0.2rem 1rem',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
color: theme.palette.grey[700],
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,20 +1,37 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import classnames from 'classnames';
|
|
||||||
import { Button, ClickAwayListener, styled } from '@mui/material';
|
import { Button, ClickAwayListener, styled } from '@mui/material';
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||||
import { useStyles } from 'component/user/UserProfile/UserProfile.styles';
|
|
||||||
import { useThemeStyles } from 'themes/themeStyles';
|
|
||||||
import { UserProfileContent } from './UserProfileContent/UserProfileContent';
|
import { UserProfileContent } from './UserProfileContent/UserProfileContent';
|
||||||
import { IUser } from 'interfaces/user';
|
import { IUser } from 'interfaces/user';
|
||||||
import { HEADER_USER_AVATAR } from 'utils/testIds';
|
import { HEADER_USER_AVATAR } from 'utils/testIds';
|
||||||
import { useId } from 'hooks/useId';
|
import { useId } from 'hooks/useId';
|
||||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||||
|
import { flexRow, focusable, itemsCenter } from 'themes/themeStyles';
|
||||||
|
|
||||||
const StyledUserAvatar = styled(UserAvatar)(({ theme }) => ({
|
const StyledUserAvatar = styled(UserAvatar)(({ theme }) => ({
|
||||||
width: theme.spacing(4.5),
|
width: theme.spacing(4.5),
|
||||||
height: theme.spacing(4.5),
|
height: theme.spacing(4.5),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledProfileContainer = styled('div')(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
|
...focusable(theme),
|
||||||
|
...flexRow,
|
||||||
|
...itemsCenter,
|
||||||
|
color: 'inherit',
|
||||||
|
padding: theme.spacing(0.5, 2),
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledIcon = styled(KeyboardArrowDownIcon)(({ theme }) => ({
|
||||||
|
color: theme.palette.neutral.main,
|
||||||
|
}));
|
||||||
|
|
||||||
interface IUserProfileProps {
|
interface IUserProfileProps {
|
||||||
profile: IUser;
|
profile: IUser;
|
||||||
}
|
}
|
||||||
@ -23,19 +40,10 @@ const UserProfile = ({ profile }: IUserProfileProps) => {
|
|||||||
const [showProfile, setShowProfile] = useState(false);
|
const [showProfile, setShowProfile] = useState(false);
|
||||||
const modalId = useId();
|
const modalId = useId();
|
||||||
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { classes: themeStyles } = useThemeStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClickAwayListener onClickAway={() => setShowProfile(false)}>
|
<ClickAwayListener onClickAway={() => setShowProfile(false)}>
|
||||||
<div className={styles.profileContainer}>
|
<StyledProfileContainer>
|
||||||
<Button
|
<StyledButton
|
||||||
className={classnames(
|
|
||||||
themeStyles.flexRow,
|
|
||||||
themeStyles.itemsCenter,
|
|
||||||
themeStyles.focusable,
|
|
||||||
styles.button
|
|
||||||
)}
|
|
||||||
onClick={() => setShowProfile(prev => !prev)}
|
onClick={() => setShowProfile(prev => !prev)}
|
||||||
aria-controls={showProfile ? modalId : undefined}
|
aria-controls={showProfile ? modalId : undefined}
|
||||||
aria-expanded={showProfile}
|
aria-expanded={showProfile}
|
||||||
@ -46,15 +54,15 @@ const UserProfile = ({ profile }: IUserProfileProps) => {
|
|||||||
user={profile}
|
user={profile}
|
||||||
data-testid={HEADER_USER_AVATAR}
|
data-testid={HEADER_USER_AVATAR}
|
||||||
/>
|
/>
|
||||||
<KeyboardArrowDownIcon className={styles.icon} />
|
<StyledIcon />
|
||||||
</Button>
|
</StyledButton>
|
||||||
<UserProfileContent
|
<UserProfileContent
|
||||||
id={modalId}
|
id={modalId}
|
||||||
showProfile={showProfile}
|
showProfile={showProfile}
|
||||||
setShowProfile={setShowProfile}
|
setShowProfile={setShowProfile}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledProfileContainer>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { Typography } from '@mui/material';
|
import { styled, Typography } from '@mui/material';
|
||||||
import classnames from 'classnames';
|
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||||
import { BAD_REQUEST, OK } from 'constants/statusCodes';
|
import { BAD_REQUEST, OK } from 'constants/statusCodes';
|
||||||
import { useStyles } from './PasswordChecker.styles';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
import { Alert } from '@mui/material';
|
import { Alert } from '@mui/material';
|
||||||
@ -38,12 +36,64 @@ const REPEATING_CHARACTER_ERROR =
|
|||||||
export const PASSWORD_FORMAT_MESSAGE =
|
export const PASSWORD_FORMAT_MESSAGE =
|
||||||
'The password must be at least 10 characters long and must include an uppercase letter, a lowercase letter, a number, and a symbol.';
|
'The password must be at least 10 characters long and must include an uppercase letter, a lowercase letter, a number, and a symbol.';
|
||||||
|
|
||||||
|
const StyledTitle = styled(Typography)(({ theme }) => ({
|
||||||
|
marginBottom: '0',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '1ch',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
border: '1px solid #f1f1f1',
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
position: 'relative',
|
||||||
|
maxWidth: '350px',
|
||||||
|
color: '#44606e',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeaderContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCheckContainer = styled('div')(({ theme }) => ({
|
||||||
|
width: '95px',
|
||||||
|
margin: theme.spacing(0, 0.5),
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledDivider = styled('div')(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.neutral.light,
|
||||||
|
height: '1px',
|
||||||
|
width: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledStatusBarContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledError = styled(Alert)(({ theme }) => ({
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
bottom: '0',
|
||||||
|
position: 'absolute',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledStatusBar = styled('div', {
|
||||||
|
shouldForwardProp: prop => prop !== 'error',
|
||||||
|
})<{ error: boolean }>(({ theme, error }) => ({
|
||||||
|
width: '50px',
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
height: '6px',
|
||||||
|
backgroundColor: error ? 'red' : theme.palette.primary.main,
|
||||||
|
}));
|
||||||
|
|
||||||
const PasswordChecker = ({
|
const PasswordChecker = ({
|
||||||
password,
|
password,
|
||||||
callback,
|
callback,
|
||||||
style = {},
|
style = {},
|
||||||
}: IPasswordCheckerProps) => {
|
}: IPasswordCheckerProps) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const [casingError, setCasingError] = useState(true);
|
const [casingError, setCasingError] = useState(true);
|
||||||
const [numberError, setNumberError] = useState(true);
|
const [numberError, setNumberError] = useState(true);
|
||||||
const [symbolError, setSymbolError] = useState(true);
|
const [symbolError, setSymbolError] = useState(true);
|
||||||
@ -141,83 +191,63 @@ const PasswordChecker = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const lengthStatusBarClasses = classnames(styles.statusBar, {
|
|
||||||
[styles.statusBarSuccess]: !lengthError,
|
|
||||||
});
|
|
||||||
|
|
||||||
const numberStatusBarClasses = classnames(styles.statusBar, {
|
|
||||||
[styles.statusBarSuccess]: !numberError,
|
|
||||||
});
|
|
||||||
|
|
||||||
const symbolStatusBarClasses = classnames(styles.statusBar, {
|
|
||||||
[styles.statusBarSuccess]: !symbolError,
|
|
||||||
});
|
|
||||||
|
|
||||||
const casingStatusBarClasses = classnames(styles.statusBar, {
|
|
||||||
[styles.statusBarSuccess]: !casingError,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography variant="body2" className={styles.title} data-loading>
|
<StyledTitle variant="body2" data-loading>
|
||||||
Please set a strong password
|
Please set a strong password
|
||||||
<HelpIcon tooltip={PASSWORD_FORMAT_MESSAGE} />
|
<HelpIcon tooltip={PASSWORD_FORMAT_MESSAGE} />
|
||||||
</Typography>
|
</StyledTitle>
|
||||||
<div
|
<StyledContainer
|
||||||
className={styles.container}
|
|
||||||
style={{
|
style={{
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles.headerContainer}>
|
<StyledHeaderContainer>
|
||||||
<div className={styles.checkContainer}>
|
<StyledCheckContainer>
|
||||||
<Typography variant="body2" data-loading>
|
<Typography variant="body2" data-loading>
|
||||||
Length
|
Length
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</StyledCheckContainer>
|
||||||
<div className={styles.checkContainer}>
|
<StyledCheckContainer>
|
||||||
<Typography variant="body2" data-loading>
|
<Typography variant="body2" data-loading>
|
||||||
Casing
|
Casing
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</StyledCheckContainer>
|
||||||
<div className={styles.checkContainer}>
|
<StyledCheckContainer>
|
||||||
<Typography variant="body2" data-loading>
|
<Typography variant="body2" data-loading>
|
||||||
Number
|
Number
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</StyledCheckContainer>
|
||||||
<div className={styles.checkContainer}>
|
<StyledCheckContainer>
|
||||||
<Typography variant="body2" data-loading>
|
<Typography variant="body2" data-loading>
|
||||||
Symbol
|
Symbol
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</StyledCheckContainer>
|
||||||
</div>
|
</StyledHeaderContainer>
|
||||||
<div className={styles.divider} />
|
<StyledDivider />
|
||||||
<div className={styles.statusBarContainer}>
|
<StyledStatusBarContainer>
|
||||||
<div className={styles.checkContainer}>
|
<StyledCheckContainer>
|
||||||
<div className={lengthStatusBarClasses} data-loading />
|
<StyledStatusBar error={lengthError} data-loading />
|
||||||
</div>
|
</StyledCheckContainer>
|
||||||
<div className={styles.checkContainer}>
|
<StyledCheckContainer>
|
||||||
<div className={casingStatusBarClasses} data-loading />
|
<StyledStatusBar error={casingError} data-loading />
|
||||||
</div>{' '}
|
</StyledCheckContainer>{' '}
|
||||||
<div className={styles.checkContainer}>
|
<StyledCheckContainer>
|
||||||
<div className={numberStatusBarClasses} data-loading />
|
<StyledStatusBar error={numberError} data-loading />
|
||||||
</div>
|
</StyledCheckContainer>
|
||||||
<div className={styles.checkContainer}>
|
<StyledCheckContainer>
|
||||||
<div className={symbolStatusBarClasses} data-loading />
|
<StyledStatusBar error={symbolError} data-loading />
|
||||||
</div>
|
</StyledCheckContainer>
|
||||||
</div>
|
</StyledStatusBarContainer>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={repeatingCharError}
|
condition={repeatingCharError}
|
||||||
show={
|
show={
|
||||||
<Alert
|
<StyledError severity="error">
|
||||||
severity="error"
|
|
||||||
className={styles.repeatingError}
|
|
||||||
>
|
|
||||||
You may not repeat three characters in a row.
|
You may not repeat three characters in a row.
|
||||||
</Alert>
|
</StyledError>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</StyledContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -1,47 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
border: '1px solid #f1f1f1',
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
position: 'relative',
|
|
||||||
maxWidth: '350px',
|
|
||||||
color: '#44606e',
|
|
||||||
},
|
|
||||||
headerContainer: { display: 'flex', padding: '0.5rem' },
|
|
||||||
divider: {
|
|
||||||
backgroundColor: theme.palette.neutral.light,
|
|
||||||
height: '1px',
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
checkContainer: {
|
|
||||||
width: '95px',
|
|
||||||
margin: '0 0.25rem',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
statusBarContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
padding: '0.5rem',
|
|
||||||
},
|
|
||||||
statusBar: {
|
|
||||||
width: '50px',
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
backgroundColor: 'red',
|
|
||||||
height: '6px',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
marginBottom: '0',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '1ch',
|
|
||||||
},
|
|
||||||
statusBarSuccess: {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
repeatingError: {
|
|
||||||
marginTop: '0.5rem',
|
|
||||||
bottom: '0',
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -0,0 +1,60 @@
|
|||||||
|
import { styled, Typography } from '@mui/material';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
|
|
||||||
|
interface IPasswordMatcherProps {
|
||||||
|
started: boolean;
|
||||||
|
matchingPasswords: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledMatcherContainer = styled('div')(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
|
paddingTop: theme.spacing(0.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMatcher = styled(Typography, {
|
||||||
|
shouldForwardProp: prop => prop !== 'matchingPasswords',
|
||||||
|
})<{ matchingPasswords: boolean }>(({ theme, matchingPasswords }) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '-8px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: matchingPasswords
|
||||||
|
? theme.palette.primary.main
|
||||||
|
: theme.palette.error.main,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMatcherCheckIcon = styled(CheckIcon)(({ theme }) => ({
|
||||||
|
marginRight: '5px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const PasswordMatcher = ({
|
||||||
|
started,
|
||||||
|
matchingPasswords,
|
||||||
|
}: IPasswordMatcherProps) => {
|
||||||
|
return (
|
||||||
|
<StyledMatcherContainer>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={started}
|
||||||
|
show={
|
||||||
|
<StyledMatcher
|
||||||
|
variant="body2"
|
||||||
|
data-loading
|
||||||
|
matchingPasswords={matchingPasswords}
|
||||||
|
>
|
||||||
|
<StyledMatcherCheckIcon />{' '}
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={matchingPasswords}
|
||||||
|
show={<Typography> Passwords match</Typography>}
|
||||||
|
elseShow={
|
||||||
|
<Typography> Passwords do not match</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledMatcher>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledMatcherContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PasswordMatcher;
|
@ -1,23 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
matcherContainer: {
|
|
||||||
position: 'relative',
|
|
||||||
paddingTop: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
matcherIcon: {
|
|
||||||
marginRight: '5px',
|
|
||||||
},
|
|
||||||
matcher: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '-8px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
matcherError: {
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
},
|
|
||||||
matcherSuccess: {
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,55 +0,0 @@
|
|||||||
import { Typography } from '@mui/material';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
|
||||||
|
|
||||||
import { useStyles } from './PasswordMatcher.styles';
|
|
||||||
|
|
||||||
interface IPasswordMatcherProps {
|
|
||||||
started: boolean;
|
|
||||||
matchingPasswords: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PasswordMatcher = ({
|
|
||||||
started,
|
|
||||||
matchingPasswords,
|
|
||||||
}: IPasswordMatcherProps) => {
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
return (
|
|
||||||
<div className={styles.matcherContainer}>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={started}
|
|
||||||
show={
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={matchingPasswords}
|
|
||||||
show={
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
data-loading
|
|
||||||
className={classnames(styles.matcher, {
|
|
||||||
[styles.matcherSuccess]: matchingPasswords,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<CheckIcon className={styles.matcherIcon} />{' '}
|
|
||||||
Passwords match
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
elseShow={
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
data-loading
|
|
||||||
className={classnames(styles.matcher, {
|
|
||||||
[styles.matcherError]: !matchingPasswords,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
Passwords do not match
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PasswordMatcher;
|
|
@ -1,8 +1,8 @@
|
|||||||
import { Button, styled } from '@mui/material';
|
import { Button, styled } from '@mui/material';
|
||||||
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
|
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import PasswordChecker from './PasswordChecker/PasswordChecker';
|
import PasswordChecker from './PasswordChecker';
|
||||||
import PasswordMatcher from './PasswordMatcher/PasswordMatcher';
|
import PasswordMatcher from './PasswordMatcher';
|
||||||
import PasswordField from 'component/common/PasswordField/PasswordField';
|
import PasswordField from 'component/common/PasswordField/PasswordField';
|
||||||
|
|
||||||
interface IResetPasswordProps {
|
interface IResetPasswordProps {
|
||||||
|
49
frontend/src/component/user/common/SecondaryLoginActions.tsx
Normal file
49
frontend/src/component/user/common/SecondaryLoginActions.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Alert, styled, Typography } from '@mui/material';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
margin: 'auto auto 0 auto',
|
||||||
|
width: '230px',
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledLink = styled(Link)(({ theme }) => ({
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledRef = styled('a')(({ theme }) => ({
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SecondaryLoginActions = () => {
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledLink to="/forgotten-password">
|
||||||
|
<StyledTypography variant="body2">
|
||||||
|
Forgot password?
|
||||||
|
</StyledTypography>
|
||||||
|
</StyledLink>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Don't have an account?{' '}
|
||||||
|
<StyledRef
|
||||||
|
href="https://www.getunleash.io/plans"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Sign up
|
||||||
|
</StyledRef>
|
||||||
|
</Typography>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecondaryLoginActions;
|
@ -1,16 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
margin: 'auto auto 0 auto',
|
|
||||||
width: '230px',
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
marginTop: '1rem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
text: { fontWeight: 'bold', marginBottom: '0.5rem' },
|
|
||||||
}));
|
|
@ -1,29 +0,0 @@
|
|||||||
import { Typography } from '@mui/material';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { useStyles } from './SecondaryLoginActions.styles';
|
|
||||||
|
|
||||||
const SecondaryLoginActions = () => {
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<Link to="/forgotten-password" className={styles.link}>
|
|
||||||
<Typography variant="body2" className={styles.text}>
|
|
||||||
Forgot password?
|
|
||||||
</Typography>
|
|
||||||
</Link>
|
|
||||||
<Typography variant="body2">
|
|
||||||
Don't have an account?{' '}
|
|
||||||
<a
|
|
||||||
href="https://www.getunleash.io/plans"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className={styles.link}
|
|
||||||
>
|
|
||||||
Sign up
|
|
||||||
</a>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SecondaryLoginActions;
|
|
83
frontend/src/component/user/common/StandaloneLayout.tsx
Normal file
83
frontend/src/component/user/common/StandaloneLayout.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import StandaloneBanner from 'component/user/StandaloneBanner';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
|
interface IStandaloneLayout {
|
||||||
|
BannerComponent?: JSX.Element;
|
||||||
|
showMenu?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(11),
|
||||||
|
background: theme.palette.standaloneBackground,
|
||||||
|
display: 'flex',
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
padding: '0',
|
||||||
|
},
|
||||||
|
minHeight: '100vh',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeader = styled('header')(({ theme }) => ({
|
||||||
|
width: '40%',
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
borderRadius: '0',
|
||||||
|
width: '100%',
|
||||||
|
minHeight: 'auto',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMain = styled('main')(({ theme }) => ({
|
||||||
|
width: '60%',
|
||||||
|
flex: '1',
|
||||||
|
borderTopRightRadius: '3px',
|
||||||
|
borderBottomRightRadius: '3px',
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
position: 'relative',
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
borderRadius: '0',
|
||||||
|
width: '100%',
|
||||||
|
position: 'static',
|
||||||
|
minHeight: 'auto',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInnerRightContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '100%',
|
||||||
|
padding: theme.spacing(12, 6),
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
padding: theme.spacing(4, 4),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
padding: theme.spacing(4, 2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StandaloneLayout: FC<IStandaloneLayout> = ({
|
||||||
|
children,
|
||||||
|
BannerComponent,
|
||||||
|
}) => {
|
||||||
|
let banner = <StandaloneBanner title="Unleash" />;
|
||||||
|
|
||||||
|
if (BannerComponent) {
|
||||||
|
banner = BannerComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledHeader>{banner}</StyledHeader>
|
||||||
|
<StyledMain>
|
||||||
|
<StyledInnerRightContainer>
|
||||||
|
{children}
|
||||||
|
</StyledInnerRightContainer>
|
||||||
|
</StyledMain>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StandaloneLayout;
|
@ -1,56 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
padding: '5.5rem',
|
|
||||||
background: theme.palette.standaloneBackground,
|
|
||||||
display: 'flex',
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
padding: '0',
|
|
||||||
},
|
|
||||||
minHeight: '100vh',
|
|
||||||
},
|
|
||||||
leftContainer: {
|
|
||||||
width: '40%',
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
borderRadius: '0',
|
|
||||||
width: '100%',
|
|
||||||
minHeight: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rightContainer: {
|
|
||||||
width: '60%',
|
|
||||||
flex: '1',
|
|
||||||
borderTopRightRadius: '3px',
|
|
||||||
borderBottomRightRadius: '3px',
|
|
||||||
backgroundColor: theme.palette.background.paper,
|
|
||||||
position: 'relative',
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
borderRadius: '0',
|
|
||||||
width: '100%',
|
|
||||||
position: 'static',
|
|
||||||
minHeight: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: '1.2rem',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
innerRightContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
height: '100%',
|
|
||||||
padding: '6rem 3rem',
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
padding: '2rem 2rem',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
padding: '2rem 1rem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,32 +0,0 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import StandaloneBanner from 'component/user/StandaloneBanner/StandaloneBanner';
|
|
||||||
import { useStyles } from './StandaloneLayout.styles';
|
|
||||||
|
|
||||||
interface IStandaloneLayout {
|
|
||||||
BannerComponent?: JSX.Element;
|
|
||||||
showMenu?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StandaloneLayout: FC<IStandaloneLayout> = ({
|
|
||||||
children,
|
|
||||||
BannerComponent,
|
|
||||||
}) => {
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
let banner = <StandaloneBanner title="Unleash" />;
|
|
||||||
|
|
||||||
if (BannerComponent) {
|
|
||||||
banner = BannerComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<header className={styles.leftContainer}>{banner}</header>
|
|
||||||
<main className={styles.rightContainer}>
|
|
||||||
<div className={styles.innerRightContainer}>{children}</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StandaloneLayout;
|
|
@ -11,11 +11,37 @@ export const focusable = (theme: Theme) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const contentSpacingY = (theme: Theme) => ({
|
||||||
|
'& > *': {
|
||||||
|
marginTop: `${theme.spacing(1)} !important`,
|
||||||
|
marginBottom: `${theme.spacing(1)} !important`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const title = (theme: Theme) => ({
|
||||||
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const textCenter = {
|
||||||
|
textAlign: 'center',
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const flexRow = {
|
export const flexRow = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const flexColumn = {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const itemsCenter = {
|
||||||
|
alignItems: 'center',
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const defaultBorderRadius = (theme: Theme) => ({
|
export const defaultBorderRadius = (theme: Theme) => ({
|
||||||
borderRadius: `${theme.shape.borderRadius}px`,
|
borderRadius: `${theme.shape.borderRadius}px`,
|
||||||
});
|
});
|
||||||
@ -40,18 +66,6 @@ export const useThemeStyles = makeStyles()(theme => ({
|
|||||||
marginBottom: '0.5rem !important',
|
marginBottom: '0.5rem !important',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
contentSpacingYLarge: {
|
|
||||||
'& > *': {
|
|
||||||
marginTop: '1.5rem !important',
|
|
||||||
marginBottom: '1.5rem !important',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contentSpacingX: {
|
|
||||||
'& > *': {
|
|
||||||
marginRight: '0.8rem !important',
|
|
||||||
marginLeft: '0.8rem !important',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
relative: {
|
relative: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
},
|
},
|
||||||
@ -101,19 +115,6 @@ export const useThemeStyles = makeStyles()(theme => ({
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
marginBottom: '0.5rem',
|
marginBottom: '0.5rem',
|
||||||
},
|
},
|
||||||
fadeInBottomStartNoPosition: {
|
|
||||||
transform: 'translateY(400px)',
|
|
||||||
opacity: '0',
|
|
||||||
boxShadow: `rgb(129 129 129 / 40%) 4px 5px 11px 4px`,
|
|
||||||
zIndex: 500,
|
|
||||||
width: '100%',
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
height: '300px',
|
|
||||||
position: 'fixed',
|
|
||||||
},
|
|
||||||
fadeInBottomStart: {
|
fadeInBottomStart: {
|
||||||
opacity: '0',
|
opacity: '0',
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
|
Loading…
Reference in New Issue
Block a user