/*! * Copyright (c) 2024 PLANKA Software GmbH * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md */ import isEmail from 'validator/lib/isEmail'; import React, { useCallback, useEffect, useMemo } from 'react'; import classNames from 'classnames'; import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { Button, Divider, Form, Grid, Header, Message } from 'semantic-ui-react'; import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks'; import { Input } from '../../../lib/custom-ui'; import selectors from '../../../selectors'; import entryActions from '../../../entry-actions'; import { useForm, useNestedRef } from '../../../hooks'; import { isUsername } from '../../../utils/validator'; import styles from './Content.module.scss'; const createMessage = (error) => { if (!error) { return error; } switch (error.message) { case 'Invalid credentials': return { type: 'error', content: 'common.invalidCredentials', }; case 'Invalid email or username': return { type: 'error', content: 'common.invalidEmailOrUsername', }; case 'Invalid password': return { type: 'error', content: 'common.invalidPassword', }; case 'Use single sign-on': return { type: 'error', content: 'common.useSingleSignOn', }; case 'Email already in use': return { type: 'error', content: 'common.emailAlreadyInUse', }; case 'Username already in use': return { type: 'error', content: 'common.usernameAlreadyInUse', }; case 'Active users limit reached': return { type: 'error', content: 'common.activeUsersLimitReached', }; case 'Failed to fetch': return { type: 'warning', content: 'common.noInternetConnection', }; case 'Network request failed': return { type: 'warning', content: 'common.serverConnectionFailed', }; default: return { type: 'warning', content: 'common.unknownError', }; } }; const Content = React.memo(() => { const config = useSelector(selectors.selectConfig); const { data: defaultData, isSubmitting, isSubmittingWithOidc, error, } = useSelector(selectors.selectAuthenticateForm); const dispatch = useDispatch(); const [t] = useTranslation(); const wasSubmitting = usePrevious(isSubmitting); const [data, handleFieldChange, setData] = useForm(() => ({ emailOrUsername: '', password: '', ...defaultData, })); const message = useMemo(() => createMessage(error), [error]); const [focusPasswordFieldState, focusPasswordField] = useToggle(); const [emailOrUsernameFieldRef, handleEmailOrUsernameFieldRef] = useNestedRef('inputRef'); const [passwordFieldRef, handlePasswordFieldRef] = useNestedRef('inputRef'); const handleSubmit = useCallback(() => { const cleanData = { ...data, emailOrUsername: data.emailOrUsername.trim(), }; if (!isEmail(cleanData.emailOrUsername) && !isUsername(cleanData.emailOrUsername)) { emailOrUsernameFieldRef.current.select(); return; } if (!cleanData.password) { passwordFieldRef.current.focus(); return; } dispatch(entryActions.authenticate(cleanData)); }, [dispatch, data, emailOrUsernameFieldRef, passwordFieldRef]); const handleAuthenticateWithOidcClick = useCallback(() => { dispatch(entryActions.authenticateWithOidc()); }, [dispatch]); const handleMessageDismiss = useCallback(() => { dispatch(entryActions.clearAuthenticateError()); }, [dispatch]); const withOidc = !!config.oidc; const isOidcEnforced = withOidc && config.oidc.isEnforced; useEffect(() => { if (!isOidcEnforced) { emailOrUsernameFieldRef.current.focus(); } }, [emailOrUsernameFieldRef, isOidcEnforced]); useDidUpdate(() => { if (wasSubmitting && !isSubmitting && error) { switch (error.message) { case 'Invalid credentials': case 'Invalid email or username': emailOrUsernameFieldRef.current.select(); break; case 'Invalid password': setData((prevData) => ({ ...prevData, password: '', })); focusPasswordField(); break; default: } } }, [isSubmitting, wasSubmitting, error]); useDidUpdate(() => { passwordFieldRef.current.focus(); }, [focusPasswordFieldState]); return (
{t('common.poweredByPlanka')}