mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Fix/standalone pages (#300)
* feat: change layout * fix: standalone banner styling * fix: change styling for banner * fix: login page * fix: standalone pages * fix: page tweaks * fix: center text * refactor: new user page * refactor: remove uneccesary markup * refactor: remove secondary actions from hosted * fix: remove authdetails blob * refactor: remove unused import * fix: move overflow * fix: add breakpoint to forgotten password
This commit is contained in:
parent
998cdf98ab
commit
bd93c5d131
@ -60,6 +60,7 @@
|
|||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
id="button"
|
id="button"
|
||||||
|
|
||||||
transform="translate(4.000000, 4.000000)"
|
transform="translate(4.000000, 4.000000)"
|
||||||
filter="url(#filter-1)"
|
filter="url(#filter-1)"
|
||||||
>
|
>
|
||||||
@ -74,22 +75,22 @@
|
|||||||
<path
|
<path
|
||||||
d="M17.64,9.20454545 C17.64,8.56636364 17.5827273,7.95272727 17.4763636,7.36363636 L9,7.36363636 L9,10.845 L13.8436364,10.845 C13.635,11.97 13.0009091,12.9231818 12.0477273,13.5613636 L12.0477273,15.8195455 L14.9563636,15.8195455 C16.6581818,14.2527273 17.64,11.9454545 17.64,9.20454545 L17.64,9.20454545 Z"
|
d="M17.64,9.20454545 C17.64,8.56636364 17.5827273,7.95272727 17.4763636,7.36363636 L9,7.36363636 L9,10.845 L13.8436364,10.845 C13.635,11.97 13.0009091,12.9231818 12.0477273,13.5613636 L12.0477273,15.8195455 L14.9563636,15.8195455 C16.6581818,14.2527273 17.64,11.9454545 17.64,9.20454545 L17.64,9.20454545 Z"
|
||||||
id="Shape"
|
id="Shape"
|
||||||
fill="#4285F4"
|
fill="#000"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M9,18 C11.43,18 13.4672727,17.1940909 14.9563636,15.8195455 L12.0477273,13.5613636 C11.2418182,14.1013636 10.2109091,14.4204545 9,14.4204545 C6.65590909,14.4204545 4.67181818,12.8372727 3.96409091,10.71 L0.957272727,10.71 L0.957272727,13.0418182 C2.43818182,15.9831818 5.48181818,18 9,18 L9,18 Z"
|
d="M9,18 C11.43,18 13.4672727,17.1940909 14.9563636,15.8195455 L12.0477273,13.5613636 C11.2418182,14.1013636 10.2109091,14.4204545 9,14.4204545 C6.65590909,14.4204545 4.67181818,12.8372727 3.96409091,10.71 L0.957272727,10.71 L0.957272727,13.0418182 C2.43818182,15.9831818 5.48181818,18 9,18 L9,18 Z"
|
||||||
id="Shape"
|
id="Shape"
|
||||||
fill="#34A853"
|
fill="#000"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M3.96409091,10.71 C3.78409091,10.17 3.68181818,9.59318182 3.68181818,9 C3.68181818,8.40681818 3.78409091,7.83 3.96409091,7.29 L3.96409091,4.95818182 L0.957272727,4.95818182 C0.347727273,6.17318182 0,7.54772727 0,9 C0,10.4522727 0.347727273,11.8268182 0.957272727,13.0418182 L3.96409091,10.71 L3.96409091,10.71 Z"
|
d="M3.96409091,10.71 C3.78409091,10.17 3.68181818,9.59318182 3.68181818,9 C3.68181818,8.40681818 3.78409091,7.83 3.96409091,7.29 L3.96409091,4.95818182 L0.957272727,4.95818182 C0.347727273,6.17318182 0,7.54772727 0,9 C0,10.4522727 0.347727273,11.8268182 0.957272727,13.0418182 L3.96409091,10.71 L3.96409091,10.71 Z"
|
||||||
id="Shape"
|
id="Shape"
|
||||||
fill="#FBBC05"
|
fill="#000"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M9,3.57954545 C10.3213636,3.57954545 11.5077273,4.03363636 12.4404545,4.92545455 L15.0218182,2.34409091 C13.4631818,0.891818182 11.4259091,0 9,0 C5.48181818,0 2.43818182,2.01681818 0.957272727,4.95818182 L3.96409091,7.29 C4.67181818,5.16272727 6.65590909,3.57954545 9,3.57954545 L9,3.57954545 Z"
|
d="M9,3.57954545 C10.3213636,3.57954545 11.5077273,4.03363636 12.4404545,4.92545455 L15.0218182,2.34409091 C13.4631818,0.891818182 11.4259091,0 9,0 C5.48181818,0 2.43818182,2.01681818 0.957272727,4.95818182 L3.96409091,7.29 C4.67181818,5.16272727 6.65590909,3.57954545 9,3.57954545 L9,3.57954545 Z"
|
||||||
id="Shape"
|
id="Shape"
|
||||||
fill="#EA4335"
|
fill="#000"
|
||||||
/>
|
/>
|
||||||
<path d="M0,0 L18,0 L18,18 L0,18 L0,0 Z" id="Shape" />
|
<path d="M0,0 L18,0 L18,18 L0,18 L0,0 Z" id="Shape" />
|
||||||
</g>
|
</g>
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
7
frontend/src/assets/img/logo.svg
Normal file
7
frontend/src/assets/img/logo.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="26" cy="26" r="26" fill="white"/>
|
||||||
|
<rect x="15" y="15" width="7" height="22" fill="#1A4049"/>
|
||||||
|
<rect x="30" y="15" width="7" height="22" fill="#1A4049"/>
|
||||||
|
<path d="M37 30L37 37L15 37L15 30L37 30Z" fill="#1A4049"/>
|
||||||
|
<rect x="30" y="30" width="7" height="7" fill="#817AFE"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 384 B |
@ -0,0 +1,21 @@
|
|||||||
|
import { makeStyles } from '@material-ui/styles';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: '1rem auto',
|
||||||
|
},
|
||||||
|
wing: {
|
||||||
|
width: '80px',
|
||||||
|
height: '3px',
|
||||||
|
backgroundColor: theme.palette.division.main,
|
||||||
|
borderRadius: theme.borders.radius.main,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
textAlign: 'center',
|
||||||
|
display: 'block',
|
||||||
|
margin: '0 1rem',
|
||||||
|
},
|
||||||
|
}));
|
22
frontend/src/component/common/DividerText/DividerText.tsx
Normal file
22
frontend/src/component/common/DividerText/DividerText.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Typography } from '@material-ui/core';
|
||||||
|
import { useStyles } from './DividerText.styles';
|
||||||
|
|
||||||
|
interface IDividerTextProps {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DividerText = ({ text, ...rest }: IDividerTextProps) => {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container} {...rest}>
|
||||||
|
<span className={styles.wing} />
|
||||||
|
<Typography variant="body2" className={styles.text}>
|
||||||
|
{text}
|
||||||
|
</Typography>
|
||||||
|
<span className={styles.wing} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DividerText;
|
@ -1,12 +1,14 @@
|
|||||||
interface IGradientProps {
|
interface IGradientProps {
|
||||||
from: string;
|
from: string;
|
||||||
to: string;
|
to: string;
|
||||||
|
style?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Gradient: React.FC<IGradientProps> = ({
|
const Gradient: React.FC<IGradientProps> = ({
|
||||||
children,
|
children,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
|
style,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
@ -15,6 +17,8 @@ const Gradient: React.FC<IGradientProps> = ({
|
|||||||
background: `linear-gradient(${from}, ${to})`,
|
background: `linear-gradient(${from}, ${to})`,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
...style,
|
||||||
}}
|
}}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
|
@ -4,7 +4,7 @@ import { Button, TextField } from '@material-ui/core';
|
|||||||
|
|
||||||
import styles from './DemoAuth.module.scss';
|
import styles from './DemoAuth.module.scss';
|
||||||
|
|
||||||
import logoIcon from '../../../assets/img/logo.png';
|
import { ReactComponent as Logo } from '../../../assets/img/logo.svg';
|
||||||
|
|
||||||
const DemoAuth = ({ demoLogin, history, authDetails }) => {
|
const DemoAuth = ({ demoLogin, history, authDetails }) => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
@ -24,13 +24,14 @@ const DemoAuth = ({ demoLogin, history, authDetails }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} action={authDetails.path}>
|
<form onSubmit={handleSubmit} action={authDetails.path}>
|
||||||
|
<Logo className={styles.logo} />
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<img alt="Unleash Logo" src={logoIcon} width="70" height="70" />
|
|
||||||
<h2>Access the Unleash demo instance</h2>
|
<h2>Access the Unleash demo instance</h2>
|
||||||
<p>No further data or Credit Card required</p>
|
<p>No further data or Credit Card required</p>
|
||||||
<div className={styles.form}>
|
<div className={styles.form}>
|
||||||
<TextField
|
<TextField
|
||||||
value={email}
|
value={email}
|
||||||
|
className={styles.emailField}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
inputProps={{ 'data-test': 'email-input-field' }}
|
inputProps={{ 'data-test': 'email-input-field' }}
|
||||||
size="small"
|
size="small"
|
||||||
@ -40,7 +41,7 @@ const DemoAuth = ({ demoLogin, history, authDetails }) => {
|
|||||||
required
|
required
|
||||||
type="email"
|
type="email"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
@ -1,11 +1,34 @@
|
|||||||
|
.container {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
.container > * {
|
.container > * {
|
||||||
margin: 0.6rem 0;
|
margin: 0.6rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.emailField {
|
||||||
min-width: 150px;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
.form > * {
|
||||||
margin: 4em 1em
|
margin: 0.6rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
height: 100px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
min-width: 150px;
|
||||||
|
margin: 1rem auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,21 @@
|
|||||||
import { makeStyles } from '@material-ui/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
export const useStyles = makeStyles({
|
export const useStyles = makeStyles(theme => ({
|
||||||
container: {
|
forgottenPassword: {
|
||||||
maxWidth: '300px',
|
width: '350px',
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
width: '150px',
|
width: '150px',
|
||||||
|
margin: '1rem auto',
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
margin: '0.5rem 0',
|
margin: '0.5rem 0',
|
||||||
},
|
},
|
||||||
});
|
loginText: {
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
@ -2,10 +2,12 @@ import { Button, TextField, Typography } from '@material-ui/core';
|
|||||||
import { AlertTitle, Alert } from '@material-ui/lab';
|
import { AlertTitle, Alert } from '@material-ui/lab';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { SyntheticEvent, useState } from 'react';
|
import { SyntheticEvent, useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { useCommonStyles } from '../../../common.styles';
|
import { useCommonStyles } from '../../../common.styles';
|
||||||
import useLoading from '../../../hooks/useLoading';
|
import useLoading from '../../../hooks/useLoading';
|
||||||
import { formatApiPath } from '../../../utils/format-path';
|
import { formatApiPath } from '../../../utils/format-path';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
|
import DividerText from '../../common/DividerText/DividerText';
|
||||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
||||||
import { useStyles } from './ForgottenPassword.styles';
|
import { useStyles } from './ForgottenPassword.styles';
|
||||||
|
|
||||||
@ -41,7 +43,8 @@ const ForgottenPassword = () => {
|
|||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
commonStyles.contentSpacingY,
|
commonStyles.contentSpacingY,
|
||||||
commonStyles.flexColumn
|
commonStyles.flexColumn,
|
||||||
|
styles.forgottenPassword
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
@ -105,6 +108,18 @@ const ForgottenPassword = () => {
|
|||||||
elseShow={<span>Try again</span>}
|
elseShow={<span>Try again</span>}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
<DividerText text="Or log in" />
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
data-loading
|
||||||
|
variant="outlined"
|
||||||
|
className={styles.button}
|
||||||
|
disabled={loading}
|
||||||
|
component={Link}
|
||||||
|
to="/login"
|
||||||
|
>
|
||||||
|
Log in
|
||||||
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</StandaloneLayout>
|
</StandaloneLayout>
|
||||||
|
@ -5,11 +5,12 @@ import { Button, Grid, TextField, Typography } from '@material-ui/core';
|
|||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { useCommonStyles } from '../../../common.styles';
|
import { useCommonStyles } from '../../../common.styles';
|
||||||
import { useStyles } from './HostedAuth.styles';
|
import { useStyles } from './HostedAuth.styles';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import useQueryParams from '../../../hooks/useQueryParams';
|
import useQueryParams from '../../../hooks/useQueryParams';
|
||||||
import AuthOptions from '../common/AuthOptions/AuthOptions';
|
import AuthOptions from '../common/AuthOptions/AuthOptions';
|
||||||
|
import DividerText from '../../common/DividerText/DividerText';
|
||||||
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
|
|
||||||
const PasswordAuth = ({ authDetails, passwordLogin }) => {
|
const HostedAuth = ({ authDetails, passwordLogin }) => {
|
||||||
const commonStyles = useCommonStyles();
|
const commonStyles = useCommonStyles();
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -67,12 +68,17 @@ const PasswordAuth = ({ authDetails, passwordLogin }) => {
|
|||||||
const { options = [] } = authDetails;
|
const { options = [] } = authDetails;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<br />
|
<ConditionallyRender
|
||||||
<div>
|
condition={options.length > 0}
|
||||||
<AuthOptions options={options} />
|
show={
|
||||||
</div>
|
<>
|
||||||
<p className={styles.fancyLine}>or</p>
|
<AuthOptions options={options} />
|
||||||
|
<DividerText text="or signin with username" />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} action={authDetails.path}>
|
<form onSubmit={handleSubmit} action={authDetails.path}>
|
||||||
<Typography variant="subtitle2" className={styles.apiError}>
|
<Typography variant="subtitle2" className={styles.apiError}>
|
||||||
{apiError}
|
{apiError}
|
||||||
@ -105,43 +111,26 @@ const PasswordAuth = ({ authDetails, passwordLogin }) => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
<Grid container spacing={3}>
|
<Grid container>
|
||||||
<Grid item xs={6}>
|
<Button
|
||||||
<Button
|
variant="contained"
|
||||||
variant="contained"
|
color="primary"
|
||||||
color="primary"
|
type="submit"
|
||||||
type="submit"
|
className={styles.button}
|
||||||
style={{ maxWidth: '150px' }}
|
>
|
||||||
>
|
Sign in
|
||||||
Sign in
|
</Button>
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<Link to="/forgotten-password">
|
|
||||||
<Typography variant="body2" align="right">
|
|
||||||
Forgot your password?
|
|
||||||
</Typography>
|
|
||||||
</Link>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<Typography variant="body2" align="center">
|
|
||||||
Don't have an account? <br />{' '}
|
|
||||||
<a href="https://www.unleash-hosted.com/pricing">
|
|
||||||
Sign up
|
|
||||||
</a>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PasswordAuth.propTypes = {
|
HostedAuth.propTypes = {
|
||||||
authDetails: PropTypes.object.isRequired,
|
authDetails: PropTypes.object.isRequired,
|
||||||
passwordLogin: PropTypes.func.isRequired,
|
passwordLogin: PropTypes.func.isRequired,
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PasswordAuth;
|
export default HostedAuth;
|
||||||
|
@ -15,26 +15,10 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
apiError: {
|
apiError: {
|
||||||
color: theme.palette.error.main,
|
color: theme.palette.error.main,
|
||||||
},
|
},
|
||||||
|
button: {
|
||||||
fancyLine: {
|
width: '150px',
|
||||||
display: 'flex',
|
margin: '1rem auto 0 auto',
|
||||||
width: '100%',
|
display: 'block',
|
||||||
margin: '10px 0',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: 'gray',
|
},
|
||||||
'&::before': {
|
|
||||||
content: '""',
|
|
||||||
borderTop: '1px solid silver',
|
|
||||||
margin: '0 20px 0 0',
|
|
||||||
flex: '1 0 20px',
|
|
||||||
},
|
|
||||||
'&::after': {
|
|
||||||
content: '""',
|
|
||||||
borderTop: '1px solid silver',
|
|
||||||
margin: '0 20px 0 0',
|
|
||||||
flex: '1 0 20px',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
|
@ -7,6 +7,7 @@ import { useStyles } from './Login.styles';
|
|||||||
import useQueryParams from '../../../hooks/useQueryParams';
|
import useQueryParams from '../../../hooks/useQueryParams';
|
||||||
import ResetPasswordSuccess from '../common/ResetPasswordSuccess/ResetPasswordSuccess';
|
import ResetPasswordSuccess from '../common/ResetPasswordSuccess/ResetPasswordSuccess';
|
||||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
||||||
|
import { DEMO_TYPE } from '../../../constants/authTypes';
|
||||||
|
|
||||||
const Login = ({ history, user, fetchUser }) => {
|
const Login = ({ history, user, fetchUser }) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
@ -28,15 +29,21 @@ const Login = ({ history, user, fetchUser }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StandaloneLayout>
|
<StandaloneLayout>
|
||||||
<div>
|
<div className={styles.loginFormContainer}>
|
||||||
<h2 className={styles.title}>Login</h2>
|
<ConditionallyRender
|
||||||
|
condition={user?.authDetails?.type !== DEMO_TYPE}
|
||||||
|
show={
|
||||||
|
<h2 className={styles.title}>
|
||||||
|
Login to continue the great work
|
||||||
|
</h2>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={resetPassword}
|
condition={resetPassword}
|
||||||
show={<ResetPasswordSuccess />}
|
show={<ResetPasswordSuccess />}
|
||||||
/>
|
/>
|
||||||
<div className={styles.loginFormContainer}>
|
<AuthenticationContainer history={history} />
|
||||||
<AuthenticationContainer history={history} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</StandaloneLayout>
|
</StandaloneLayout>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import { makeStyles } from '@material-ui/styles';
|
import { makeStyles } from '@material-ui/styles';
|
||||||
|
|
||||||
export const useStyles = makeStyles(theme => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
login: {
|
||||||
|
width: '350px',
|
||||||
|
maxWidth: '350px',
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
loginContainer: {
|
loginContainer: {
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -25,7 +32,7 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: theme.fontSizes.mainHeader,
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
marginBottom: '0.5rem',
|
marginBottom: '1rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
@ -38,7 +45,8 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
fontSize: '1.25rem',
|
fontSize: '1.25rem',
|
||||||
},
|
},
|
||||||
loginFormContainer: {
|
loginFormContainer: {
|
||||||
maxWidth: '500px',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
},
|
},
|
||||||
imageContainer: {
|
imageContainer: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -1,6 +1,22 @@
|
|||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
export const useStyles = makeStyles(theme => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
newUser: {
|
||||||
|
width: '350px',
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
marginBottom: '1.25rem',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
inviteText: {
|
||||||
|
marginBottom: '1rem',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
container: {
|
container: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
},
|
},
|
||||||
@ -9,7 +25,6 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
},
|
},
|
||||||
innerContainer: {
|
innerContainer: {
|
||||||
width: '60%',
|
width: '60%',
|
||||||
minHeight: '100vh',
|
|
||||||
padding: '4rem 3rem',
|
padding: '4rem 3rem',
|
||||||
},
|
},
|
||||||
buttonContainer: {
|
buttonContainer: {
|
||||||
@ -20,14 +35,14 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
marginRight: '8px',
|
marginRight: '8px',
|
||||||
},
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
marginBottom: '0.5rem',
|
margin: '0.5rem 0',
|
||||||
fontSize: '1.1rem',
|
|
||||||
},
|
},
|
||||||
passwordHeader: {
|
passwordHeader: {
|
||||||
marginTop: '2rem',
|
marginTop: '2rem',
|
||||||
},
|
},
|
||||||
emailField: {
|
emailField: {
|
||||||
minWidth: '300px',
|
minWidth: '300px',
|
||||||
|
width: '100%',
|
||||||
[theme.breakpoints.down('xs')]: {
|
[theme.breakpoints.down('xs')]: {
|
||||||
minWidth: '100%',
|
minWidth: '100%',
|
||||||
},
|
},
|
||||||
|
@ -5,13 +5,13 @@ import StandaloneBanner from '../StandaloneBanner/StandaloneBanner';
|
|||||||
import ResetPasswordDetails from '../common/ResetPasswordDetails/ResetPasswordDetails';
|
import ResetPasswordDetails from '../common/ResetPasswordDetails/ResetPasswordDetails';
|
||||||
|
|
||||||
import { useStyles } from './NewUser.styles';
|
import { useStyles } from './NewUser.styles';
|
||||||
import { useCommonStyles } from '../../../common.styles';
|
|
||||||
import useResetPassword from '../../../hooks/useResetPassword';
|
import useResetPassword from '../../../hooks/useResetPassword';
|
||||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
import InvalidToken from '../common/InvalidToken/InvalidToken';
|
import InvalidToken from '../common/InvalidToken/InvalidToken';
|
||||||
import { IAuthStatus } from '../../../interfaces/user';
|
import { IAuthStatus } from '../../../interfaces/user';
|
||||||
import AuthOptions from '../common/AuthOptions/AuthOptions';
|
import AuthOptions from '../common/AuthOptions/AuthOptions';
|
||||||
|
import DividerText from '../../common/DividerText/DividerText';
|
||||||
|
|
||||||
interface INewUserProps {
|
interface INewUserProps {
|
||||||
user: IAuthStatus;
|
user: IAuthStatus;
|
||||||
@ -21,109 +21,97 @@ const NewUser = ({ user }: INewUserProps) => {
|
|||||||
const { token, data, loading, setLoading, invalidToken } =
|
const { token, data, loading, setLoading, invalidToken } =
|
||||||
useResetPassword();
|
useResetPassword();
|
||||||
const ref = useLoading(loading);
|
const ref = useLoading(loading);
|
||||||
const commonStyles = useCommonStyles();
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
<StandaloneLayout
|
<StandaloneLayout
|
||||||
showMenu={false}
|
showMenu={false}
|
||||||
BannerComponent={
|
BannerComponent={<StandaloneBanner title={'Unleash'} />}
|
||||||
<StandaloneBanner showStars title={'Welcome to Unleash'}>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={data?.createdBy}
|
|
||||||
show={
|
|
||||||
<Typography variant="body1">
|
|
||||||
You have been invited by {data?.createdBy}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</StandaloneBanner>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<ConditionallyRender
|
<div className={styles.newUser}>
|
||||||
condition={invalidToken}
|
<ConditionallyRender
|
||||||
show={<InvalidToken />}
|
condition={invalidToken}
|
||||||
elseShow={
|
show={<InvalidToken />}
|
||||||
<ResetPasswordDetails
|
elseShow={
|
||||||
token={token}
|
<ResetPasswordDetails
|
||||||
setLoading={setLoading}
|
token={token}
|
||||||
>
|
setLoading={setLoading}
|
||||||
<Typography
|
|
||||||
data-loading
|
|
||||||
variant="subtitle1"
|
|
||||||
className={styles.subtitle}
|
|
||||||
>
|
>
|
||||||
Your username is
|
<h2 className={styles.title}>
|
||||||
</Typography>
|
Enter your personal details and start your
|
||||||
<TextField
|
journey
|
||||||
data-loading
|
</h2>
|
||||||
value={data?.email || ''}
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
className={styles.emailField}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
<div className={styles.roleContainer}>
|
|
||||||
<Typography
|
|
||||||
data-loading
|
|
||||||
variant="subtitle1"
|
|
||||||
className={styles.subtitle}
|
|
||||||
>
|
|
||||||
In Unleash your role is:{' '}
|
|
||||||
<i>{data?.role?.name}</i>
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body1" data-loading>
|
|
||||||
{data?.role?.description}
|
|
||||||
</Typography>
|
|
||||||
<div
|
|
||||||
className={commonStyles.largeDivider}
|
|
||||||
data-loading
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={
|
condition={data?.createdBy}
|
||||||
user?.authDetails?.options?.length > 0
|
|
||||||
}
|
|
||||||
show={
|
show={
|
||||||
<>
|
|
||||||
<Typography data-loading>
|
|
||||||
Login with 3rd party providers
|
|
||||||
</Typography>
|
|
||||||
<AuthOptions
|
|
||||||
options={
|
|
||||||
user?.authDetails?.options
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
commonStyles.largeDivider
|
|
||||||
}
|
|
||||||
data-loading
|
|
||||||
/>
|
|
||||||
<Typography
|
|
||||||
className={
|
|
||||||
styles.passwordHeader
|
|
||||||
}
|
|
||||||
data-loading
|
|
||||||
>
|
|
||||||
OR set a new password for your
|
|
||||||
account
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
elseShow={
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
data-loading
|
data-loading
|
||||||
|
className={styles.inviteText}
|
||||||
>
|
>
|
||||||
Set a password for your account.
|
{data?.createdBy}
|
||||||
|
<br></br> has invited you to join
|
||||||
|
Unleash.
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</ResetPasswordDetails>
|
<Typography
|
||||||
}
|
data-loading
|
||||||
/>
|
variant="body1"
|
||||||
|
className={styles.subtitle}
|
||||||
|
>
|
||||||
|
Your username is
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
data-loading
|
||||||
|
value={data?.email || ''}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
className={styles.emailField}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<div className={styles.roleContainer}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
user?.authDetails?.options?.length >
|
||||||
|
0
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<DividerText
|
||||||
|
text="sign in with"
|
||||||
|
data-loading
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AuthOptions
|
||||||
|
options={
|
||||||
|
user?.authDetails
|
||||||
|
?.options
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<DividerText
|
||||||
|
text="or set a new password for your account"
|
||||||
|
data-loading
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
data-loading
|
||||||
|
>
|
||||||
|
Set a password for your account.
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ResetPasswordDetails>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</StandaloneLayout>
|
</StandaloneLayout>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
import React, { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, TextField, Typography, IconButton } from '@material-ui/core';
|
import { Button, TextField, Typography } from '@material-ui/core';
|
||||||
import LockRounded from '@material-ui/icons/LockRounded';
|
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { useCommonStyles } from '../../../common.styles';
|
import { useCommonStyles } from '../../../common.styles';
|
||||||
import { useStyles } from './PasswordAuth.styles';
|
import { useStyles } from './PasswordAuth.styles';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import useQueryParams from '../../../hooks/useQueryParams';
|
import useQueryParams from '../../../hooks/useQueryParams';
|
||||||
import { GoogleSvg } from '../HostedAuth/Icons';
|
import AuthOptions from '../common/AuthOptions/AuthOptions';
|
||||||
|
import DividerText from '../../common/DividerText/DividerText';
|
||||||
|
|
||||||
const PasswordAuth = ({ authDetails, passwordLogin }) => {
|
const PasswordAuth = ({ authDetails, passwordLogin }) => {
|
||||||
const commonStyles = useCommonStyles();
|
const commonStyles = useCommonStyles();
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [showFields, setShowFields] = useState(false);
|
|
||||||
const params = useQueryParams();
|
const params = useQueryParams();
|
||||||
const [username, setUsername] = useState(params.get('email') || '');
|
const [username, setUsername] = useState(params.get('email') || '');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
@ -24,11 +22,6 @@ const PasswordAuth = ({ authDetails, passwordLogin }) => {
|
|||||||
passwordError: '',
|
passwordError: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const onShowOptions = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
setShowFields(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async evt => {
|
const handleSubmit = async evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|
||||||
@ -110,17 +103,11 @@ const PasswordAuth = ({ authDetails, passwordLogin }) => {
|
|||||||
autoComplete="true"
|
autoComplete="true"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Link to="/forgotten-password">
|
|
||||||
<Typography variant="body2">
|
|
||||||
Forgot your password?
|
|
||||||
</Typography>
|
|
||||||
</Link>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
style={{ maxWidth: '150px' }}
|
style={{ width: '150px', margin: '1rem auto' }}
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
@ -130,44 +117,23 @@ const PasswordAuth = ({ authDetails, passwordLogin }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderWithOptions = options => (
|
const renderWithOptions = options => (
|
||||||
<div>
|
<>
|
||||||
{options.map(o => (
|
<AuthOptions options={options} />
|
||||||
<div
|
<DividerText text="Or signin with username" />
|
||||||
key={o.type}
|
{renderLoginForm()}
|
||||||
className={classnames(
|
</>
|
||||||
styles.contentContainer,
|
|
||||||
commonStyles.contentSpacingY
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Button color="primary" variant="outlined" href={o.path} startIcon={o.type === 'google' ? <GoogleSvg /> : <LockRounded />}>
|
|
||||||
{o.message}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={showFields}
|
|
||||||
show={renderLoginForm()}
|
|
||||||
elseShow={
|
|
||||||
|
|
||||||
<IconButton size="small" onClick={onShowOptions}>
|
|
||||||
Show more options
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { options = [] } = authDetails;
|
const { options = [] } = authDetails;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Typography variant="subtitle1">{authDetails.message}</Typography>
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={options.length > 0}
|
condition={options.length > 0}
|
||||||
show={renderWithOptions(options)}
|
show={renderWithOptions(options)}
|
||||||
elseShow={renderLoginForm()}
|
elseShow={renderLoginForm()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,20 @@
|
|||||||
|
.container {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
.container > * {
|
.container > * {
|
||||||
margin: 0.6rem 0;
|
margin: 0.6rem 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,22 @@ import { makeStyles } from '@material-ui/core/styles';
|
|||||||
export const useStyles = makeStyles(theme => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
title: {
|
title: {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontSize: '1.2rem',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginBottom: '1rem',
|
marginBottom: '1rem',
|
||||||
|
fontSize: '2rem',
|
||||||
|
marginTop: '5rem',
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
textAlign: 'left',
|
||||||
|
fontSize: '1.75rem',
|
||||||
|
marginTop: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
padding: '4rem 2rem',
|
padding: '6rem 4rem',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
borderTopLeftRadius: '3px',
|
||||||
|
borderBottomLeftRadius: '3px',
|
||||||
|
textAlign: 'right',
|
||||||
[theme.breakpoints.down('sm')]: {
|
[theme.breakpoints.down('sm')]: {
|
||||||
padding: '3rem 2rem',
|
padding: '3rem 2rem',
|
||||||
},
|
},
|
||||||
@ -18,9 +26,22 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
padding: '3rem 1rem',
|
padding: '3rem 1rem',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
bannerSubtitle: {
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
maxWidth: '300px',
|
||||||
|
fontSize: '1.75rem',
|
||||||
|
textAlign: 'left',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
fontSize: '2rem',
|
||||||
|
fontWeight: '300',
|
||||||
|
},
|
||||||
switchesContainer: {
|
switchesContainer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: '40px',
|
bottom: '15px',
|
||||||
|
left: '-50px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
[theme.breakpoints.down('sm')]: {
|
[theme.breakpoints.down('sm')]: {
|
||||||
@ -28,46 +49,6 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
switchIcon: {
|
switchIcon: {
|
||||||
height: '180px',
|
height: '100px',
|
||||||
},
|
|
||||||
bottomStar: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '-54px',
|
|
||||||
left: '100px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bottomRightStar: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '-100px',
|
|
||||||
left: '200px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
midRightStar: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '-80px',
|
|
||||||
left: '300px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
midLeftStar: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: '10px',
|
|
||||||
left: '150px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
midLeftStarTwo: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: '25px',
|
|
||||||
left: '350px',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -2,45 +2,35 @@ import { FC } from 'react';
|
|||||||
|
|
||||||
import { Typography, useTheme } from '@material-ui/core';
|
import { Typography, useTheme } from '@material-ui/core';
|
||||||
import Gradient from '../../common/Gradient/Gradient';
|
import Gradient from '../../common/Gradient/Gradient';
|
||||||
import { ReactComponent as StarIcon } from '../../../assets/icons/star.svg';
|
|
||||||
import { ReactComponent as RightToggleIcon } from '../../../assets/icons/toggleRight.svg';
|
import { ReactComponent as RightToggleIcon } from '../../../assets/icons/toggleRight.svg';
|
||||||
import { ReactComponent as LeftToggleIcon } from '../../../assets/icons/toggleLeft.svg';
|
import { ReactComponent as LeftToggleIcon } from '../../../assets/icons/toggleLeft.svg';
|
||||||
|
|
||||||
import { useStyles } from './StandaloneBanner.styles';
|
import { useStyles } from './StandaloneBanner.styles';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender';
|
|
||||||
|
|
||||||
interface IStandaloneBannerProps {
|
interface IStandaloneBannerProps {
|
||||||
showStars?: boolean;
|
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StandaloneBanner: FC<IStandaloneBannerProps> = ({
|
const StandaloneBanner: FC<IStandaloneBannerProps> = ({ title, children }) => {
|
||||||
showStars = false,
|
|
||||||
title,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
return (
|
return (
|
||||||
<Gradient from={theme.palette.primary.main} to={'#173341'}>
|
<Gradient
|
||||||
|
from={theme.palette.primary.main}
|
||||||
|
to={theme.palette.login.gradient.bottom}
|
||||||
|
style={{
|
||||||
|
borderBottomLeftRadius: '3px',
|
||||||
|
borderTopLeftRadius: '3px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Typography variant="h1" className={styles.title}>
|
<Typography variant="h1" className={styles.title}>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
{children}
|
<Typography className={styles.bannerSubtitle}>
|
||||||
|
Committed to creating new ways of developing software
|
||||||
<ConditionallyRender
|
</Typography>
|
||||||
condition={showStars}
|
|
||||||
show={
|
|
||||||
<>
|
|
||||||
<StarIcon className={styles.midLeftStarTwo} />
|
|
||||||
<StarIcon className={styles.midLeftStar} />
|
|
||||||
<StarIcon className={styles.midRightStar} />
|
|
||||||
<StarIcon className={styles.bottomRightStar} />
|
|
||||||
<StarIcon className={styles.bottomStar} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.switchesContainer}>
|
<div className={styles.switchesContainer}>
|
||||||
|
@ -6,10 +6,13 @@ import PasswordAuth from './PasswordAuth/PasswordAuth';
|
|||||||
import HostedAuth from './HostedAuth/HostedAuth';
|
import HostedAuth from './HostedAuth/HostedAuth';
|
||||||
import DemoAuth from './DemoAuth';
|
import DemoAuth from './DemoAuth';
|
||||||
|
|
||||||
const SIMPLE_TYPE = 'unsecure';
|
import {
|
||||||
const DEMO_TYPE = 'demo';
|
SIMPLE_TYPE,
|
||||||
const PASSWORD_TYPE = 'password';
|
DEMO_TYPE,
|
||||||
const HOSTED_TYPE = 'enterprise-hosted';
|
PASSWORD_TYPE,
|
||||||
|
HOSTED_TYPE,
|
||||||
|
} from '../../constants/authTypes';
|
||||||
|
import SecondaryLoginActions from './common/SecondaryLoginActions/SecondaryLoginActions';
|
||||||
|
|
||||||
class AuthComponent extends React.Component {
|
class AuthComponent extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -22,16 +25,20 @@ class AuthComponent extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const authDetails = this.props.user.authDetails;
|
const authDetails = this.props.user.authDetails;
|
||||||
|
|
||||||
if (!authDetails) return null;
|
if (!authDetails) return null;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (authDetails.type === PASSWORD_TYPE) {
|
if (authDetails.type === PASSWORD_TYPE) {
|
||||||
content = (
|
content = (
|
||||||
<PasswordAuth
|
<>
|
||||||
passwordLogin={this.props.passwordLogin}
|
<PasswordAuth
|
||||||
authDetails={authDetails}
|
passwordLogin={this.props.passwordLogin}
|
||||||
history={this.props.history}
|
authDetails={authDetails}
|
||||||
/>
|
history={this.props.history}
|
||||||
|
/>
|
||||||
|
<SecondaryLoginActions />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
} else if (authDetails.type === SIMPLE_TYPE) {
|
} else if (authDetails.type === SIMPLE_TYPE) {
|
||||||
content = (
|
content = (
|
||||||
@ -51,18 +58,21 @@ class AuthComponent extends React.Component {
|
|||||||
);
|
);
|
||||||
} else if (authDetails.type === HOSTED_TYPE) {
|
} else if (authDetails.type === HOSTED_TYPE) {
|
||||||
content = (
|
content = (
|
||||||
<HostedAuth
|
<>
|
||||||
passwordLogin={this.props.passwordLogin}
|
<HostedAuth
|
||||||
authDetails={authDetails}
|
passwordLogin={this.props.passwordLogin}
|
||||||
history={this.props.history}
|
authDetails={authDetails}
|
||||||
/>
|
history={this.props.history}
|
||||||
|
/>
|
||||||
|
<SecondaryLoginActions />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<AuthenticationCustomComponent authDetails={authDetails} />
|
<AuthenticationCustomComponent authDetails={authDetails} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <div>{content}</div>;
|
return <>{content}</>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,14 @@ class AuthenticationCustomComponent extends React.Component {
|
|||||||
<div>
|
<div>
|
||||||
<p>{authDetails.message}</p>
|
<p>{authDetails.message}</p>
|
||||||
<CardActions style={{ textAlign: 'center' }}>
|
<CardActions style={{ textAlign: 'center' }}>
|
||||||
<a href={authDetails.path}>
|
<a href={authDetails.path} style={{ width: '100%' }}>
|
||||||
<Button>Sign In</Button>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
style={{ width: '150px', margin: '0 auto' }}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,6 +4,7 @@ import { useCommonStyles } from '../../../../common.styles';
|
|||||||
import { IAuthOptions } from '../../../../interfaces/user';
|
import { IAuthOptions } from '../../../../interfaces/user';
|
||||||
import { ReactComponent as GoogleSvg } from '../../../../assets/icons/google.svg';
|
import { ReactComponent as GoogleSvg } from '../../../../assets/icons/google.svg';
|
||||||
import LockRounded from '@material-ui/icons/LockRounded';
|
import LockRounded from '@material-ui/icons/LockRounded';
|
||||||
|
import ConditionallyRender from '../../../common/ConditionallyRender';
|
||||||
|
|
||||||
interface IAuthOptionProps {
|
interface IAuthOptionProps {
|
||||||
options?: IAuthOptions[];
|
options?: IAuthOptions[];
|
||||||
@ -27,13 +28,27 @@ const AuthOptions = ({ options }: IAuthOptionProps) => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
href={o.path}
|
href={o.path}
|
||||||
size="small"
|
size="small"
|
||||||
style={{ maxWidth: '300px' }}
|
style={{ height: '40px', color: '#000' }}
|
||||||
startIcon={
|
startIcon={
|
||||||
o.type === 'google' ? (
|
<ConditionallyRender
|
||||||
<GoogleSvg />
|
condition={o.type === 'google'}
|
||||||
) : (
|
show={
|
||||||
<LockRounded />
|
<GoogleSvg
|
||||||
)
|
style={{
|
||||||
|
height: '35px',
|
||||||
|
width: '35px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<LockRounded
|
||||||
|
style={{
|
||||||
|
height: '25px',
|
||||||
|
width: '25px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{o.message}
|
{o.message}
|
||||||
|
@ -10,6 +10,8 @@ import { formatApiPath } from '../../../../../utils/format-path';
|
|||||||
interface IPasswordCheckerProps {
|
interface IPasswordCheckerProps {
|
||||||
password: string;
|
password: string;
|
||||||
callback: Dispatch<SetStateAction<boolean>>;
|
callback: Dispatch<SetStateAction<boolean>>;
|
||||||
|
style?: object;
|
||||||
|
hideOnCompletion?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IErrorResponse {
|
interface IErrorResponse {
|
||||||
@ -30,7 +32,11 @@ const UPPERCASE_ERROR =
|
|||||||
const LOWERCASE_ERROR =
|
const LOWERCASE_ERROR =
|
||||||
'The password must contain at least one lowercase letter.';
|
'The password must contain at least one lowercase letter.';
|
||||||
|
|
||||||
const PasswordChecker = ({ password, callback }: IPasswordCheckerProps) => {
|
const PasswordChecker = ({
|
||||||
|
password,
|
||||||
|
callback,
|
||||||
|
style = {},
|
||||||
|
}: IPasswordCheckerProps) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const [casingError, setCasingError] = useState(true);
|
const [casingError, setCasingError] = useState(true);
|
||||||
const [numberError, setNumberError] = useState(true);
|
const [numberError, setNumberError] = useState(true);
|
||||||
@ -142,7 +148,12 @@ const PasswordChecker = ({ password, callback }: IPasswordCheckerProps) => {
|
|||||||
<HelpIcon className={styles.helpIcon} />
|
<HelpIcon className={styles.helpIcon} />
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div className={styles.container}>
|
<div
|
||||||
|
className={styles.container}
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className={styles.headerContainer}>
|
<div className={styles.headerContainer}>
|
||||||
<div className={styles.checkContainer}>
|
<div className={styles.checkContainer}>
|
||||||
<Typography variant="body2" data-loading>
|
<Typography variant="body2" data-loading>
|
||||||
|
@ -4,9 +4,11 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
container: {
|
container: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
maxWidth: '300px',
|
position: 'relative',
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
width: '150px',
|
width: '150px',
|
||||||
|
margin: '1rem auto',
|
||||||
|
display: 'block',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -28,6 +28,7 @@ const ResetPasswordForm = ({ token, setLoading }: IResetPasswordProps) => {
|
|||||||
const commonStyles = useCommonStyles();
|
const commonStyles = useCommonStyles();
|
||||||
const [apiError, setApiError] = useState(false);
|
const [apiError, setApiError] = useState(false);
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
|
const [showPasswordChecker, setShowPasswordChecker] = useState(false);
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
const [matchingPasswords, setMatchingPasswords] = useState(false);
|
const [matchingPasswords, setMatchingPasswords] = useState(false);
|
||||||
const [validOwaspPassword, setValidOwaspPassword] = useState(false);
|
const [validOwaspPassword, setValidOwaspPassword] = useState(false);
|
||||||
@ -102,10 +103,6 @@ const ResetPasswordForm = ({ token, setLoading }: IResetPasswordProps) => {
|
|||||||
styles.container
|
styles.container
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<PasswordChecker
|
|
||||||
password={password}
|
|
||||||
callback={setValidOwaspPasswordMemo}
|
|
||||||
/>
|
|
||||||
<TextField
|
<TextField
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
@ -113,6 +110,7 @@ const ResetPasswordForm = ({ token, setLoading }: IResetPasswordProps) => {
|
|||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
value={password || ''}
|
value={password || ''}
|
||||||
onChange={e => setPassword(e.target.value)}
|
onChange={e => setPassword(e.target.value)}
|
||||||
|
onFocus={() => setShowPasswordChecker(true)}
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
data-loading
|
data-loading
|
||||||
/>
|
/>
|
||||||
@ -126,6 +124,17 @@ const ResetPasswordForm = ({ token, setLoading }: IResetPasswordProps) => {
|
|||||||
autoComplete="confirm-password"
|
autoComplete="confirm-password"
|
||||||
data-loading
|
data-loading
|
||||||
/>
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={showPasswordChecker}
|
||||||
|
show={
|
||||||
|
<PasswordChecker
|
||||||
|
password={password}
|
||||||
|
callback={setValidOwaspPasswordMemo}
|
||||||
|
style={{ marginBottom: '1rem' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<PasswordMatcher
|
<PasswordMatcher
|
||||||
started={started}
|
started={started}
|
||||||
matchingPasswords={matchingPasswords}
|
matchingPasswords={matchingPasswords}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
container: {
|
||||||
|
margin: 'auto auto 0 auto',
|
||||||
|
width: '200px',
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
marginTop: '1rem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
textDecoration: 'none',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
text: { fontWeight: 'bold', marginBottom: '0.5rem' },
|
||||||
|
}));
|
@ -0,0 +1,29 @@
|
|||||||
|
import { Typography } from '@material-ui/core';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useStyles } from './SecondaryLoginActions.styles';
|
||||||
|
|
||||||
|
const SecondaryLoginActions = () => {
|
||||||
|
const 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;
|
@ -2,28 +2,31 @@ import { makeStyles } from '@material-ui/core/styles';
|
|||||||
|
|
||||||
export const useStyles = makeStyles(theme => ({
|
export const useStyles = makeStyles(theme => ({
|
||||||
container: {
|
container: {
|
||||||
|
padding: '4rem',
|
||||||
|
background: '#3a5663',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
[theme.breakpoints.down('sm')]: {
|
[theme.breakpoints.down('sm')]: {
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
},
|
},
|
||||||
overflow: 'hidden',
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
padding: '0',
|
||||||
|
},
|
||||||
|
minHeight: '100vh',
|
||||||
},
|
},
|
||||||
leftContainer: {
|
leftContainer: {
|
||||||
width: '40%',
|
width: '40%',
|
||||||
minHeight: '100vh',
|
borderRadius: '3px',
|
||||||
[theme.breakpoints.down('sm')]: {
|
[theme.breakpoints.down('sm')]: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
minHeight: 'auto',
|
minHeight: 'auto',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
bannerSubtitle: {
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
maxWidth: '300px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rightContainer: {
|
rightContainer: {
|
||||||
width: '60%',
|
width: '60%',
|
||||||
minHeight: '100vh',
|
flex: '1',
|
||||||
|
borderTopRightRadius: '3px',
|
||||||
|
borderBottomRightRadius: '3px',
|
||||||
|
backgroundColor: '#fff',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
[theme.breakpoints.down('sm')]: {
|
[theme.breakpoints.down('sm')]: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -31,27 +34,16 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
minHeight: 'auto',
|
minHeight: 'auto',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
menu: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: '20px',
|
|
||||||
top: '20px',
|
|
||||||
'& a': {
|
|
||||||
textDecoration: 'none',
|
|
||||||
color: '#000',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
'& a': {
|
|
||||||
color: '#fff',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: {
|
title: {
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: '1.2rem',
|
fontSize: '1.2rem',
|
||||||
marginBottom: '1rem',
|
marginBottom: '1rem',
|
||||||
},
|
},
|
||||||
innerRightContainer: {
|
innerRightContainer: {
|
||||||
padding: '4rem 3rem',
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '100%',
|
||||||
|
padding: '6rem 3rem',
|
||||||
[theme.breakpoints.down('sm')]: {
|
[theme.breakpoints.down('sm')]: {
|
||||||
padding: '2rem 2rem',
|
padding: '2rem 2rem',
|
||||||
},
|
},
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import StandaloneBanner from '../../StandaloneBanner/StandaloneBanner';
|
import StandaloneBanner from '../../StandaloneBanner/StandaloneBanner';
|
||||||
|
|
||||||
import { Typography } from '@material-ui/core';
|
|
||||||
|
|
||||||
import { useStyles } from './StandaloneLayout.styles';
|
import { useStyles } from './StandaloneLayout.styles';
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender';
|
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
interface IStandaloneLayout {
|
interface IStandaloneLayout {
|
||||||
BannerComponent?: JSX.Element;
|
BannerComponent?: JSX.Element;
|
||||||
@ -14,39 +10,20 @@ interface IStandaloneLayout {
|
|||||||
|
|
||||||
const StandaloneLayout: FC<IStandaloneLayout> = ({
|
const StandaloneLayout: FC<IStandaloneLayout> = ({
|
||||||
children,
|
children,
|
||||||
showMenu = true,
|
|
||||||
BannerComponent,
|
BannerComponent,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
let banner = (
|
let banner = <StandaloneBanner title="Unleash" />;
|
||||||
<StandaloneBanner title="Unleash">
|
|
||||||
<Typography variant="subtitle1" className={styles.bannerSubtitle}>
|
|
||||||
Committed to creating new ways of developing software.
|
|
||||||
</Typography>
|
|
||||||
</StandaloneBanner>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (BannerComponent) {
|
if (BannerComponent) {
|
||||||
banner = BannerComponent;
|
banner = BannerComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoginpage = location.pathname.includes('login');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.leftContainer}>{banner}</div>
|
<div className={styles.leftContainer}>{banner}</div>
|
||||||
<div className={styles.rightContainer}>
|
<div className={styles.rightContainer}>
|
||||||
<ConditionallyRender
|
|
||||||
condition={showMenu && !isLoginpage}
|
|
||||||
show={
|
|
||||||
<div className={styles.menu}>
|
|
||||||
<Link to="/login">Login</Link>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.innerRightContainer}>{children}</div>
|
<div className={styles.innerRightContainer}>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
5
frontend/src/constants/authTypes.ts
Normal file
5
frontend/src/constants/authTypes.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/* AUTH TYPES */
|
||||||
|
export const SIMPLE_TYPE = 'unsecure';
|
||||||
|
export const DEMO_TYPE = 'demo';
|
||||||
|
export const PASSWORD_TYPE = 'password';
|
||||||
|
export const HOSTED_TYPE = 'enterprise-hosted';
|
13
frontend/src/interfaces/palette.d.ts
vendored
13
frontend/src/interfaces/palette.d.ts
vendored
@ -1,10 +1,19 @@
|
|||||||
import * as createPalette from '@material-ui/core/styles/createPalette';
|
|
||||||
|
|
||||||
declare module '@material-ui/core/styles/createPalette' {
|
declare module '@material-ui/core/styles/createPalette' {
|
||||||
interface PaletteOptions {
|
interface PaletteOptions {
|
||||||
borders?: PaletteColorOptions;
|
borders?: PaletteColorOptions;
|
||||||
|
login?: ILoginPaletteOptions;
|
||||||
}
|
}
|
||||||
interface Palette {
|
interface Palette {
|
||||||
borders?: PaletteColor;
|
borders?: PaletteColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ILoginPaletteOptions {
|
||||||
|
gradient?: IGradientPaletteOptions;
|
||||||
|
main?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGradientPaletteOptions {
|
||||||
|
top: string;
|
||||||
|
bottom: string;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user