change(ui) - password validations
This commit is contained in:
parent
2494a98a57
commit
3f5691f485
7 changed files with 340 additions and 270 deletions
|
|
@ -4,10 +4,11 @@ import { Button, Message, Form, Input } from 'UI';
|
|||
import styles from './profileSettings.module.css';
|
||||
import { updatePassword } from 'Duck/user';
|
||||
import { toast } from 'react-toastify';
|
||||
import { validatePassword } from 'App/validate';
|
||||
import { PASSWORD_POLICY } from 'App/constants';
|
||||
|
||||
const ERROR_DOESNT_MATCH = "Passwords don't match";
|
||||
const MIN_LENGTH = 8;
|
||||
const PASSWORD_POLICY = `Password should contain at least ${MIN_LENGTH} symbols`;
|
||||
|
||||
type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
|
|
@ -39,12 +40,6 @@ const ChangePassword: React.FC<PropsFromRedux> = ({ passwordErrors, loading, upd
|
|||
return false;
|
||||
}, [newPassword, newPasswordRepeat, oldPassword]);
|
||||
|
||||
const validatePassword = (password: string) => {
|
||||
const regex =
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?])[A-Za-z\d!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]{8,}$/;
|
||||
return regex.test(password);
|
||||
};
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -126,12 +121,8 @@ const ChangePassword: React.FC<PropsFromRedux> = ({ passwordErrors, loading, upd
|
|||
{ERROR_DOESNT_MATCH}
|
||||
</Message>
|
||||
<Message error hidden={!newPassword.error}>
|
||||
Password should contain at least one capital letter, special character, and digit, and
|
||||
length min 8
|
||||
{PASSWORD_POLICY}
|
||||
</Message>
|
||||
{/* <Message error hidden={!newPasswordRepeat.error}>
|
||||
Passwords don't match
|
||||
</Message> */}
|
||||
<div className="flex items-center pt-3">
|
||||
<Button type="submit" variant="outline" disabled={isSubmitDisabled()} loading={loading}>
|
||||
Change Password
|
||||
|
|
|
|||
166
frontend/app/components/ForgotPassword/CreatePassword.tsx
Normal file
166
frontend/app/components/ForgotPassword/CreatePassword.tsx
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ReCAPTCHA from 'react-google-recaptcha';
|
||||
import { Form, Input, Loader, Button, Icon, Message } from 'UI';
|
||||
import { requestResetPassword, resetPassword, resetErrors } from 'Duck/user';
|
||||
import stl from './forgotPassword.module.css';
|
||||
import { validatePassword } from 'App/validate';
|
||||
import { PASSWORD_POLICY } from 'App/constants';
|
||||
|
||||
const recaptchaRef = React.createRef();
|
||||
const ERROR_DONT_MATCH = "Passwords don't match.";
|
||||
const CAPTCHA_ENABLED = window.env.CAPTCHA_ENABLED === 'true';
|
||||
const CAPTCHA_SITE_KEY = window.env.CAPTCHA_SITE_KEY;
|
||||
|
||||
interface Props {
|
||||
errors: any;
|
||||
resetErrors: any;
|
||||
loading: boolean;
|
||||
params: any;
|
||||
resetPassword: Function;
|
||||
}
|
||||
function CreatePassword(props: Props) {
|
||||
const { loading, params } = props;
|
||||
const [error, setError] = React.useState<String | null>(null);
|
||||
const [validationError, setValidationError] = React.useState<String | null>(null);
|
||||
const [updated, setUpdated] = React.useState(false);
|
||||
const [requested, setRequested] = React.useState(false);
|
||||
const [passwordRepeat, setPasswordRepeat] = React.useState('');
|
||||
const [password, setPassword] = React.useState('');
|
||||
const [doesntMatch, setDoesntMatch] = React.useState(false);
|
||||
const pass = params.get('pass');
|
||||
const invitation = params.get('invitation');
|
||||
|
||||
const handleSubmit = (token?: any) => {
|
||||
if (!validatePassword(password)) {
|
||||
return;
|
||||
}
|
||||
props.resetPassword({ invitation, pass, password }).then((response: any) => {
|
||||
if (response && response.errors && response.errors.length > 0) {
|
||||
setError(response.errors[0]);
|
||||
} else {
|
||||
setUpdated(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (e: any) => {
|
||||
e.preventDefault();
|
||||
if (CAPTCHA_ENABLED && recaptchaRef.current) {
|
||||
recaptchaRef.current.execute();
|
||||
} else if (!CAPTCHA_ENABLED) {
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const write = (e: any) => {
|
||||
const { name, value } = e.target;
|
||||
if (name === 'password') setPassword(value);
|
||||
if (name === 'passwordRepeat') setPasswordRepeat(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (passwordRepeat.length > 0 && passwordRepeat !== password) {
|
||||
setValidationError(ERROR_DONT_MATCH);
|
||||
} else if (passwordRepeat.length > 0 && !validatePassword(password)) {
|
||||
setValidationError(PASSWORD_POLICY);
|
||||
} else {
|
||||
setValidationError(null);
|
||||
}
|
||||
}, [passwordRepeat, password]);
|
||||
|
||||
return (
|
||||
<Form
|
||||
onSubmit={onSubmit}
|
||||
style={{ minWidth: '50%' }}
|
||||
className="flex flex-col items-center justify-center"
|
||||
>
|
||||
{!error && (
|
||||
<>
|
||||
<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={CAPTCHA_SITE_KEY}
|
||||
onChange={(token: any) => handleSubmit(token)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<React.Fragment>
|
||||
<Form.Field>
|
||||
<label>{'New password'}</label>
|
||||
<Input
|
||||
autoComplete="new-password"
|
||||
type="password"
|
||||
placeholder="Type here..."
|
||||
name="password"
|
||||
onChange={write}
|
||||
className="w-full"
|
||||
icon="key"
|
||||
required
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<label>{'Cofirm password'}</label>
|
||||
<Input
|
||||
autoComplete="new-password"
|
||||
type="password"
|
||||
placeholder="Re-enter your new password"
|
||||
name="passwordRepeat"
|
||||
onChange={write}
|
||||
className="w-full"
|
||||
icon="key"
|
||||
required
|
||||
/>
|
||||
</Form.Field>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
</Loader>
|
||||
<div className="mt-4">
|
||||
<div data-hidden={!updated} className="flex items-center flex-col text-center">
|
||||
<div className="w-10 h-10 bg-tealx-lightest rounded-full flex items-center justify-center mb-3">
|
||||
<Icon name="check" size="30" color="tealx" />
|
||||
</div>
|
||||
<span>{'Your password has been updated sucessfully.'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{validationError && <Message error>{validationError}</Message>}
|
||||
|
||||
<Button type="submit" variant="primary" loading={loading} className="w-full mt-4">
|
||||
Create
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="flex items-center flex-col text-center">
|
||||
<div className="w-16 h-16 rounded-full bg-red-lightest flex items-center justify-center mb-2">
|
||||
<Icon name="envelope-x" size="30" color="red" />
|
||||
</div>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
errors: state.getIn(['user', 'requestResetPassowrd', 'errors']),
|
||||
resetErrors: state.getIn(['user', 'resetPassword', 'errors']),
|
||||
loading:
|
||||
state.getIn(['user', 'requestResetPassowrd', 'loading']) ||
|
||||
state.getIn(['user', 'resetPassword', 'loading']),
|
||||
}),
|
||||
{
|
||||
requestResetPassword,
|
||||
resetPassword,
|
||||
resetErrors,
|
||||
}
|
||||
)(CreatePassword);
|
||||
|
|
@ -1,257 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ReCAPTCHA from 'react-google-recaptcha';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import { Form, Input, Loader, Button, Link, Icon, Message } from 'UI';
|
||||
import { requestResetPassword, resetPassword, resetErrors } from 'Duck/user';
|
||||
import { login as loginRoute } from 'App/routes';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { validateEmail } from 'App/validate';
|
||||
import cn from 'classnames';
|
||||
import stl from './forgotPassword.module.css';
|
||||
import Copyright from 'Shared/Copyright';
|
||||
|
||||
const LOGIN = loginRoute();
|
||||
const recaptchaRef = React.createRef();
|
||||
const ERROR_DONT_MATCH = "Passwords don't match.";
|
||||
const MIN_LENGTH = 8;
|
||||
const PASSWORD_POLICY = `Password should contain at least ${MIN_LENGTH} symbols.`;
|
||||
|
||||
const checkDontMatch = (newPassword, newPasswordRepeat) =>
|
||||
newPasswordRepeat.length > 0 && newPasswordRepeat !== newPassword;
|
||||
|
||||
@connect(
|
||||
(state, props) => ({
|
||||
errors: state.getIn(['user', 'requestResetPassowrd', 'errors']),
|
||||
resetErrors: state.getIn(['user', 'resetPassword', 'errors']),
|
||||
loading:
|
||||
state.getIn(['user', 'requestResetPassowrd', 'loading']) ||
|
||||
state.getIn(['user', 'resetPassword', 'loading']),
|
||||
params: new URLSearchParams(props.location.search),
|
||||
}),
|
||||
{ requestResetPassword, resetPassword, resetErrors }
|
||||
)
|
||||
@withPageTitle('Password Reset - OpenReplay')
|
||||
@withRouter
|
||||
export default class ForgotPassword extends React.PureComponent {
|
||||
state = {
|
||||
email: '',
|
||||
code: ' ',
|
||||
password: '',
|
||||
passwordRepeat: '',
|
||||
requested: false,
|
||||
updated: false,
|
||||
CAPTCHA_ENABLED: window.env.CAPTCHA_ENABLED === 'true',
|
||||
};
|
||||
|
||||
handleSubmit = (token) => {
|
||||
const { email, password } = this.state;
|
||||
const { params } = this.props;
|
||||
|
||||
const pass = params.get('pass');
|
||||
const invitation = params.get('invitation');
|
||||
const resetting = pass && invitation;
|
||||
|
||||
if (!resetting) {
|
||||
this.props
|
||||
.requestResetPassword({ email: email.trim(), 'g-recaptcha-response': token })
|
||||
.then(() => {
|
||||
const { errors } = this.props;
|
||||
if (!errors) this.setState({ requested: true });
|
||||
});
|
||||
} else {
|
||||
if (this.isSubmitDisabled()) return;
|
||||
this.props.resetPassword({ email: email.trim(), invitation, pass, password }).then(() => {
|
||||
const { resetErrors } = this.props;
|
||||
if (!resetErrors) this.setState({ updated: true });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
isSubmitDisabled() {
|
||||
const { password, passwordRepeat } = this.state;
|
||||
if (password !== passwordRepeat || password.length < MIN_LENGTH) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
write = ({ target: { value, name } }) => this.setState({ [name]: value });
|
||||
|
||||
shouldShouwPolicy() {
|
||||
const { password } = this.state;
|
||||
if (password.length > 7) return false;
|
||||
if (password === '') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const { CAPTCHA_ENABLED } = this.state;
|
||||
if (CAPTCHA_ENABLED && recaptchaRef.current) {
|
||||
recaptchaRef.current.execute();
|
||||
} else if (!CAPTCHA_ENABLED) {
|
||||
this.handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.resetErrors();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { CAPTCHA_ENABLED } = this.state;
|
||||
const { errors, loading, params } = this.props;
|
||||
const { requested, updated, password, passwordRepeat, email } = this.state;
|
||||
const dontMatch = checkDontMatch(password, passwordRepeat);
|
||||
|
||||
const pass = params.get('pass');
|
||||
const invitation = params.get('invitation');
|
||||
const resetting = pass && invitation;
|
||||
const validEmail = validateEmail(email);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="m-10 ">
|
||||
<img src="/assets/logo.svg" width={200} />
|
||||
</div>
|
||||
<div className="border rounded bg-white" style={{ width: '350px' }}>
|
||||
{!resetting && <h2 className="text-center text-2xl font-medium mb-6 border-b p-5 w-full">Reset Password</h2>}
|
||||
{resetting && <h2 className="text-center text-lg font-medium mb-6 border-b p-5 w-full">
|
||||
Welcome, join your organization by <br/> creating a new password
|
||||
</h2>
|
||||
}
|
||||
|
||||
<div className="px-8">
|
||||
<Form
|
||||
onSubmit={this.onSubmit}
|
||||
style={{ minWidth: '50%' }}
|
||||
className="flex flex-col items-center justify-center"
|
||||
>
|
||||
<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 && (
|
||||
<Form.Field>
|
||||
<label>{'Email Address'}</label>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
autoComplete="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
name="email"
|
||||
onChange={this.write}
|
||||
className="w-full"
|
||||
icon="envelope"
|
||||
required
|
||||
/>
|
||||
</Form.Field>
|
||||
)}
|
||||
|
||||
{requested && !errors && (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="w-16 h-16 rounded-full bg-tealx-light flex items-center justify-center mb-2">
|
||||
<Icon name="envelope-check" size={30} color="tealx" />
|
||||
</div>
|
||||
<div>Alright! a reset link was emailed to {email}. Click on it to reset your account password.</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{resetting && (
|
||||
<React.Fragment>
|
||||
<Form.Field>
|
||||
<label>{'New password'}</label>
|
||||
{/* <i className={stl.inputIconPassword} /> */}
|
||||
<Input
|
||||
autoComplete="new-password"
|
||||
type="password"
|
||||
placeholder="Type here..."
|
||||
name="password"
|
||||
onChange={this.write}
|
||||
className="w-full"
|
||||
icon="key"
|
||||
required
|
||||
/>
|
||||
</Form.Field>
|
||||
<div className={stl.passwordPolicy} data-hidden={!this.shouldShouwPolicy()}>
|
||||
{PASSWORD_POLICY}
|
||||
</div>
|
||||
<Form.Field>
|
||||
<label>{'Cofirm password'}</label>
|
||||
<Input
|
||||
autoComplete="new-password"
|
||||
type="password"
|
||||
placeholder="Re-enter your new password"
|
||||
name="passwordRepeat"
|
||||
onChange={this.write}
|
||||
className="w-full"
|
||||
icon="key"
|
||||
required
|
||||
/>
|
||||
</Form.Field>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<Message error hidden={!dontMatch}>
|
||||
{ERROR_DONT_MATCH}
|
||||
</Message>
|
||||
</div>
|
||||
</Loader>
|
||||
<div className="mt-4">
|
||||
{errors && errors.map((error, i) => (
|
||||
<div className="flex items-center flex-col text-center" key={i}>
|
||||
<div className="w-16 h-16 rounded-full bg-red-lightest flex items-center justify-center mb-2">
|
||||
<Icon name="envelope-x" size="30" color="red" />
|
||||
</div>
|
||||
{error}
|
||||
</div>
|
||||
))}
|
||||
<div data-hidden={!updated} className="flex items-center flex-col text-center">
|
||||
<div className="w-10 h-10 bg-tealx-lightest rounded-full flex items-center justify-center mb-3">
|
||||
<Icon name="check" size="30" color="tealx" />
|
||||
</div>
|
||||
<span>{'Your password has been updated sucessfully.'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!(updated || requested) && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
loading={loading}
|
||||
className="w-full"
|
||||
// disabled={(resetting && this.isSubmitDisabled()) || (!resetting && !validEmail)}
|
||||
>
|
||||
{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>
|
||||
<Copyright />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
58
frontend/app/components/ForgotPassword/ForgotPassword.tsx
Normal file
58
frontend/app/components/ForgotPassword/ForgotPassword.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import Copyright from 'Shared/Copyright';
|
||||
import React from 'react';
|
||||
import { Form, Input, Loader, Button, Link, Icon, Message } from 'UI';
|
||||
import { login as loginRoute } from 'App/routes';
|
||||
import { connect } from 'react-redux';
|
||||
import ResetPassword from './ResetPasswordRequest';
|
||||
import CreatePassword from './CreatePassword';
|
||||
|
||||
const LOGIN = loginRoute();
|
||||
|
||||
interface Props {
|
||||
params: any;
|
||||
}
|
||||
function ForgotPassword(props: Props) {
|
||||
const { params } = props;
|
||||
const pass = params.get('pass');
|
||||
const invitation = params.get('invitation');
|
||||
const creatingNewPassword = pass && invitation;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen -mt-20">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="m-10 ">
|
||||
<img src="/assets/logo.svg" width={200} />
|
||||
</div>
|
||||
<div className="border rounded bg-white" style={{ width: '350px' }}>
|
||||
{creatingNewPassword ? (
|
||||
<h2 className="text-center text-lg font-medium mb-6 border-b p-5 w-full">
|
||||
Welcome, join your organization by creating a new password
|
||||
</h2>
|
||||
) : (
|
||||
<h2 className="text-center text-2xl font-medium mb-6 border-b p-5 w-full">
|
||||
Reset Password
|
||||
</h2>
|
||||
)}
|
||||
|
||||
<div className="w-full px-8">
|
||||
{creatingNewPassword ? <CreatePassword params={params} /> : <ResetPassword />}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="my-8">
|
||||
<Link to={LOGIN}>
|
||||
<div className="link">{'Back to Login'}</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Copyright />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect((state: any, props: any) => ({
|
||||
params: new URLSearchParams(props.location.search),
|
||||
}))(ForgotPassword);
|
||||
105
frontend/app/components/ForgotPassword/ResetPasswordRequest.tsx
Normal file
105
frontend/app/components/ForgotPassword/ResetPasswordRequest.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import React from 'react';
|
||||
import { Form, Input, Loader, Button, Link, Icon, Message } from 'UI';
|
||||
import ReCAPTCHA from 'react-google-recaptcha';
|
||||
import { connect } from 'react-redux';
|
||||
import { requestResetPassword, resetPassword, resetErrors } from 'Duck/user';
|
||||
|
||||
interface Props {
|
||||
requestResetPassword: Function;
|
||||
}
|
||||
function ResetPasswordRequest(props: Props) {
|
||||
const recaptchaRef = React.createRef();
|
||||
const [requested, setRequested] = React.useState(false);
|
||||
const [email, setEmail] = React.useState('');
|
||||
const [error, setError] = React.useState(null);
|
||||
const CAPTCHA_ENABLED = window.env.CAPTCHA_ENABLED === 'true';
|
||||
const CAPTCHA_SITE_KEY = window.env.CAPTCHA_SITE_KEY;
|
||||
|
||||
const write = (e: any) => {
|
||||
const { name, value } = e.target;
|
||||
if (name === 'email') setEmail(value);
|
||||
};
|
||||
|
||||
const onSubmit = (e: any) => {
|
||||
e.preventDefault();
|
||||
if (CAPTCHA_ENABLED && recaptchaRef.current) {
|
||||
recaptchaRef.current.execute();
|
||||
} else if (!CAPTCHA_ENABLED) {
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (token?: any) => {
|
||||
setError(null);
|
||||
props
|
||||
.requestResetPassword({ email: email.trim(), 'g-recaptcha-response': token })
|
||||
.then((response: any) => {
|
||||
setRequested(true);
|
||||
if (response && response.errors && response.errors.length > 0) {
|
||||
setError(response.errors[0]);
|
||||
} else {
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Form onSubmit={onSubmit} style={{ minWidth: '50%' }} className="flex flex-col">
|
||||
<Loader loading={false}>
|
||||
{CAPTCHA_ENABLED && (
|
||||
<div className="flex justify-center">
|
||||
<ReCAPTCHA
|
||||
ref={recaptchaRef}
|
||||
size="invisible"
|
||||
data-hidden={requested}
|
||||
sitekey={CAPTCHA_SITE_KEY}
|
||||
onChange={(token: any) => handleSubmit(token)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!requested && (
|
||||
<>
|
||||
<Form.Field>
|
||||
<label>{'Email Address'}</label>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
autoComplete="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
name="email"
|
||||
onChange={write}
|
||||
className="w-full"
|
||||
icon="envelope"
|
||||
required
|
||||
/>
|
||||
</Form.Field>
|
||||
<Button type="submit" variant="primary" className="mt-4">
|
||||
Email password reset link
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{requested && !error && (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="w-16 h-16 rounded-full bg-tealx-light flex items-center justify-center mb-2">
|
||||
<Icon name="envelope-check" size={30} color="tealx" />
|
||||
</div>
|
||||
<div>
|
||||
Alright! a reset link was emailed to <span className="font-medium">{email}</span>.
|
||||
Click on it to reset your account password.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="flex items-center flex-col text-center">
|
||||
<div className="w-16 h-16 rounded-full bg-red-lightest flex items-center justify-center mb-2">
|
||||
<Icon name="envelope-x" size="30" color="red" />
|
||||
</div>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</Loader>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect((state: any) => ({}), { requestResetPassword })(ResetPasswordRequest);
|
||||
|
|
@ -21,4 +21,5 @@ export {
|
|||
} from './schedule';
|
||||
export { default } from './filterOptions';
|
||||
// export { default as storageKeys } from './storageKeys';
|
||||
export const ENTERPRISE_REQUEIRED = "This feature requires an enterprise license.";
|
||||
export const ENTERPRISE_REQUEIRED = "This feature requires an enterprise license.";
|
||||
export const PASSWORD_POLICY = "The password should have a minimum length of 8 characters and include at least one uppercase letter, one lowercase letter, one digit, and one special character.";
|
||||
|
|
@ -82,3 +82,9 @@ export function validateNumber(str, options = {}) {
|
|||
if (max && n > max) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export const validatePassword = (password) => {
|
||||
const regex =
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?])[A-Za-z\d!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]{8,}$/;
|
||||
return regex.test(password);
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue