mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-15 01:16:22 +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 PasswordChecker, {
|
||||
PASSWORD_FORMAT_MESSAGE,
|
||||
} from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker';
|
||||
} from 'component/user/common/ResetPasswordForm/PasswordChecker';
|
||||
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 useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
|
||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import SimpleAuth from '../SimpleAuth/SimpleAuth';
|
||||
import { AuthenticationCustomComponent } from 'component/user/AuthenticationCustomComponent';
|
||||
import PasswordAuth from '../PasswordAuth/PasswordAuth';
|
||||
import HostedAuth from '../HostedAuth/HostedAuth';
|
||||
import PasswordAuth from '../PasswordAuth';
|
||||
import HostedAuth from '../HostedAuth';
|
||||
import DemoAuth from '../DemoAuth/DemoAuth';
|
||||
import {
|
||||
SIMPLE_TYPE,
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PASSWORD_TYPE,
|
||||
HOSTED_TYPE,
|
||||
} from 'constants/authTypes';
|
||||
import SecondaryLoginActions from '../common/SecondaryLoginActions/SecondaryLoginActions';
|
||||
import SecondaryLoginActions from '../common/SecondaryLoginActions';
|
||||
import useQueryParams from 'hooks/useQueryParams';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
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 classnames from 'classnames';
|
||||
import { SyntheticEvent, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useThemeStyles } from 'themes/themeStyles';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { FORGOTTEN_PASSWORD_FIELD } from 'utils/testIds';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import DividerText from 'component/common/DividerText/DividerText';
|
||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
||||
import { useStyles } from './ForgottenPassword.styles';
|
||||
import StandaloneLayout from '../common/StandaloneLayout';
|
||||
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 [email, setEmail] = useState('');
|
||||
const [attempted, setAttempted] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [attemptedEmail, setAttemptedEmail] = useState('');
|
||||
const { classes: themeStyles } = useThemeStyles();
|
||||
const { classes: styles } = useStyles();
|
||||
const ref = useLoading(loading);
|
||||
|
||||
const onClick = async (e: SyntheticEvent) => {
|
||||
@ -41,32 +75,15 @@ const ForgottenPassword = () => {
|
||||
|
||||
return (
|
||||
<StandaloneLayout>
|
||||
<div
|
||||
className={classnames(
|
||||
themeStyles.contentSpacingY,
|
||||
themeStyles.flexColumn,
|
||||
styles.forgottenPassword
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
<h2
|
||||
className={classnames(
|
||||
themeStyles.title,
|
||||
themeStyles.textCenter
|
||||
)}
|
||||
data-loading
|
||||
>
|
||||
Forgotten password
|
||||
</h2>
|
||||
<StyledDiv ref={ref}>
|
||||
<StyledTitle data-loading>Forgotten password</StyledTitle>
|
||||
<ConditionallyRender
|
||||
condition={attempted}
|
||||
show={
|
||||
<Alert severity="success" data-loading>
|
||||
<AlertTitle>Attempted to send email</AlertTitle>
|
||||
We've attempted to send a reset password email to:
|
||||
<strong className={styles.email}>
|
||||
{attemptedEmail}
|
||||
</strong>
|
||||
<StyledStrong>{attemptedEmail}</StyledStrong>
|
||||
If you did not receive an email, please verify that
|
||||
you typed in the correct email, and contact your
|
||||
administrator to make sure that you are in the
|
||||
@ -74,21 +91,11 @@ const ForgottenPassword = () => {
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
<form
|
||||
onSubmit={onClick}
|
||||
className={classnames(
|
||||
themeStyles.contentSpacingY,
|
||||
themeStyles.flexColumn
|
||||
)}
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
data-loading
|
||||
className={themeStyles.textCenter}
|
||||
>
|
||||
<StyledForm onSubmit={onClick}>
|
||||
<StyledTypography variant="body1" data-loading>
|
||||
Please provide your email address. If it exists in the
|
||||
system we'll send a new reset link.
|
||||
</Typography>
|
||||
</StyledTypography>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
size="small"
|
||||
@ -101,12 +108,11 @@ const ForgottenPassword = () => {
|
||||
setEmail(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
<StyledButton
|
||||
variant="contained"
|
||||
type="submit"
|
||||
data-loading
|
||||
color="primary"
|
||||
className={styles.button}
|
||||
disabled={loading}
|
||||
>
|
||||
<ConditionallyRender
|
||||
@ -114,21 +120,24 @@ const ForgottenPassword = () => {
|
||||
show={<span>Submit</span>}
|
||||
elseShow={<span>Try again</span>}
|
||||
/>
|
||||
</Button>
|
||||
</StyledButton>
|
||||
<DividerText text="Or log in" />
|
||||
<Button
|
||||
type="submit"
|
||||
data-loading
|
||||
variant="outlined"
|
||||
className={styles.button}
|
||||
disabled={loading}
|
||||
component={Link}
|
||||
to="/login"
|
||||
sx={theme => ({
|
||||
width: '150px',
|
||||
margin: theme.spacing(2, 'auto'),
|
||||
})}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</StyledForm>
|
||||
</StyledDiv>
|
||||
</StandaloneLayout>
|
||||
);
|
||||
};
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { FormEventHandler, useState, VFC } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Button, Grid, TextField, Typography } from '@mui/material';
|
||||
import { Button, Grid, styled, TextField, Typography } from '@mui/material';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useThemeStyles } from 'themes/themeStyles';
|
||||
import { useStyles } from './HostedAuth.styles';
|
||||
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 { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
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 { IAuthEndpointDetailsResponse } from 'hooks/api/getters/useAuth/useAuthEndpoint';
|
||||
import { BadRequestError, NotFoundError } from 'utils/apiUtils';
|
||||
import { contentSpacingY } from 'themes/themeStyles';
|
||||
|
||||
interface IHostedAuthProps {
|
||||
authDetails: IAuthEndpointDetailsResponse;
|
||||
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 { classes: themeStyles } = useThemeStyles();
|
||||
const { classes: styles } = useStyles();
|
||||
const { refetchUser } = useAuthUser();
|
||||
const navigate = useNavigate();
|
||||
const params = useQueryParams();
|
||||
@ -97,18 +110,10 @@ const HostedAuth: VFC<IHostedAuthProps> = ({ authDetails, redirect }) => {
|
||||
condition={!authDetails.defaultHidden}
|
||||
show={
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
className={styles.apiError}
|
||||
>
|
||||
<StyledTypography variant="subtitle2">
|
||||
{apiError}
|
||||
</Typography>
|
||||
<div
|
||||
className={classnames(
|
||||
styles.contentContainer,
|
||||
themeStyles.contentSpacingY
|
||||
)}
|
||||
>
|
||||
</StyledTypography>
|
||||
<StyledDiv>
|
||||
<TextField
|
||||
label="Username or email"
|
||||
name="username"
|
||||
@ -134,17 +139,16 @@ const HostedAuth: VFC<IHostedAuthProps> = ({ authDetails, redirect }) => {
|
||||
data-testid={LOGIN_PASSWORD_ID}
|
||||
/>
|
||||
<Grid container>
|
||||
<Button
|
||||
<StyledButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
className={styles.button}
|
||||
data-testid={LOGIN_BUTTON}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</StyledButton>
|
||||
</Grid>
|
||||
</div>
|
||||
</StyledDiv>
|
||||
</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 { Alert, AlertTitle } from '@mui/material';
|
||||
import { Alert, AlertTitle, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useStyles } from 'component/user/Login/Login.styles';
|
||||
import useQueryParams from 'hooks/useQueryParams';
|
||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
||||
import StandaloneLayout from '../common/StandaloneLayout';
|
||||
import { DEMO_TYPE } from 'constants/authTypes';
|
||||
import Authentication from '../Authentication/Authentication';
|
||||
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
|
||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||
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 { classes: styles } = useStyles();
|
||||
const { authDetails } = useAuthDetails();
|
||||
const { user } = useAuthUser();
|
||||
const query = useQueryParams();
|
||||
@ -25,7 +34,7 @@ const Login = () => {
|
||||
|
||||
return (
|
||||
<StandaloneLayout>
|
||||
<div className={styles.loginFormContainer}>
|
||||
<StyledDiv>
|
||||
<ConditionallyRender
|
||||
condition={resetPassword}
|
||||
show={
|
||||
@ -47,13 +56,13 @@ const Login = () => {
|
||||
<ConditionallyRender
|
||||
condition={authDetails?.type !== DEMO_TYPE}
|
||||
show={
|
||||
<h2 className={styles.title}>
|
||||
<StyledHeader>
|
||||
Log in to continue the great work
|
||||
</h2>
|
||||
</StyledHeader>
|
||||
}
|
||||
/>
|
||||
<Authentication redirect={redirect} />
|
||||
</div>
|
||||
</StyledDiv>
|
||||
</StandaloneLayout>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC } from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import StandaloneLayout from 'component/user/common/StandaloneLayout/StandaloneLayout';
|
||||
import StandaloneBanner from 'component/user/StandaloneBanner/StandaloneBanner';
|
||||
import StandaloneLayout from 'component/user/common/StandaloneLayout';
|
||||
import StandaloneBanner from 'component/user/StandaloneBanner';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { FormEventHandler, useState, VFC } from 'react';
|
||||
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 { useNavigate } from 'react-router';
|
||||
import { useThemeStyles } from 'themes/themeStyles';
|
||||
import { useStyles } from './PasswordAuth.styles';
|
||||
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 { Alert } from '@mui/material';
|
||||
import { LOGIN_BUTTON, LOGIN_EMAIL_ID, LOGIN_PASSWORD_ID } from 'utils/testIds';
|
||||
@ -19,15 +17,25 @@ import {
|
||||
BadRequestError,
|
||||
NotFoundError,
|
||||
} from 'utils/apiUtils';
|
||||
import { contentSpacingY } from 'themes/themeStyles';
|
||||
|
||||
interface IPasswordAuthProps {
|
||||
authDetails: IAuthEndpointDetailsResponse;
|
||||
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 { classes: themeStyles } = useThemeStyles();
|
||||
const { classes: styles } = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const { refetchUser } = useAuthUser();
|
||||
const params = useQueryParams();
|
||||
@ -98,21 +106,13 @@ const PasswordAuth: VFC<IPasswordAuthProps> = ({ authDetails, redirect }) => {
|
||||
<ConditionallyRender
|
||||
condition={Boolean(apiError)}
|
||||
show={
|
||||
<Alert
|
||||
severity="error"
|
||||
className={styles.apiError}
|
||||
>
|
||||
<StyledAlert severity="error">
|
||||
{apiError}
|
||||
</Alert>
|
||||
</StyledAlert>
|
||||
}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={classnames(
|
||||
styles.contentContainer,
|
||||
themeStyles.contentSpacingY
|
||||
)}
|
||||
>
|
||||
<StyledDiv>
|
||||
<TextField
|
||||
label="Username or email"
|
||||
name="username"
|
||||
@ -148,7 +148,7 @@ const PasswordAuth: VFC<IPasswordAuthProps> = ({ authDetails, redirect }) => {
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</div>
|
||||
</StyledDiv>
|
||||
</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 PasswordChecker, {
|
||||
PASSWORD_FORMAT_MESSAGE,
|
||||
} from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker';
|
||||
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
|
||||
} from 'component/user/common/ResetPasswordForm/PasswordChecker';
|
||||
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher';
|
||||
import { usePasswordApi } from 'hooks/api/actions/usePasswordApi/usePasswordApi';
|
||||
import useToast from 'hooks/useToast';
|
||||
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 { OK } from 'constants/statusCodes';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { useStyles } from './ResetPassword.styles';
|
||||
import { Typography } from '@mui/material';
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import InvalidToken from '../common/InvalidToken/InvalidToken';
|
||||
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 ResetPasswordError from '../common/ResetPasswordError/ResetPasswordError';
|
||||
import { useAuthResetPasswordApi } from 'hooks/api/actions/useAuthResetPasswordApi/useAuthResetPasswordApi';
|
||||
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 { classes: styles } = useStyles();
|
||||
const { token, loading, isValidToken } = useResetPassword();
|
||||
const { resetPassword, loading: actionLoading } = useAuthResetPasswordApi();
|
||||
const { authDetails } = useAuthDetails();
|
||||
@ -40,19 +52,15 @@ const ResetPassword = () => {
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<StandaloneLayout>
|
||||
<div className={styles.resetPassword}>
|
||||
<StyledDiv>
|
||||
<ConditionallyRender
|
||||
condition={!isValidToken || passwordDisabled}
|
||||
show={<InvalidToken />}
|
||||
elseShow={
|
||||
<>
|
||||
<Typography
|
||||
variant="h2"
|
||||
className={styles.title}
|
||||
data-loading
|
||||
>
|
||||
<StyledTypography variant="h2" data-loading>
|
||||
Reset password
|
||||
</Typography>
|
||||
</StyledTypography>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={hasApiError}
|
||||
@ -62,7 +70,7 @@ const ResetPassword = () => {
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</StyledDiv>
|
||||
</StandaloneLayout>
|
||||
</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 classnames from 'classnames';
|
||||
import { Button, ClickAwayListener, styled } from '@mui/material';
|
||||
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 { IUser } from 'interfaces/user';
|
||||
import { HEADER_USER_AVATAR } from 'utils/testIds';
|
||||
import { useId } from 'hooks/useId';
|
||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||
import { flexRow, focusable, itemsCenter } from 'themes/themeStyles';
|
||||
|
||||
const StyledUserAvatar = styled(UserAvatar)(({ theme }) => ({
|
||||
width: 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 {
|
||||
profile: IUser;
|
||||
}
|
||||
@ -23,19 +40,10 @@ const UserProfile = ({ profile }: IUserProfileProps) => {
|
||||
const [showProfile, setShowProfile] = useState(false);
|
||||
const modalId = useId();
|
||||
|
||||
const { classes: styles } = useStyles();
|
||||
const { classes: themeStyles } = useThemeStyles();
|
||||
|
||||
return (
|
||||
<ClickAwayListener onClickAway={() => setShowProfile(false)}>
|
||||
<div className={styles.profileContainer}>
|
||||
<Button
|
||||
className={classnames(
|
||||
themeStyles.flexRow,
|
||||
themeStyles.itemsCenter,
|
||||
themeStyles.focusable,
|
||||
styles.button
|
||||
)}
|
||||
<StyledProfileContainer>
|
||||
<StyledButton
|
||||
onClick={() => setShowProfile(prev => !prev)}
|
||||
aria-controls={showProfile ? modalId : undefined}
|
||||
aria-expanded={showProfile}
|
||||
@ -46,15 +54,15 @@ const UserProfile = ({ profile }: IUserProfileProps) => {
|
||||
user={profile}
|
||||
data-testid={HEADER_USER_AVATAR}
|
||||
/>
|
||||
<KeyboardArrowDownIcon className={styles.icon} />
|
||||
</Button>
|
||||
<StyledIcon />
|
||||
</StyledButton>
|
||||
<UserProfileContent
|
||||
id={modalId}
|
||||
showProfile={showProfile}
|
||||
setShowProfile={setShowProfile}
|
||||
profile={profile}
|
||||
/>
|
||||
</div>
|
||||
</StyledProfileContainer>
|
||||
</ClickAwayListener>
|
||||
);
|
||||
};
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Typography } from '@mui/material';
|
||||
import classnames from 'classnames';
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { BAD_REQUEST, OK } from 'constants/statusCodes';
|
||||
import { useStyles } from './PasswordChecker.styles';
|
||||
import { useCallback } from 'react';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import { Alert } from '@mui/material';
|
||||
@ -38,12 +36,64 @@ const REPEATING_CHARACTER_ERROR =
|
||||
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.';
|
||||
|
||||
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 = ({
|
||||
password,
|
||||
callback,
|
||||
style = {},
|
||||
}: IPasswordCheckerProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const [casingError, setCasingError] = useState(true);
|
||||
const [numberError, setNumberError] = 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 (
|
||||
<>
|
||||
<Typography variant="body2" className={styles.title} data-loading>
|
||||
<StyledTitle variant="body2" data-loading>
|
||||
Please set a strong password
|
||||
<HelpIcon tooltip={PASSWORD_FORMAT_MESSAGE} />
|
||||
</Typography>
|
||||
<div
|
||||
className={styles.container}
|
||||
</StyledTitle>
|
||||
<StyledContainer
|
||||
style={{
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<div className={styles.headerContainer}>
|
||||
<div className={styles.checkContainer}>
|
||||
<StyledHeaderContainer>
|
||||
<StyledCheckContainer>
|
||||
<Typography variant="body2" data-loading>
|
||||
Length
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.checkContainer}>
|
||||
</StyledCheckContainer>
|
||||
<StyledCheckContainer>
|
||||
<Typography variant="body2" data-loading>
|
||||
Casing
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.checkContainer}>
|
||||
</StyledCheckContainer>
|
||||
<StyledCheckContainer>
|
||||
<Typography variant="body2" data-loading>
|
||||
Number
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.checkContainer}>
|
||||
</StyledCheckContainer>
|
||||
<StyledCheckContainer>
|
||||
<Typography variant="body2" data-loading>
|
||||
Symbol
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.divider} />
|
||||
<div className={styles.statusBarContainer}>
|
||||
<div className={styles.checkContainer}>
|
||||
<div className={lengthStatusBarClasses} data-loading />
|
||||
</div>
|
||||
<div className={styles.checkContainer}>
|
||||
<div className={casingStatusBarClasses} data-loading />
|
||||
</div>{' '}
|
||||
<div className={styles.checkContainer}>
|
||||
<div className={numberStatusBarClasses} data-loading />
|
||||
</div>
|
||||
<div className={styles.checkContainer}>
|
||||
<div className={symbolStatusBarClasses} data-loading />
|
||||
</div>
|
||||
</div>
|
||||
</StyledCheckContainer>
|
||||
</StyledHeaderContainer>
|
||||
<StyledDivider />
|
||||
<StyledStatusBarContainer>
|
||||
<StyledCheckContainer>
|
||||
<StyledStatusBar error={lengthError} data-loading />
|
||||
</StyledCheckContainer>
|
||||
<StyledCheckContainer>
|
||||
<StyledStatusBar error={casingError} data-loading />
|
||||
</StyledCheckContainer>{' '}
|
||||
<StyledCheckContainer>
|
||||
<StyledStatusBar error={numberError} data-loading />
|
||||
</StyledCheckContainer>
|
||||
<StyledCheckContainer>
|
||||
<StyledStatusBar error={symbolError} data-loading />
|
||||
</StyledCheckContainer>
|
||||
</StyledStatusBarContainer>
|
||||
<ConditionallyRender
|
||||
condition={repeatingCharError}
|
||||
show={
|
||||
<Alert
|
||||
severity="error"
|
||||
className={styles.repeatingError}
|
||||
>
|
||||
<StyledError severity="error">
|
||||
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 React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import PasswordChecker from './PasswordChecker/PasswordChecker';
|
||||
import PasswordMatcher from './PasswordMatcher/PasswordMatcher';
|
||||
import PasswordChecker from './PasswordChecker';
|
||||
import PasswordMatcher from './PasswordMatcher';
|
||||
import PasswordField from 'component/common/PasswordField/PasswordField';
|
||||
|
||||
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 = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
} as const;
|
||||
|
||||
export const flexColumn = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
} as const;
|
||||
|
||||
export const itemsCenter = {
|
||||
alignItems: 'center',
|
||||
} as const;
|
||||
|
||||
export const defaultBorderRadius = (theme: Theme) => ({
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
});
|
||||
@ -40,18 +66,6 @@ export const useThemeStyles = makeStyles()(theme => ({
|
||||
marginBottom: '0.5rem !important',
|
||||
},
|
||||
},
|
||||
contentSpacingYLarge: {
|
||||
'& > *': {
|
||||
marginTop: '1.5rem !important',
|
||||
marginBottom: '1.5rem !important',
|
||||
},
|
||||
},
|
||||
contentSpacingX: {
|
||||
'& > *': {
|
||||
marginRight: '0.8rem !important',
|
||||
marginLeft: '0.8rem !important',
|
||||
},
|
||||
},
|
||||
relative: {
|
||||
position: 'relative',
|
||||
},
|
||||
@ -101,19 +115,6 @@ export const useThemeStyles = makeStyles()(theme => ({
|
||||
fontWeight: 'bold',
|
||||
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: {
|
||||
opacity: '0',
|
||||
position: 'fixed',
|
||||
|
Loading…
Reference in New Issue
Block a user