From 2be5ec26f8a083faa489631ffc9cfee8b044d3d1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 19 Oct 2023 13:42:12 +1100 Subject: [PATCH] [React] Login improvements (#5752) * Logout user if user query fails * login / logout improvements - Add timeout to request (otherwise hangs indefinitely) * Improve "checking login" page * Update login form - Disable button if logging in - Show loader * Fixed unused vars --- .../components/forms/AuthenticationForm.tsx | 24 +++++++++++++++---- src/frontend/src/functions/auth.tsx | 20 ++++++++++------ src/frontend/src/pages/Auth/Logged-In.tsx | 19 +++++++++++---- src/frontend/src/states/UserState.tsx | 11 ++++++--- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx index 7bedd2e012..53396bed83 100644 --- a/src/frontend/src/components/forms/AuthenticationForm.tsx +++ b/src/frontend/src/components/forms/AuthenticationForm.tsx @@ -3,6 +3,7 @@ import { Anchor, Button, Group, + Loader, Paper, PasswordInput, Stack, @@ -13,6 +14,7 @@ import { useForm } from '@mantine/form'; import { useDisclosure } from '@mantine/hooks'; import { notifications } from '@mantine/notifications'; import { IconCheck } from '@tabler/icons-react'; +import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { doClassicLogin, doSimpleLogin } from '../../functions/auth'; @@ -25,12 +27,18 @@ export function AuthenticationForm() { const [classicLoginMode, setMode] = useDisclosure(true); const navigate = useNavigate(); + const [isLoggingIn, setIsLoggingIn] = useState(false); + function handleLogin() { + setIsLoggingIn(true); + if (classicLoginMode === true) { doClassicLogin( classicForm.values.username, classicForm.values.password ).then((ret) => { + setIsLoggingIn(false); + if (ret === false) { notifications.show({ title: t`Login failed`, @@ -49,6 +57,8 @@ export function AuthenticationForm() { }); } else { doSimpleLogin(simpleForm.values.email).then((ret) => { + setIsLoggingIn(false); + if (ret?.status === 'ok') { notifications.show({ title: t`Mail delivery successful`, @@ -126,11 +136,17 @@ export function AuthenticationForm() { I will use username and password )} - diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index c688761d88..d768138720 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -20,7 +20,8 @@ export const doClassicLogin = async (username: string, password: string) => { const token = await axios .get(apiUrl(ApiPaths.user_token), { auth: { username, password }, - baseURL: host.toString() + baseURL: host.toString(), + timeout: 5000 }) .then((response) => response.data.token) .catch((error) => { @@ -62,7 +63,7 @@ export const doSimpleLogin = async (email: string) => { email: email }) .then((response) => response.data) - .catch((error) => { + .catch((_error) => { return false; }); return mail; @@ -107,9 +108,14 @@ export function handleReset(navigate: any, values: { email: string }) { }); } -export function checkLoginState(navigate: any) { +/** + * Check login state, and redirect the user as required + */ +export function checkLoginState(navigate: any, redirect?: string) { api - .get(apiUrl(ApiPaths.user_token)) + .get(apiUrl(ApiPaths.user_token), { + timeout: 5000 + }) .then((val) => { if (val.status === 200 && val.data.token) { doTokenLogin(val.data.token); @@ -120,13 +126,13 @@ export function checkLoginState(navigate: any) { color: 'green', icon: }); - - navigate('/home'); + navigate(redirect ?? '/home'); } else { navigate('/login'); } }) - .catch(() => { + .catch((error) => { + console.error('Error fetching login information:', error); navigate('/login'); }); } diff --git a/src/frontend/src/pages/Auth/Logged-In.tsx b/src/frontend/src/pages/Auth/Logged-In.tsx index e6a6039873..24f9465855 100644 --- a/src/frontend/src/pages/Auth/Logged-In.tsx +++ b/src/frontend/src/pages/Auth/Logged-In.tsx @@ -1,5 +1,5 @@ import { Trans } from '@lingui/macro'; -import { Text } from '@mantine/core'; +import { Card, Container, Group, Loader, Stack, Text } from '@mantine/core'; import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -14,9 +14,20 @@ export default function Logged_In() { return ( <> - - Checking if you are already logged in - + + + + + + Checking if you are already logged in + + + + + + + + ); } diff --git a/src/frontend/src/states/UserState.tsx b/src/frontend/src/states/UserState.tsx index ef3f4f5339..d8edeee061 100644 --- a/src/frontend/src/states/UserState.tsx +++ b/src/frontend/src/states/UserState.tsx @@ -1,6 +1,7 @@ import { create } from 'zustand'; import { api } from '../App'; +import { doClassicLogout } from '../functions/auth'; import { ApiPaths, apiUrl } from './ApiState'; import { UserProps } from './states'; @@ -19,17 +20,19 @@ export const useUserState = create((set, get) => ({ username: () => { const user: UserProps = get().user as UserProps; - if (user.first_name || user.last_name) { + if (user?.first_name || user?.last_name) { return `${user.first_name} ${user.last_name}`.trim(); } else { - return user.username; + return user?.username ?? ''; } }, setUser: (newUser: UserProps) => set({ user: newUser }), fetchUserState: async () => { // Fetch user data await api - .get(apiUrl(ApiPaths.user_me)) + .get(apiUrl(ApiPaths.user_me), { + timeout: 5000 + }) .then((response) => { const user: UserProps = { first_name: response.data?.first_name ?? '', @@ -41,6 +44,8 @@ export const useUserState = create((set, get) => ({ }) .catch((error) => { console.error('Error fetching user data:', error); + // Redirect to login page + doClassicLogout(); }); // Fetch role data