change(ui) - login changes
This commit is contained in:
parent
561f333870
commit
7adf87a323
9 changed files with 294 additions and 240 deletions
|
|
@ -14,21 +14,23 @@ const LOGIN = loginRoute();
|
||||||
const recaptchaRef = React.createRef();
|
const recaptchaRef = React.createRef();
|
||||||
const ERROR_DONT_MATCH = "Passwords don't match.";
|
const ERROR_DONT_MATCH = "Passwords don't match.";
|
||||||
const MIN_LENGTH = 8;
|
const MIN_LENGTH = 8;
|
||||||
const PASSWORD_POLICY = `Password should contain at least ${ MIN_LENGTH } symbols.`;
|
const PASSWORD_POLICY = `Password should contain at least ${MIN_LENGTH} symbols.`;
|
||||||
|
|
||||||
const checkDontMatch = (newPassword, newPasswordRepeat) =>
|
const checkDontMatch = (newPassword, newPasswordRepeat) =>
|
||||||
newPasswordRepeat.length > 0 && newPasswordRepeat !== newPassword;
|
newPasswordRepeat.length > 0 && newPasswordRepeat !== newPassword;
|
||||||
|
|
||||||
@connect(
|
@connect(
|
||||||
(state, props) => ({
|
(state, props) => ({
|
||||||
errors: state.getIn([ 'user', 'requestResetPassowrd', 'errors' ]),
|
errors: state.getIn(['user', 'requestResetPassowrd', 'errors']),
|
||||||
resetErrors: state.getIn([ 'user', 'resetPassword', 'errors' ]),
|
resetErrors: state.getIn(['user', 'resetPassword', 'errors']),
|
||||||
loading: state.getIn([ 'user', 'requestResetPassowrd', 'loading' ]) || state.getIn([ 'user', 'resetPassword', 'loading' ]),
|
loading:
|
||||||
params: new URLSearchParams(props.location.search)
|
state.getIn(['user', 'requestResetPassowrd', 'loading']) ||
|
||||||
|
state.getIn(['user', 'resetPassword', 'loading']),
|
||||||
|
params: new URLSearchParams(props.location.search),
|
||||||
}),
|
}),
|
||||||
{ requestResetPassword, resetPassword, resetErrors },
|
{ requestResetPassword, resetPassword, resetErrors }
|
||||||
)
|
)
|
||||||
@withPageTitle("Password Reset - OpenReplay")
|
@withPageTitle('Password Reset - OpenReplay')
|
||||||
@withRouter
|
@withRouter
|
||||||
export default class ForgotPassword extends React.PureComponent {
|
export default class ForgotPassword extends React.PureComponent {
|
||||||
state = {
|
state = {
|
||||||
|
|
@ -45,15 +47,17 @@ export default class ForgotPassword extends React.PureComponent {
|
||||||
const { email, password } = this.state;
|
const { email, password } = this.state;
|
||||||
const { params } = this.props;
|
const { params } = this.props;
|
||||||
|
|
||||||
const pass = params.get('pass')
|
const pass = params.get('pass');
|
||||||
const invitation = params.get('invitation')
|
const invitation = params.get('invitation');
|
||||||
const resetting = pass && invitation
|
const resetting = pass && invitation;
|
||||||
|
|
||||||
if (!resetting) {
|
if (!resetting) {
|
||||||
this.props.requestResetPassword({ email: email.trim(), 'g-recaptcha-response': token }).then(() => {
|
this.props
|
||||||
const { errors } = this.props;
|
.requestResetPassword({ email: email.trim(), 'g-recaptcha-response': token })
|
||||||
if (!errors) this.setState({ requested: true });
|
.then(() => {
|
||||||
});
|
const { errors } = this.props;
|
||||||
|
if (!errors) this.setState({ requested: true });
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (this.isSubmitDisabled()) return;
|
if (this.isSubmitDisabled()) return;
|
||||||
this.props.resetPassword({ email: email.trim(), invitation, pass, password }).then(() => {
|
this.props.resetPassword({ email: email.trim(), invitation, pass, password }).then(() => {
|
||||||
|
|
@ -61,16 +65,15 @@ export default class ForgotPassword extends React.PureComponent {
|
||||||
if (!resetErrors) this.setState({ updated: true });
|
if (!resetErrors) this.setState({ updated: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
isSubmitDisabled() {
|
isSubmitDisabled() {
|
||||||
const { password, passwordRepeat } = this.state;
|
const { password, passwordRepeat } = this.state;
|
||||||
if (password !== passwordRepeat ||
|
if (password !== passwordRepeat || password.length < MIN_LENGTH) return true;
|
||||||
password.length < MIN_LENGTH) return true;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
write = ({ target: { value, name } }) => this.setState({ [ name ]: value })
|
write = ({ target: { value, name } }) => this.setState({ [name]: value });
|
||||||
|
|
||||||
shouldShouwPolicy() {
|
shouldShouwPolicy() {
|
||||||
const { password } = this.state;
|
const { password } = this.state;
|
||||||
|
|
@ -79,152 +82,170 @@ export default class ForgotPassword extends React.PureComponent {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit = e => {
|
onSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { CAPTCHA_ENABLED } = this.state;
|
const { CAPTCHA_ENABLED } = this.state;
|
||||||
if (CAPTCHA_ENABLED && recaptchaRef.current) {
|
if (CAPTCHA_ENABLED && recaptchaRef.current) {
|
||||||
recaptchaRef.current.execute()
|
recaptchaRef.current.execute();
|
||||||
} else if (!CAPTCHA_ENABLED) {
|
} else if (!CAPTCHA_ENABLED) {
|
||||||
this.handleSubmit();
|
this.handleSubmit();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.resetErrors()
|
this.props.resetErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { CAPTCHA_ENABLED } = this.state;
|
const { CAPTCHA_ENABLED } = this.state;
|
||||||
const { errors, loading, params } = this.props;
|
const { errors, loading, params } = this.props;
|
||||||
const { requested, updated, password, passwordRepeat, email } = this.state;
|
const { requested, updated, password, passwordRepeat, email } = this.state;
|
||||||
const dontMatch = checkDontMatch(password, passwordRepeat);
|
const dontMatch = checkDontMatch(password, passwordRepeat);
|
||||||
|
|
||||||
const pass = params.get('pass')
|
const pass = params.get('pass');
|
||||||
const invitation = params.get('invitation')
|
const invitation = params.get('invitation');
|
||||||
const resetting = pass && invitation
|
const resetting = pass && invitation;
|
||||||
const validEmail = validateEmail(email)
|
const validEmail = validateEmail(email);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex" style={{ height: '100vh'}}>
|
<div className="flex items-center justify-center h-screen">
|
||||||
<div className={cn("w-6/12", stl.left)}>
|
<div className="flex flex-col items-center">
|
||||||
<div className="px-6 pt-10">
|
<div className="m-10 ">
|
||||||
<img src="/assets/logo-white.svg" />
|
<img src="/assets/logo.svg" width={200} />
|
||||||
</div>
|
</div>
|
||||||
<div className="color-white text-lg flex items-center">
|
<div className="border rounded bg-white" style={{ width: '350px' }}>
|
||||||
<div className="flex items-center justify-center w-full" style={{ height: 'calc(100vh - 130px)'}}>
|
<h2 className="text-center text-2xl font-medium mb-6 border-b p-5 w-full">
|
||||||
<div className="text-4xl">Welcome Back!</div>
|
{ resetting ? "Create Password" : "Reset Password" }
|
||||||
</div>
|
</h2>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-6/12 flex items-center justify-center">
|
|
||||||
<Form onSubmit={ this.onSubmit } style={{ minWidth: '50%' }} className="flex flex-col items-center justify-center">
|
|
||||||
<div className="mb-8">
|
|
||||||
<h2 className="text-center text-3xl mb-6">{`${resetting ? 'Create' : 'Reset'} Password`}</h2>
|
|
||||||
</div>
|
|
||||||
<Loader loading={ loading }>
|
|
||||||
<div data-hidden={ updated } className="w-full">
|
|
||||||
{ CAPTCHA_ENABLED && (
|
|
||||||
<div className={ stl.recaptcha }>
|
|
||||||
<ReCAPTCHA
|
|
||||||
ref={ recaptchaRef }
|
|
||||||
size="invisible"
|
|
||||||
data-hidden={ requested }
|
|
||||||
sitekey={ window.env.CAPTCHA_SITE_KEY }
|
|
||||||
onChange={ token => this.handleSubmit(token) }
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{ !resetting && !requested &&
|
<div className="px-8">
|
||||||
<Form.Field>
|
{resetting && <div className="my-6">Provide your email address, so we can send you a link to reset your password.</div> }
|
||||||
<i className={ stl.inputIconUser } />
|
<Form
|
||||||
<Input
|
onSubmit={this.onSubmit}
|
||||||
autoFocus={true}
|
style={{ minWidth: '50%' }}
|
||||||
autocomplete="email"
|
className="flex flex-col items-center justify-center"
|
||||||
type="text"
|
>
|
||||||
placeholder="Email"
|
{/* <div className="mb-8">
|
||||||
name="email"
|
<h2 className="text-center text-3xl mb-6">{`${
|
||||||
onChange={ this.write }
|
resetting ? 'Create' : 'Reset'
|
||||||
className="w-full"
|
} Password`}</h2>
|
||||||
icon="user-alt"
|
</div> */}
|
||||||
/>
|
<Loader loading={loading}>
|
||||||
</Form.Field>
|
<div data-hidden={updated} className="w-full">
|
||||||
}
|
{CAPTCHA_ENABLED && (
|
||||||
|
<div className={stl.recaptcha}>
|
||||||
{
|
<ReCAPTCHA
|
||||||
requested && !errors && (
|
ref={recaptchaRef}
|
||||||
<div>Reset password link has been sent to your email.</div>
|
size="invisible"
|
||||||
)
|
data-hidden={requested}
|
||||||
}
|
sitekey={window.env.CAPTCHA_SITE_KEY}
|
||||||
|
onChange={(token) => this.handleSubmit(token)}
|
||||||
{
|
|
||||||
resetting && (
|
|
||||||
<React.Fragment>
|
|
||||||
<Form.Field>
|
|
||||||
<i className={ stl.inputIconPassword } />
|
|
||||||
<Input
|
|
||||||
autocomplete="new-password"
|
|
||||||
type="password"
|
|
||||||
placeholder="Password"
|
|
||||||
name="password"
|
|
||||||
onChange={ this.write }
|
|
||||||
className="w-full"
|
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
|
||||||
<div className={ stl.passwordPolicy } data-hidden={ !this.shouldShouwPolicy() }>
|
|
||||||
{ PASSWORD_POLICY }
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!resetting && !requested && (
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<i className={ stl.inputIconPassword } />
|
<label>{'Email Address:'}</label>
|
||||||
<Input
|
<Input
|
||||||
autocomplete="new-password"
|
autoFocus={true}
|
||||||
type="password"
|
autocomplete="email"
|
||||||
placeholder="Confirm Password"
|
type="text"
|
||||||
name="passwordRepeat"
|
placeholder="Email"
|
||||||
onChange={ this.write }
|
name="email"
|
||||||
|
onChange={this.write}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
|
icon="envelope"
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
</React.Fragment>
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
<Message error hidden={ !dontMatch }>
|
{requested && !errors && (
|
||||||
{ ERROR_DONT_MATCH }
|
<div className="flex flex-col items-center justify-center">
|
||||||
</Message>
|
<div className="w-16 h-16 rounded-full bg-tealx-light flex items-center justify-center mb-2">
|
||||||
</div>
|
<Icon name="envelope-check" size={30} color="tealx" />
|
||||||
</Loader>
|
</div>
|
||||||
<div className="mt-4">
|
<div>Alright! a reset link was emailed to {email}. Click on it to reset your account password.</div>
|
||||||
{ errors &&
|
</div>
|
||||||
<div className={ stl.errors }>
|
)}
|
||||||
{ errors.map(error => <span>{ error }<br /></span>) }
|
|
||||||
|
{resetting && (
|
||||||
|
<React.Fragment>
|
||||||
|
<Form.Field>
|
||||||
|
<i className={stl.inputIconPassword} />
|
||||||
|
<Input
|
||||||
|
autocomplete="new-password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Password"
|
||||||
|
name="password"
|
||||||
|
onChange={this.write}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<div className={stl.passwordPolicy} data-hidden={!this.shouldShouwPolicy()}>
|
||||||
|
{PASSWORD_POLICY}
|
||||||
|
</div>
|
||||||
|
<Form.Field>
|
||||||
|
<i className={stl.inputIconPassword} />
|
||||||
|
<Input
|
||||||
|
autocomplete="new-password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
name="passwordRepeat"
|
||||||
|
onChange={this.write}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Message error hidden={!dontMatch}>
|
||||||
|
{ERROR_DONT_MATCH}
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
</Loader>
|
||||||
|
<div className="mt-4">
|
||||||
|
{errors && (
|
||||||
|
<div className={stl.errors}>
|
||||||
|
{errors.map((error) => (
|
||||||
|
<span>
|
||||||
|
{error}
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div data-hidden={!updated} className={stl.success}>
|
||||||
|
<Icon name="check" size="30" color="green" />
|
||||||
|
{'Your password has been updated sucessfully.'}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
<div data-hidden={ !updated } className={ stl.success }>
|
|
||||||
<Icon name="check" size="30" color="green" />
|
|
||||||
{ 'Your password has been updated sucessfully.' }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* <div className={ stl.formFooter }> */}
|
|
||||||
{!(updated || requested) && (
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="primary"
|
|
||||||
loading={loading}
|
|
||||||
disabled={ (resetting && this.isSubmitDisabled()) || (!resetting && !validEmail)}
|
|
||||||
>
|
|
||||||
{ resetting ? 'Create' : 'Reset' }
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={ stl.links }>
|
{!(updated || requested) && (
|
||||||
<Link to={ LOGIN }>
|
<Button
|
||||||
{ updated && (<Button variant="primary" type="submit" primary >{ 'Login' }</Button>)}
|
type="submit"
|
||||||
<div data-hidden={ updated }>{'Back to Login'}</div>
|
variant="primary"
|
||||||
</Link>
|
loading={loading}
|
||||||
</div>
|
className="w-full"
|
||||||
{/* </div> */}
|
disabled={(resetting && this.isSubmitDisabled()) || (!resetting && !validEmail)}
|
||||||
</Form>
|
>
|
||||||
|
{resetting ? 'Create' : 'Email password reset link'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="my-8">
|
||||||
|
<Link to={LOGIN}>
|
||||||
|
{updated && (
|
||||||
|
<Button variant="primary" type="submit" primary>
|
||||||
|
{'Login'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div data-hidden={updated} className="link">{'Back to Login'}</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -69,18 +69,16 @@ class Login extends React.Component {
|
||||||
const { CAPTCHA_ENABLED } = this.state;
|
const { CAPTCHA_ENABLED } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col md:flex-row" style={{ height: '100vh' }}>
|
<div className="flex items-center justify-center h-screen">
|
||||||
<div className={cn('md:w-6/12 relative overflow-hidden', stl.left)}>
|
<div className="flex flex-col items-center">
|
||||||
<div className="px-6 pt-10">
|
<div className="m-10 ">
|
||||||
<img src="/assets/logo-white.svg" />
|
<img src="/assets/logo.svg" width={200}/>
|
||||||
</div>
|
</div>
|
||||||
<img style={{ width: '800px', position: 'absolute', bottom: -100, left: 0 }} src={LoginBg} />
|
<div className="border rounded bg-white">
|
||||||
</div>
|
|
||||||
<div className="md:w-6/12 flex items-center justify-center py-10">
|
|
||||||
<div className="">
|
|
||||||
<Form onSubmit={this.onSubmit} className="flex items-center justify-center flex-col">
|
<Form onSubmit={this.onSubmit} className="flex items-center justify-center flex-col">
|
||||||
<div className="mb-8">
|
<h2 className="text-center text-2xl font-medium mb-6 border-b p-5 w-full">Login to your account</h2>
|
||||||
<h2 className="text-center text-3xl mb-6">Login to OpenReplay</h2>
|
<div className="">
|
||||||
|
|
||||||
{!authDetails.tenants && (
|
{!authDetails.tenants && (
|
||||||
<div className="text-center text-xl">
|
<div className="text-center text-xl">
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
|
|
@ -99,19 +97,19 @@ class Login extends React.Component {
|
||||||
onChange={(token) => this.handleSubmit(token)}
|
onChange={(token) => this.handleSubmit(token)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div style={{ width: '350px' }}>
|
<div style={{ width: '350px' }} className="px-8">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label>Email</label>
|
<label>Email Address</label>
|
||||||
<Input
|
<Input
|
||||||
data-test-id={"login"}
|
data-test-id={"login"}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Email"
|
placeholder="e.g. john@example.com"
|
||||||
name="email"
|
name="email"
|
||||||
onChange={this.write}
|
onChange={this.write}
|
||||||
required
|
required
|
||||||
icon="user-alt"
|
icon="envelope"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
|
|
@ -142,15 +140,16 @@ class Login extends React.Component {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{/* <div className={ stl.formFooter }> */}
|
|
||||||
<Button data-test-id={"log-button"} className="mt-2" type="submit" variant="primary">
|
<div className="px-8 w-full">
|
||||||
{'Login'}
|
<Button data-test-id={"log-button"} className="mt-2 w-full text-center" type="submit" variant="primary">
|
||||||
</Button>
|
{'Login'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<div className={cn(stl.links, 'text-lg')}>
|
<div className="my-8">
|
||||||
<Link to={FORGOT_PASSWORD}>{'Forgot your password?'}</Link>
|
<span className="color-gray-medium">Having trouble logging in?</span> <Link to={FORGOT_PASSWORD} className="link ml-1">{'Reset password'}</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* </div> */}
|
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<div className={cn(stl.sso, 'py-2 flex flex-col items-center')}>
|
<div className={cn(stl.sso, 'py-2 flex flex-col items-center')}>
|
||||||
|
|
@ -182,6 +181,8 @@ class Login extends React.Component {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -19,24 +19,19 @@ const BulletItem = ({ text }) => (
|
||||||
export default class Signup extends React.Component {
|
export default class Signup extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="flex" style={{ height: '100vh' }}>
|
<div className="flex justify-center items-center gap-6" style={{ height: '100vh' }}>
|
||||||
<div className={cn('w-6/12 relative overflow-hidden', stl.left)}>
|
<div className={cn('relative overflow-hidden')}>
|
||||||
<div className="px-6 pt-10">
|
<div className="text-lg flex items-center" style={{ width: '350px'}}>
|
||||||
<img src="/assets/logo-white.svg" />
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
style={{ width: '800px', position: 'absolute', bottom: -100, left: 0 }}
|
|
||||||
src={RegisterBg}
|
|
||||||
/>
|
|
||||||
<div className="color-white text-lg flex items-center px-20 pt-32">
|
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center text-3xl font-bold mb-6">
|
<div className="flex items-end text-3xl font-bold mb-6">
|
||||||
OpenReplay Cloud{' '}
|
<div className="">
|
||||||
<div className="ml-2">
|
<img src="/assets/logo.svg" width={200}/>
|
||||||
<Icon name="signup" size="28" color="white" />
|
</div>{' '}
|
||||||
|
<div className="ml-2 text-lg color-gray-medium">
|
||||||
|
Cloud
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>OpenReplay Cloud is the hosted version of our open-source project.</div>
|
<div className="border-b pb-2 mb-2">OpenReplay Cloud is the hosted version of our <a className="link" href="https://github.com/openreplay/openreplay" target="_blank">open-source</a> project.</div>
|
||||||
<div>We’ll manage hosting, scaling and upgrades.</div>
|
<div>We’ll manage hosting, scaling and upgrades.</div>
|
||||||
|
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
|
|
@ -47,7 +42,7 @@ export default class Signup extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-6/12 flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
<div className="">
|
<div className="">
|
||||||
<SignupForm />
|
<SignupForm />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,25 @@
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { Form, Input, Icon, Button, Link } from 'UI'
|
import { Form, Input, Icon, Button, Link } from 'UI';
|
||||||
import { login } from 'App/routes'
|
import { login } from 'App/routes';
|
||||||
import ReCAPTCHA from 'react-google-recaptcha'
|
import ReCAPTCHA from 'react-google-recaptcha';
|
||||||
import stl from './signup.module.css'
|
import stl from './signup.module.css';
|
||||||
import { signup } from 'Duck/user';
|
import { signup } from 'Duck/user';
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux';
|
||||||
import Select from 'Shared/Select'
|
import Select from 'Shared/Select';
|
||||||
import { SITE_ID_STORAGE_KEY } from 'App/constants/storageKeys';
|
import { SITE_ID_STORAGE_KEY } from 'App/constants/storageKeys';
|
||||||
|
|
||||||
const LOGIN_ROUTE = login()
|
const LOGIN_ROUTE = login();
|
||||||
const recaptchaRef = React.createRef()
|
const recaptchaRef = React.createRef();
|
||||||
|
|
||||||
@connect(
|
@connect(
|
||||||
state => ({
|
(state) => ({
|
||||||
tenants: state.getIn(['user', 'tenants']),
|
tenants: state.getIn(['user', 'tenants']),
|
||||||
errors: state.getIn([ 'user', 'signupRequest', 'errors' ]),
|
errors: state.getIn(['user', 'signupRequest', 'errors']),
|
||||||
loading: state.getIn([ 'user', 'signupRequest', 'loading' ]),
|
loading: state.getIn(['user', 'signupRequest', 'loading']),
|
||||||
}),
|
}),
|
||||||
{ signup },
|
{ signup }
|
||||||
)
|
)
|
||||||
export default class SignupForm extends React.Component {
|
export default class SignupForm extends React.Component {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
tenantId: '',
|
tenantId: '',
|
||||||
fullname: '',
|
fullname: '',
|
||||||
|
|
@ -36,21 +35,30 @@ export default class SignupForm extends React.Component {
|
||||||
if (props.errors && props.errors.size > 0 && state.reload) {
|
if (props.errors && props.errors.size > 0 && state.reload) {
|
||||||
recaptchaRef.current.reset();
|
recaptchaRef.current.reset();
|
||||||
return {
|
return {
|
||||||
reload: false
|
reload: false,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = (token) => {
|
handleSubmit = (token) => {
|
||||||
const { tenantId, fullname, password, email, projectName, organizationName, auth } = this.state;
|
const { tenantId, fullname, password, email, projectName, organizationName, auth } = this.state;
|
||||||
localStorage.removeItem(SITE_ID_STORAGE_KEY)
|
localStorage.removeItem(SITE_ID_STORAGE_KEY);
|
||||||
this.props.signup({ tenantId, fullname, password, email, projectName, organizationName, auth, 'g-recaptcha-response': token })
|
this.props.signup({
|
||||||
this.setState({ reload: true })
|
tenantId,
|
||||||
}
|
fullname,
|
||||||
|
password,
|
||||||
|
email,
|
||||||
|
projectName,
|
||||||
|
organizationName,
|
||||||
|
auth,
|
||||||
|
'g-recaptcha-response': token,
|
||||||
|
});
|
||||||
|
this.setState({ reload: true });
|
||||||
|
};
|
||||||
|
|
||||||
write = ({ target: { value, name } }) => this.setState({ [ name ]: value })
|
write = ({ target: { value, name } }) => this.setState({ [name]: value });
|
||||||
writeOption = ({ name, value }) => this.setState({ [ name ]: value.value });
|
writeOption = ({ name, value }) => this.setState({ [name]: value.value });
|
||||||
|
|
||||||
onSubmit = (e) => {
|
onSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -60,52 +68,51 @@ export default class SignupForm extends React.Component {
|
||||||
} else if (!CAPTCHA_ENABLED) {
|
} else if (!CAPTCHA_ENABLED) {
|
||||||
this.handleSubmit();
|
this.handleSubmit();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
render() {
|
render() {
|
||||||
const { loading, errors, tenants } = this.props;
|
const { loading, errors, tenants } = this.props;
|
||||||
const { CAPTCHA_ENABLED } = this.state;
|
const { CAPTCHA_ENABLED } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={ this.onSubmit }>
|
<Form onSubmit={this.onSubmit} className="bg-white border rounded">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h2 className="text-center text-3xl mb-6">Get Started</h2>
|
<h2 className="text-center text-2xl font-medium mb-6 border-b p-5 w-full">Create Account</h2>
|
||||||
<div className="text-center text-xl">Already having an account? <span className="link"><Link to={ LOGIN_ROUTE }>Sign in</Link></span></div>
|
|
||||||
</div>
|
</div>
|
||||||
<>
|
<>
|
||||||
{ CAPTCHA_ENABLED && (
|
{CAPTCHA_ENABLED && (
|
||||||
<ReCAPTCHA
|
<ReCAPTCHA
|
||||||
ref={ recaptchaRef }
|
ref={recaptchaRef}
|
||||||
size="invisible"
|
size="invisible"
|
||||||
sitekey={ window.env.CAPTCHA_SITE_KEY }
|
sitekey={window.env.CAPTCHA_SITE_KEY}
|
||||||
onChange={ token => this.handleSubmit(token) }
|
onChange={(token) => this.handleSubmit(token)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div className="px-8">
|
||||||
{ tenants.length > 0 && (
|
{tenants.length > 0 && (
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<label>Existing Accounts</label>
|
<label>Existing Accounts</label>
|
||||||
<Select
|
<Select
|
||||||
className="w-full"
|
className="w-full"
|
||||||
placeholder="Select account"
|
placeholder="Select account"
|
||||||
selection
|
selection
|
||||||
options={ tenants }
|
options={tenants}
|
||||||
name="tenantId"
|
name="tenantId"
|
||||||
// value={ instance.currentPeriod }
|
// value={ instance.currentPeriod }
|
||||||
onChange={ this.writeOption }
|
onChange={this.writeOption}
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
)}
|
)}
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<label>Email</label>
|
<label>Email Address</label>
|
||||||
<Input
|
<Input
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="E.g. email@yourcompany.com"
|
placeholder="E.g. email@yourcompany.com"
|
||||||
name="email"
|
name="email"
|
||||||
onChange={ this.write }
|
onChange={this.write}
|
||||||
className={ stl.email }
|
|
||||||
required="true"
|
required="true"
|
||||||
|
icon="envelope"
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
|
|
@ -115,57 +122,72 @@ export default class SignupForm extends React.Component {
|
||||||
placeholder="Min 8 Characters"
|
placeholder="Min 8 Characters"
|
||||||
minLength="8"
|
minLength="8"
|
||||||
name="password"
|
name="password"
|
||||||
onChange={ this.write }
|
onChange={this.write}
|
||||||
className={ stl.password }
|
|
||||||
required="true"
|
required="true"
|
||||||
|
icon="key"
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<label>Name</label>
|
<label>Name</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="E.g John Doe"
|
placeholder="E.g John Doe"
|
||||||
name="fullname"
|
name="fullname"
|
||||||
onChange={ this.write }
|
onChange={this.write}
|
||||||
className={ stl.email }
|
|
||||||
required="true"
|
required="true"
|
||||||
|
icon="user-alt"
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<label>Organization</label>
|
<label>Organization</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="E.g Uber"
|
placeholder="E.g Uber"
|
||||||
name="organizationName"
|
name="organizationName"
|
||||||
onChange={ this.write }
|
onChange={this.write}
|
||||||
className={ stl.email }
|
|
||||||
required="true"
|
required="true"
|
||||||
|
icon="buildings"
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
|
||||||
<div className="mb-6">
|
<Button type="submit" variant="primary" loading={loading} className="w-full">
|
||||||
<div className="text-sm">By creating an account, you agree to our <a href="https://openreplay.com/terms.html" className="link">Terms of Service</a> and <a href="https://openreplay.com/privacy.html" className="link">Privacy Policy</a>.</div>
|
Create account
|
||||||
|
</Button>
|
||||||
|
<div className="my-6">
|
||||||
|
<div className="text-sm">
|
||||||
|
By signing up, you agree to our{' '}
|
||||||
|
<a href="https://openreplay.com/terms.html" className="link">
|
||||||
|
terms of service
|
||||||
|
</a>{' '}
|
||||||
|
and{' '}
|
||||||
|
<a href="https://openreplay.com/privacy.html" className="link">
|
||||||
|
privacy policy
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
{ errors &&
|
{errors && (
|
||||||
<div className={ stl.errors }>
|
<div className={stl.errors}>
|
||||||
{ errors.map(error => (
|
{errors.map((error) => (
|
||||||
<div className={stl.errorItem}>
|
<div className={stl.errorItem}>
|
||||||
<Icon name="info" color="red" size="20"/>
|
<Icon name="info" color="red" size="20" />
|
||||||
<span className="color-red ml-2">{ error }<br /></span>
|
<span className="color-red ml-2">
|
||||||
|
{error}
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)) }
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="text-center bg-gray-50 py-4 border-t">
|
||||||
|
Already having an account?{' '}
|
||||||
|
<span className="link">
|
||||||
|
<Link to={LOGIN_ROUTE}>Login</Link>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
<div className={ stl.formFooter }>
|
|
||||||
<Button type="submit" variant="primary" loading={loading}>
|
|
||||||
Create account
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export default (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const render = () => (
|
const render = () => (
|
||||||
<button {...rest} type={type} className={cn(classes, className)}>
|
<button {...rest} type={type} className={cn(classes, className, 'flex items-center justify-center')}>
|
||||||
{icon && (
|
{icon && (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<Icon className={cn({ 'mr-2': children })} name={icon} color={iconColor} size={iconSize} />
|
<Icon className={cn({ 'mr-2': children })} name={icon} color={iconColor} size={iconSize} />
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
4
frontend/app/svg/icons/buildings.svg
Normal file
4
frontend/app/svg/icons/buildings.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-buildings" viewBox="0 0 16 16">
|
||||||
|
<path d="M14.763.075A.5.5 0 0 1 15 .5v15a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5V14h-1v1.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V10a.5.5 0 0 1 .342-.474L6 7.64V4.5a.5.5 0 0 1 .276-.447l8-4a.5.5 0 0 1 .487.022ZM6 8.694 1 10.36V15h5V8.694ZM7 15h2v-1.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5V15h2V1.309l-7 3.5V15Z"/>
|
||||||
|
<path d="M2 11h1v1H2v-1Zm2 0h1v1H4v-1Zm-2 2h1v1H2v-1Zm2 0h1v1H4v-1Zm4-4h1v1H8V9Zm2 0h1v1h-1V9Zm-2 2h1v1H8v-1Zm2 0h1v1h-1v-1Zm2-2h1v1h-1V9Zm0 2h1v1h-1v-1ZM8 7h1v1H8V7Zm2 0h1v1h-1V7Zm2 0h1v1h-1V7ZM8 5h1v1H8V5Zm2 0h1v1h-1V5Zm2 0h1v1h-1V5Zm0-2h1v1h-1V3Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 672 B |
4
frontend/app/svg/icons/envelope-check.svg
Normal file
4
frontend/app/svg/icons/envelope-check.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-envelope-check" viewBox="0 0 16 16">
|
||||||
|
<path d="M2 2a2 2 0 0 0-2 2v8.01A2 2 0 0 0 2 14h5.5a.5.5 0 0 0 0-1H2a1 1 0 0 1-.966-.741l5.64-3.471L8 9.583l7-4.2V8.5a.5.5 0 0 0 1 0V4a2 2 0 0 0-2-2H2Zm3.708 6.208L1 11.105V5.383l4.708 2.825ZM1 4.217V4a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v.217l-7 4.2-7-4.2Z"/>
|
||||||
|
<path d="M16 12.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Zm-1.993-1.679a.5.5 0 0 0-.686.172l-1.17 1.95-.547-.547a.5.5 0 0 0-.708.708l.774.773a.75.75 0 0 0 1.174-.144l1.335-2.226a.5.5 0 0 0-.172-.686Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 574 B |
4
frontend/app/svg/icons/key.svg
Normal file
4
frontend/app/svg/icons/key.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-key" viewBox="0 0 16 16">
|
||||||
|
<path d="M0 8a4 4 0 0 1 7.465-2H14a.5.5 0 0 1 .354.146l1.5 1.5a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0L13 9.207l-.646.647a.5.5 0 0 1-.708 0L11 9.207l-.646.647a.5.5 0 0 1-.708 0L9 9.207l-.646.647A.5.5 0 0 1 8 10h-.535A4 4 0 0 1 0 8zm4-3a3 3 0 1 0 2.712 4.285A.5.5 0 0 1 7.163 9h.63l.853-.854a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .708 0l.646.647.793-.793-1-1h-6.63a.5.5 0 0 1-.451-.285A3 3 0 0 0 4 5z"/>
|
||||||
|
<path d="M4 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 608 B |
Loading…
Add table
Reference in a new issue