mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Create Signup page for users from Invite link (#2052)
* refactor: user creation screen cleanup * feat: deprecation notice for google sso * fix: docs openid typo * user invite hook mock
This commit is contained in:
parent
51c7ea053e
commit
ce3db75133
@ -1,5 +1,6 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
@ -75,23 +76,26 @@ export const GoogleAuth = () => {
|
||||
|
||||
return (
|
||||
<PageContent>
|
||||
<Grid container style={{ marginBottom: '1rem' }}>
|
||||
<Grid item xs={12}>
|
||||
<Alert severity="info">
|
||||
Please read the{' '}
|
||||
<a
|
||||
href="https://www.unleash-hosted.com/docs/enterprise-authentication/google"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
documentation
|
||||
</a>{' '}
|
||||
to learn how to integrate with Google OAuth 2.0. <br />
|
||||
Callback URL:{' '}
|
||||
<code>{uiConfig.unleashUrl}/auth/google/callback</code>
|
||||
</Alert>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box>
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
This integration is deprecated and will be removed in next
|
||||
major version. Please use <strong>OpenID Connect</strong> to
|
||||
enable Google SSO.
|
||||
</Alert>
|
||||
<Alert severity="info" sx={{ mb: 3 }}>
|
||||
Read the{' '}
|
||||
<a
|
||||
href="https://www.unleash-hosted.com/docs/enterprise-authentication/google"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
documentation
|
||||
</a>{' '}
|
||||
to learn how to integrate with Google OAuth 2.0. <br />
|
||||
Callback URL:{' '}
|
||||
<code>{uiConfig.unleashUrl}/auth/google/callback</code>
|
||||
</Alert>
|
||||
</Box>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Grid container spacing={3} mb={2}>
|
||||
<Grid item xs={5}>
|
||||
|
@ -1,49 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
newUser: {
|
||||
width: '350px',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
marginBottom: '1.25rem',
|
||||
textAlign: 'center',
|
||||
},
|
||||
inviteText: {
|
||||
marginBottom: '1rem',
|
||||
textAlign: 'center',
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
},
|
||||
roleContainer: {
|
||||
marginTop: '2rem',
|
||||
},
|
||||
innerContainer: {
|
||||
width: '60%',
|
||||
padding: '4rem 3rem',
|
||||
},
|
||||
buttonContainer: {
|
||||
display: 'flex',
|
||||
marginTop: '1rem',
|
||||
},
|
||||
primaryBtn: {
|
||||
marginRight: '8px',
|
||||
},
|
||||
subtitle: {
|
||||
margin: '0.5rem 0',
|
||||
},
|
||||
passwordHeader: {
|
||||
marginTop: '2rem',
|
||||
},
|
||||
emailField: {
|
||||
minWidth: '300px',
|
||||
width: '100%',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
minWidth: '100%',
|
||||
},
|
||||
},
|
||||
}));
|
@ -1,113 +1,137 @@
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { TextField, Typography } from '@mui/material';
|
||||
import StandaloneBanner from '../StandaloneBanner/StandaloneBanner';
|
||||
import ResetPasswordDetails from '../common/ResetPasswordDetails/ResetPasswordDetails';
|
||||
import { useStyles } from './NewUser.styles';
|
||||
import { Box, TextField, Typography } from '@mui/material';
|
||||
import useResetPassword from 'hooks/api/getters/useResetPassword/useResetPassword';
|
||||
import StandaloneLayout from '../common/StandaloneLayout/StandaloneLayout';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import InvalidToken from '../common/InvalidToken/InvalidToken';
|
||||
import AuthOptions from '../common/AuthOptions/AuthOptions';
|
||||
import DividerText from 'component/common/DividerText/DividerText';
|
||||
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
|
||||
import { useInviteUserToken } from 'hooks/api/getters/useInviteUserToken/useInviteUserToken';
|
||||
import ResetPasswordForm from '../common/ResetPasswordForm/ResetPasswordForm';
|
||||
import InvalidToken from '../common/InvalidToken/InvalidToken';
|
||||
import { NewUserWrapper } from './NewUserWrapper/NewUserWrapper';
|
||||
|
||||
export const NewUser = () => {
|
||||
const { authDetails } = useAuthDetails();
|
||||
const { token, data, loading, setLoading, invalidToken } =
|
||||
useResetPassword();
|
||||
const ref = useLoading(loading);
|
||||
const { classes: styles } = useStyles();
|
||||
const {
|
||||
token,
|
||||
data,
|
||||
loading: resetLoading,
|
||||
setLoading,
|
||||
invalidToken,
|
||||
} = useResetPassword();
|
||||
const { invite, loading: inviteLoading } = useInviteUserToken();
|
||||
const passwordDisabled = authDetails?.defaultHidden === true;
|
||||
|
||||
if (invalidToken && !invite) {
|
||||
return (
|
||||
<NewUserWrapper loading={resetLoading || inviteLoading}>
|
||||
<InvalidToken />
|
||||
</NewUserWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<StandaloneLayout
|
||||
showMenu={false}
|
||||
BannerComponent={<StandaloneBanner title={'Unleash'} />}
|
||||
>
|
||||
<div className={styles.newUser}>
|
||||
<ConditionallyRender
|
||||
condition={invalidToken}
|
||||
show={<InvalidToken />}
|
||||
elseShow={
|
||||
<ResetPasswordDetails
|
||||
token={token}
|
||||
setLoading={setLoading}
|
||||
>
|
||||
<h2 className={styles.title}>
|
||||
Enter your personal details and start your
|
||||
journey
|
||||
</h2>
|
||||
<ConditionallyRender
|
||||
condition={data?.createdBy}
|
||||
show={
|
||||
<Typography
|
||||
variant="body1"
|
||||
data-loading
|
||||
className={styles.inviteText}
|
||||
>
|
||||
{data?.createdBy}
|
||||
<br></br> has invited you to join
|
||||
Unleash.
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
|
||||
<NewUserWrapper
|
||||
loading={resetLoading || inviteLoading}
|
||||
title={
|
||||
passwordDisabled
|
||||
? 'Connect your account and start your journey'
|
||||
: 'Enter your personal details and start your journey'
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={data?.createdBy}
|
||||
show={
|
||||
<Typography
|
||||
variant="body1"
|
||||
data-loading
|
||||
sx={{ textAlign: 'center', mb: 2 }}
|
||||
>
|
||||
{data?.createdBy}
|
||||
<br /> has invited you to join Unleash.
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<Typography color="text.secondary">
|
||||
We suggest using{' '}
|
||||
<Typography component="strong" fontWeight="bold">
|
||||
the email you use for work
|
||||
</Typography>
|
||||
.
|
||||
</Typography>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(authDetails?.options?.length)}
|
||||
show={
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<AuthOptions options={authDetails?.options} />
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
Boolean(authDetails?.options?.length) && !passwordDisabled
|
||||
}
|
||||
show={
|
||||
<DividerText
|
||||
text="or sign-up with an email address"
|
||||
data-loading
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={!passwordDisabled}
|
||||
show={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={data?.email}
|
||||
show={() => (
|
||||
<Typography
|
||||
data-loading
|
||||
variant="body1"
|
||||
className={styles.subtitle}
|
||||
sx={{ my: 1 }}
|
||||
>
|
||||
Your username is
|
||||
</Typography>
|
||||
|
||||
)}
|
||||
/>
|
||||
<TextField
|
||||
data-loading
|
||||
type="email"
|
||||
value={data?.email || ''}
|
||||
id="username"
|
||||
label="Email"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1 }}
|
||||
disabled={Boolean(data?.email)}
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(invite)}
|
||||
show={() => (
|
||||
<TextField
|
||||
data-loading
|
||||
value={data?.email || ''}
|
||||
value=""
|
||||
id="username"
|
||||
label="Username"
|
||||
label="Full name"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className={styles.emailField}
|
||||
disabled
|
||||
sx={{ my: 1 }}
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
<div className={styles.roleContainer}>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(
|
||||
authDetails?.options?.length
|
||||
)}
|
||||
show={
|
||||
<>
|
||||
<DividerText
|
||||
text="sign in with"
|
||||
data-loading
|
||||
/>
|
||||
|
||||
<AuthOptions
|
||||
options={
|
||||
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>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Typography variant="body1" data-loading sx={{ mt: 2 }}>
|
||||
Set a password for your account.
|
||||
</Typography>
|
||||
<ResetPasswordForm
|
||||
token={token}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</NewUserWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,53 @@
|
||||
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 useLoading from 'hooks/useLoading';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
interface INewUserWrapperProps {
|
||||
loading?: boolean;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const NewUserWrapper: FC<INewUserWrapperProps> = ({
|
||||
children,
|
||||
loading,
|
||||
title,
|
||||
}) => {
|
||||
const ref = useLoading(loading || false);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<StandaloneLayout
|
||||
showMenu={false}
|
||||
BannerComponent={<StandaloneBanner title={'Unleash'} />}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: ['100%', '350px'],
|
||||
}}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(title)}
|
||||
show={
|
||||
<Typography
|
||||
component="h2"
|
||||
sx={{
|
||||
fontSize: theme =>
|
||||
theme.fontSizes.mainHeader,
|
||||
marginBottom: 2,
|
||||
textAlign: 'center',
|
||||
fontWeight: theme => theme.fontWeight.bold,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
{children}
|
||||
</Box>
|
||||
</StandaloneLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,13 +1,11 @@
|
||||
import useLoading from 'hooks/useLoading';
|
||||
|
||||
import ResetPasswordDetails from '../common/ResetPasswordDetails/ResetPasswordDetails';
|
||||
|
||||
import { useStyles } from './ResetPassword.styles';
|
||||
import { 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 ResetPasswordForm from '../common/ResetPasswordForm/ResetPasswordForm';
|
||||
|
||||
const ResetPassword = () => {
|
||||
const { classes: styles } = useStyles();
|
||||
@ -22,10 +20,7 @@ const ResetPassword = () => {
|
||||
condition={invalidToken}
|
||||
show={<InvalidToken />}
|
||||
elseShow={
|
||||
<ResetPasswordDetails
|
||||
token={token}
|
||||
setLoading={setLoading}
|
||||
>
|
||||
<>
|
||||
<Typography
|
||||
variant="h2"
|
||||
className={styles.title}
|
||||
@ -33,7 +28,12 @@ const ResetPassword = () => {
|
||||
>
|
||||
Reset password
|
||||
</Typography>
|
||||
</ResetPasswordDetails>
|
||||
|
||||
<ResetPasswordForm
|
||||
token={token}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,11 +1,17 @@
|
||||
import { VFC } from 'react';
|
||||
import { Button, Typography } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { INVALID_TOKEN_BUTTON } from 'utils/testIds';
|
||||
import { useThemeStyles } from 'themes/themeStyles';
|
||||
import classnames from 'classnames';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
|
||||
|
||||
const InvalidToken = () => {
|
||||
const InvalidToken: VFC = () => {
|
||||
const { authDetails } = useAuthDetails();
|
||||
const { classes: themeStyles } = useThemeStyles();
|
||||
const passwordDisabled = authDetails?.defaultHidden === true;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
@ -17,20 +23,44 @@ const InvalidToken = () => {
|
||||
<Typography variant="h2" className={themeStyles.title}>
|
||||
Invalid token
|
||||
</Typography>
|
||||
<Typography variant="subtitle1">
|
||||
Your token has either been used to reset your password, or it
|
||||
has expired. Please request a new reset password URL in order to
|
||||
reset your password.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
component={Link}
|
||||
to="forgotten-password"
|
||||
data-testid={INVALID_TOKEN_BUTTON}
|
||||
>
|
||||
Reset password
|
||||
</Button>
|
||||
<ConditionallyRender
|
||||
condition={passwordDisabled}
|
||||
show={
|
||||
<>
|
||||
<Typography variant="subtitle1">
|
||||
Your instance does not support password
|
||||
authentication. Use correct work email to access
|
||||
your account.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
component={Link}
|
||||
to="/login"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
elseShow={
|
||||
<>
|
||||
<Typography variant="subtitle1">
|
||||
Your token has either been used to reset your
|
||||
password, or it has expired. Please request a new
|
||||
reset password URL in order to reset your password.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
component={Link}
|
||||
to="/forgotten-password"
|
||||
data-testid={INVALID_TOKEN_BUTTON}
|
||||
>
|
||||
Reset password
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,22 +0,0 @@
|
||||
import { FC, Dispatch, SetStateAction } from 'react';
|
||||
import ResetPasswordForm from '../ResetPasswordForm/ResetPasswordForm';
|
||||
|
||||
interface IResetPasswordDetails {
|
||||
token: string;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const ResetPasswordDetails: FC<IResetPasswordDetails> = ({
|
||||
children,
|
||||
token,
|
||||
setLoading,
|
||||
}) => {
|
||||
return (
|
||||
<div style={{ width: '100%' }}>
|
||||
{children}
|
||||
<ResetPasswordForm token={token} setLoading={setLoading} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPasswordDetails;
|
@ -3,6 +3,7 @@ import { makeStyles } from 'tss-react/mui';
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
matcherContainer: {
|
||||
position: 'relative',
|
||||
paddingTop: theme.spacing(0.5),
|
||||
},
|
||||
matcherIcon: {
|
||||
marginRight: '5px',
|
||||
|
@ -74,7 +74,7 @@ const ResetPasswordForm = ({ token, setLoading }: IResetPasswordProps) => {
|
||||
const res = await makeResetPasswordReq();
|
||||
setLoading(false);
|
||||
if (res.status === OK) {
|
||||
navigate('login?reset=true');
|
||||
navigate('/login?reset=true');
|
||||
setApiError(false);
|
||||
} else {
|
||||
setApiError(true);
|
||||
|
@ -0,0 +1,10 @@
|
||||
import useQueryParams from 'hooks/useQueryParams';
|
||||
|
||||
export const useInviteUserToken = () => {
|
||||
const query = useQueryParams();
|
||||
const invite = query.get('invite') || '';
|
||||
|
||||
// TODO: Invite token API
|
||||
|
||||
return { invite, loading: false };
|
||||
};
|
@ -1,9 +1,9 @@
|
||||
---
|
||||
id: sso-google
|
||||
title: "[Deprecated] How to add SSO with Google"
|
||||
title: '[Deprecated] How to add SSO with Google'
|
||||
---
|
||||
|
||||
> Single Sign-on via the Google Authenticator provider is deprecated. We recommend using [OpenId Connect](./sso-open-id-connect.md) instead.
|
||||
> Single Sign-on via the Google Authenticator provider is deprecated. We recommend using [OpenID Connect](./sso-open-id-connect.md) instead.
|
||||
|
||||
## Introduction {#introduction}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
id: sso-open-id-connect
|
||||
title: How to add SSO with OpenId Connect
|
||||
title: How to add SSO with OpenID Connect
|
||||
---
|
||||
|
||||
> The **Single-Sign-On capability** is only available for customers on the Enterprise subscription. Check out the [Unleash plans](https://www.getunleash.io/plans) for details.
|
||||
|
@ -5,7 +5,7 @@ title: Securing Unleash
|
||||
|
||||
**If you are still using Unleash v3 you need to follow the [securing-unleash-v3](./securing-unleash-v3)**
|
||||
|
||||
> This guide is only relevant if you are using Unleash Open-Source. The Enterprise edition does already ship with multiple SSO options, such as SAML 2.0, OpenId Connect.
|
||||
> This guide is only relevant if you are using Unleash Open-Source. The Enterprise edition does already ship with multiple SSO options, such as SAML 2.0, OpenID Connect.
|
||||
|
||||
Unleash Open-Source v4 comes with username/password authentication out of the box. In addition Unleash v4 also comes with API token support, to make it easy to handle access tokens for Client SDKs and programmatic access to the Unleash APIs.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user