change(ui) - login changes

This commit is contained in:
Shekar Siri 2023-03-24 15:58:07 +01:00
parent 561f333870
commit 7adf87a323
9 changed files with 294 additions and 240 deletions

View file

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

View file

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

View file

@ -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>Well manage hosting, scaling and upgrades.</div> <div>Well 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>

View file

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

View file

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

View 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

View 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

View 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