* refactor(searchStore): reformat filterMap function parameters (#3166) - Reformat the parameters of the filterMap function for better readability. - Comment out the fetchSessions call in clearSearch method to avoid unnecessary session fetch. * Increment frontend chart version (#3167) Co-authored-by: GitHub Action <action@github.com> * refactor(chalice): cleaned code fix(chalice): fixed session-search-pg sortKey issue fix(chalice): fixed CH-query-formatter to handle special chars fix(chalice): fixed /ids response * feat(auth): implement withCaptcha HOC for consistent reCAPTCHA This commit refactors the reCAPTCHA implementation across the application by introducing a Higher Order Component (withCaptcha) that encapsulates captcha verification logic. The changes: - Create a reusable withCaptcha HOC in withRecaptcha.tsx - Refactor Login, ResetPasswordRequest, and CreatePassword components - Extract SSOLogin into a separate component - Improve error handling and user feedback - Standardize loading and verification states across forms - Make captcha implementation more maintainable and consistent --------- Co-authored-by: Mehdi Osman <estradino@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Taha Yassine Kraiem <tahayk2@gmail.com>
178 lines
5.4 KiB
TypeScript
178 lines
5.4 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { observer } from 'mobx-react-lite';
|
|
import { useStore } from 'App/mstore';
|
|
import { Form, Input, Loader, Icon, Message } from 'UI';
|
|
import { Button } from 'antd';
|
|
import { validatePassword } from 'App/validate';
|
|
import { PASSWORD_POLICY } from 'App/constants';
|
|
import { useTranslation } from 'react-i18next';
|
|
import withCaptcha, { WithCaptchaProps } from 'App/withRecaptcha';
|
|
|
|
const ERROR_DONT_MATCH = (t) => t("Passwords don't match.");
|
|
|
|
interface Props {
|
|
params: any;
|
|
}
|
|
|
|
function CreatePassword(props: Props & WithCaptchaProps) {
|
|
const { t } = useTranslation();
|
|
const { params } = props;
|
|
const { userStore } = useStore();
|
|
const { loading } = userStore;
|
|
const { resetPassword } = userStore;
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [validationError, setValidationError] = useState<string | null>(null);
|
|
const [updated, setUpdated] = useState(false);
|
|
const [passwordRepeat, setPasswordRepeat] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
|
|
const pass = params.get('pass');
|
|
const invitation = params.get('invitation');
|
|
|
|
const { submitWithCaptcha, isVerifyingCaptcha, resetCaptcha } = props;
|
|
|
|
const handleSubmit = (token?: string) => {
|
|
if (!validatePassword(password) || !token) {
|
|
return;
|
|
}
|
|
|
|
resetPassword({
|
|
invitation,
|
|
pass,
|
|
password,
|
|
'g-recaptcha-response': token
|
|
})
|
|
.then(() => {
|
|
setUpdated(true);
|
|
})
|
|
.catch((err) => {
|
|
setError(err.message);
|
|
// Reset captcha for the next attempt
|
|
resetCaptcha();
|
|
});
|
|
};
|
|
|
|
const onSubmit = () => {
|
|
// Validate before attempting captcha verification
|
|
if (!validatePassword(password) || password !== passwordRepeat) {
|
|
setValidationError(
|
|
password !== passwordRepeat
|
|
? ERROR_DONT_MATCH(t)
|
|
: PASSWORD_POLICY(t)
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Reset any previous errors
|
|
setError(null);
|
|
setValidationError(null);
|
|
|
|
submitWithCaptcha({ pass, invitation, password })
|
|
.then((data) => {
|
|
handleSubmit(data['g-recaptcha-response']);
|
|
})
|
|
.catch((error) => {
|
|
console.error('Captcha verification failed:', error);
|
|
// The component will handle showing appropriate messages
|
|
});
|
|
};
|
|
|
|
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(t));
|
|
} else if (passwordRepeat.length > 0 && !validatePassword(password)) {
|
|
setValidationError(PASSWORD_POLICY(t));
|
|
} else {
|
|
setValidationError(null);
|
|
}
|
|
}, [passwordRepeat, password, t]);
|
|
|
|
return (
|
|
<Form
|
|
onSubmit={onSubmit}
|
|
style={{ minWidth: '50%' }}
|
|
className="flex flex-col items-center justify-center"
|
|
>
|
|
{!error && (
|
|
<>
|
|
<Loader loading={loading || isVerifyingCaptcha}>
|
|
<div data-hidden={updated} className="w-full">
|
|
<Form.Field>
|
|
<label>{t('New password')}</label>
|
|
<Input
|
|
autoComplete="new-password"
|
|
type="password"
|
|
placeholder={t('Type here...')}
|
|
name="password"
|
|
onChange={write}
|
|
className="w-full"
|
|
icon="key"
|
|
required
|
|
/>
|
|
</Form.Field>
|
|
<Form.Field>
|
|
<label>{t('Confirm password')}</label>
|
|
<Input
|
|
autoComplete="new-password"
|
|
type="password"
|
|
placeholder={t('Re-enter your new password')}
|
|
name="passwordRepeat"
|
|
onChange={write}
|
|
className="w-full"
|
|
icon="key"
|
|
required
|
|
/>
|
|
</Form.Field>
|
|
</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>{t('Your password has been updated successfully.')}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{validationError && <Message error>{validationError}</Message>}
|
|
|
|
{updated ? null : (
|
|
<Button
|
|
htmlType="submit"
|
|
type="primary"
|
|
loading={loading || isVerifyingCaptcha}
|
|
disabled={loading || isVerifyingCaptcha || validationError !== null}
|
|
className="w-full mt-4"
|
|
>
|
|
{isVerifyingCaptcha
|
|
? t('Verifying...')
|
|
: loading
|
|
? t('Processing...')
|
|
: t('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 withCaptcha(observer(CreatePassword));
|