* feat: prevent user form login before conforming his email * chore: update files and improve things * feat: fix errors issue and improve logs
315 lines
11 KiB
TypeScript
315 lines
11 KiB
TypeScript
import React, {
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useState
|
|
} from 'react';
|
|
import {
|
|
Alert,
|
|
Button,
|
|
Card,
|
|
Col,
|
|
Container,
|
|
Form,
|
|
Row
|
|
} from 'react-bootstrap';
|
|
import { Link, useNavigate, useSearchParams } from 'react-router';
|
|
import { useTranslation } from 'react-i18next';
|
|
import AuthContext from '../../context/AuthContext';
|
|
import { translateServerResponse } from '../../utils/TranslatorUtils';
|
|
import { handleDefaultLang } from '../../lang-service/LangHandler';
|
|
import FormInput from '../FormInput';
|
|
import api from '../../api-service/api';
|
|
import ApiConfig from '../../api-service/apiConfig';
|
|
|
|
/**
|
|
* @returns {React.ReactNode} The form component for Login and Register pages.
|
|
*/
|
|
function LoginForm({ prefix }: { prefix: string }): React.ReactNode {
|
|
const { signIn, register } = useContext(AuthContext);
|
|
const { i18n, t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const [searchParams] = useSearchParams();
|
|
const [validated, setValidated] = useState<boolean>(false);
|
|
const [formInvalid, setFormInvalid] = useState<boolean>(false);
|
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
|
const [successMessage, setSuccessMessage] = useState<string>('');
|
|
const [showResend, setShowResend] = useState<boolean>(false);
|
|
const [email, setEmail] = useState<string>('');
|
|
const [password, setPassword] = useState<string>('');
|
|
const [passwordAgain, setPasswordAgain] = useState<string>('');
|
|
const [secondsLeft, setSecondsLeft] = useState<number>(0);
|
|
const [isResendEnabled, setIsResendEnabled] = useState(true);
|
|
const [langToSave, setLangToSave] = useState<string>('');
|
|
|
|
/**
|
|
* Navigates to the specified page.
|
|
*
|
|
* @param {string} page - The page to navigate to.
|
|
*/
|
|
const goTo = useCallback((page: string) => {
|
|
navigate(page);
|
|
}, []);
|
|
|
|
/**
|
|
* Handles the form submit button click event.
|
|
*
|
|
* When clicked, checks if the form is valid. If it's not, raise an error
|
|
* message in an Alert. Otherwise, it calls the signIn API and navigates to the home page.
|
|
*/
|
|
const handleSubmit = async (event: React.SubmitEvent<HTMLFormElement>): Promise<void> => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
setValidated(true);
|
|
|
|
const form = event.currentTarget;
|
|
if (!form.checkValidity()) {
|
|
setFormInvalid(true);
|
|
if (prefix === 'complete_reset') {
|
|
setErrorMessage(translateServerResponse('Please fill in the new password', i18n.language));
|
|
}
|
|
else if (prefix !== 'reset') {
|
|
setErrorMessage(translateServerResponse('Please fill in your username and password!' + prefix, i18n.language));
|
|
}
|
|
else {
|
|
setErrorMessage(translateServerResponse('Please fill in your email', i18n.language));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (password !== passwordAgain && prefix === 'register') {
|
|
setFormInvalid(true);
|
|
setErrorMessage(translateServerResponse('Please fill in your username and password!', i18n.language));
|
|
}
|
|
|
|
setFormInvalid(false);
|
|
setShowResend(false);
|
|
try {
|
|
if (prefix === 'login') {
|
|
await signIn(email, password);
|
|
goTo('/home');
|
|
}
|
|
else if (prefix === 'register') {
|
|
await register({ email, password, passwordAgain, lang: langToSave });
|
|
// Do not clear the email, because user might request to resend
|
|
setPassword('');
|
|
setPasswordAgain('');
|
|
setSuccessMessage(translateServerResponse('Please confirm your email before proceeding', i18n.language));
|
|
setShowResend(true);
|
|
setSecondsLeft(30);
|
|
setIsResendEnabled(false);
|
|
}
|
|
else if (prefix === 'reset') {
|
|
await api.postJSON(ApiConfig.resetPwdUrl, { email });
|
|
setSuccessMessage(translateServerResponse('If the email address you entered is associated with an account, you will receive a password reset link shortly.', i18n.language));
|
|
}
|
|
else if (prefix === 'complete_reset') {
|
|
const token = searchParams.get('token');
|
|
await api.postJSON(ApiConfig.completeResetPwdUrl, { token, password, passwordAgain });
|
|
goTo('/home');
|
|
}
|
|
}
|
|
catch (e) {
|
|
setFormInvalid(true);
|
|
let msg = '';
|
|
if (typeof e === 'string') {
|
|
console.log('string');
|
|
msg = e;
|
|
}
|
|
else if (e instanceof Error) {
|
|
msg = e.message;
|
|
}
|
|
|
|
setErrorMessage(translateServerResponse(msg, i18n.language));
|
|
if (msg === 'Email not confirmed!') {
|
|
setShowResend(true);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleResend = async () => {
|
|
try {
|
|
await api.postJSON(ApiConfig.resendConfirmUrl, { email });
|
|
|
|
setSuccessMessage(translateServerResponse('Confirmation email re-sent! Please check the spam folder.', i18n.language));
|
|
setFormInvalid(false);
|
|
setShowResend(true);
|
|
setSecondsLeft(30);
|
|
setIsResendEnabled(false);
|
|
}
|
|
catch (e) {
|
|
setFormInvalid(true);
|
|
if (typeof e === 'string') {
|
|
setErrorMessage(translateServerResponse(e, i18n.language));
|
|
}
|
|
else if (e instanceof Error) {
|
|
setErrorMessage(translateServerResponse(e.message, i18n.language));
|
|
}
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const lang = handleDefaultLang();
|
|
setLangToSave(lang);
|
|
}, [formInvalid]);
|
|
|
|
useEffect(() => {
|
|
if (secondsLeft > 0) {
|
|
const timer = setTimeout(() => setSecondsLeft(prev => prev - 1), 1000);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
else {
|
|
setIsResendEnabled(true);
|
|
}
|
|
}, [secondsLeft]);
|
|
|
|
return (
|
|
<Container as="main" fluid className="login-page">
|
|
<Row className="justify-content-center w-100">
|
|
<Col xs={12} md={6} lg={4}>
|
|
<Card>
|
|
<Card.Body className="p-4">
|
|
<h2 className="text-center">{t(`${prefix}_title`)}</h2>
|
|
|
|
{formInvalid
|
|
? (
|
|
<>
|
|
<Alert variant="danger">{errorMessage}</Alert>
|
|
{showResend && (
|
|
<div className="text-center mt-2">
|
|
<Button
|
|
variant="outline-secondary"
|
|
size="sm"
|
|
onClick={handleResend}
|
|
disabled={!isResendEnabled}
|
|
>
|
|
{isResendEnabled
|
|
? `${t('register_resent_email')}`
|
|
: `${t('register_resent_email_secs')} ${secondsLeft}`}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
: null}
|
|
|
|
{successMessage.length > 1 && (
|
|
<>
|
|
<Alert variant="success">
|
|
{ successMessage }
|
|
</Alert>
|
|
|
|
{showResend && (
|
|
<div className="text-center mb-3">
|
|
<Button
|
|
variant="outline-secondary"
|
|
onClick={handleResend}
|
|
disabled={!isResendEnabled}
|
|
>
|
|
{isResendEnabled
|
|
? `${t('register_resent_email')}`
|
|
: `${t('register_resent_email_secs')} ${secondsLeft}`}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
<Form noValidate validated={validated} onSubmit={handleSubmit}>
|
|
{prefix !== 'complete_reset' && (
|
|
<FormInput
|
|
labelText={t(`${prefix}_email_label`)}
|
|
iconName="At"
|
|
required={true}
|
|
name="email"
|
|
placeholder={t(`${prefix}_email_placeholder`)}
|
|
dataTestId={`${prefix}_email_input`}
|
|
value={email}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setEmail(e.target.value);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{prefix !== 'reset' && (
|
|
<FormInput
|
|
labelText={t(`${prefix}_password_label`)}
|
|
iconName="Lock"
|
|
required={true}
|
|
type="password"
|
|
name="password"
|
|
placeholder={t(`${prefix}_password_placeholder`)}
|
|
value={password}
|
|
pwdShowText={t('password_show_txt')}
|
|
pwdHideText={t('password_hide_txt')}
|
|
pwdHelperTxt={t('password_helper')}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setPassword(e.target.value);
|
|
}}
|
|
dataTestId="account-password-login"
|
|
/>
|
|
)}
|
|
|
|
{(prefix === 'register' || prefix === 'complete_reset') && (
|
|
<FormInput
|
|
labelText={t('register_password_repeat_label')}
|
|
iconName="Lock"
|
|
required={true}
|
|
type="password"
|
|
name="passwordAgain"
|
|
placeholder={t('login_password_placeholder')}
|
|
value={passwordAgain}
|
|
pwdShowText={t('password_show_txt')}
|
|
pwdHideText={t('password_hide_txt')}
|
|
pwdHelperTxt={t('password_helper')}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setPasswordAgain(e.target.value);
|
|
}}
|
|
dataTestId={`account-repeat-password-${prefix}`}
|
|
/>
|
|
)}
|
|
|
|
<Button
|
|
variant="primary"
|
|
type="submit"
|
|
className="w-100"
|
|
>
|
|
{t(`${prefix}_btn_submit`)}
|
|
</Button>
|
|
</Form>
|
|
|
|
<div className="text-center mt-3">
|
|
{t(`${prefix}_account`)}
|
|
<Link
|
|
to={prefix === 'login' ? '/register' : '/login'}
|
|
className="text-decoration-none ms-2"
|
|
>
|
|
{t(`${prefix}_go_other`)}
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="text-center mt-3">
|
|
<Link to="/" className="text-decoration-none">
|
|
{t(`${prefix}_back_home`)}
|
|
</Link>
|
|
</div>
|
|
<div className="text-center mt-3">
|
|
{prefix === 'login' && (
|
|
<Link
|
|
to="/reset-password"
|
|
className="text-decoration-none ms-2"
|
|
>
|
|
{t('login_forgot_password')}
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</Card.Body>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
export default LoginForm;
|