1
0
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:
sjaanus 2023-01-04 11:17:13 +02:00 committed by GitHub
parent 0af162a8e6
commit 45652f6bf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 585 additions and 742 deletions

View File

@ -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';

View File

@ -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';

View File

@ -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',
},
}));

View File

@ -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>
);
};

View File

@ -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>
}
/>

View File

@ -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',
},
}));

View File

@ -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',
},
}));

View File

@ -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>
);
};

View File

@ -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';

View File

@ -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>
}
/>

View File

@ -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',
},
}));

View File

@ -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';

View File

@ -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',
},
}));

View File

@ -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>
);

View 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;

View File

@ -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',
},
},
}));

View File

@ -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;

View File

@ -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],
},
}));

View File

@ -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>
);
};

View File

@ -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>
</>
);
};

View File

@ -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',
},
}));

View File

@ -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;

View File

@ -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,
},
}));

View File

@ -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;

View File

@ -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 {

View 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;

View File

@ -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' },
}));

View File

@ -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;

View 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;

View File

@ -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',
},
},
}));

View File

@ -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;

View File

@ -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',