Refactor registration and login and improve routing
Changes:
- Improve registration and login pages
- Extract Home component from App.tsx file into its own
- Make Home component display registration or login
- Extract routes from App component and refactor routing
GitLab: #12
Change-Id: I68b01890781308282072b6dcf5e6df0d54837b4a
diff --git a/client/src/pages/JamiRegistration.tsx b/client/src/pages/JamiRegistration.tsx
new file mode 100644
index 0000000..ebfc5fd
--- /dev/null
+++ b/client/src/pages/JamiRegistration.tsx
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Box, Button, Stack, Typography, useMediaQuery } from '@mui/material';
+import { Theme, useTheme } from '@mui/material/styles';
+import { ChangeEvent, FormEvent, MouseEvent, useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Form } from 'react-router-dom';
+
+import { PasswordInput, UsernameInput } from '../components/Input';
+import ProcessingRequest from '../components/ProcessingRequest';
+import { checkPasswordStrength, isNameRegistered, StrengthValueCode } from '../utils/auth';
+import { inputWidth, jamiUsernamePattern } from '../utils/constants';
+
+const usernameTooltipTitle =
+ 'Choose a password hard to guess for others but easy to remember for you, ' +
+ 'it must be at least 8 characters. ' +
+ "Your account won't be recovered if you forget it!\n\n" +
+ 'Click for more details';
+
+const passwordTooltipTitle =
+ 'Username may be from 3 to 32 chraracters long and contain a-z, A-Z, -, _\n\n' + 'Click for more details';
+
+type NameStatus = 'default' | 'success' | 'taken' | 'invalid' | 'registration_failed';
+type PasswordStatus = StrengthValueCode | 'registration_failed';
+
+type JamiRegistrationProps = {
+ login: () => void;
+};
+
+export default function JamiRegistration(props: JamiRegistrationProps) {
+ const theme: Theme = useTheme();
+ const { t } = useTranslation();
+
+ const [isCreatingUser, setIsCreatingUser] = useState(false);
+ const [usernameValue, setUsernameValue] = useState('');
+ const [passwordValue, setPasswordValue] = useState('');
+ const [usernameStatus, setUsernameStatus] = useState<NameStatus>('default');
+ const [passwordStatus, setPasswordStatus] = useState<PasswordStatus>('default');
+
+ const usernameError = usernameStatus !== 'success' && usernameStatus !== 'default';
+ const usernameSuccess = usernameStatus === 'success';
+ const passwordError = passwordStatus !== 'strong' && passwordStatus !== 'default';
+ const passwordSuccess = passwordStatus === 'strong';
+
+ useEffect(() => {
+ // To prevent lookup if field is empty, in error state or lookup already done
+ if (usernameValue.length > 0 && usernameStatus === 'default') {
+ const validateUsername = async () => {
+ if (await isNameRegistered(usernameValue)) {
+ setUsernameStatus('taken');
+ } else {
+ setUsernameStatus('success');
+ }
+ };
+ const timeout = setTimeout(validateUsername, 1000);
+
+ return () => clearTimeout(timeout);
+ }
+ }, [usernameValue, usernameStatus]);
+
+ const handleUsername = async (event: ChangeEvent<HTMLInputElement>) => {
+ const username: string = event.target.value;
+ setUsernameValue(username);
+
+ if (username.length > 0 && !jamiUsernamePattern.test(username)) {
+ setUsernameStatus('invalid');
+ } else {
+ setUsernameStatus('default');
+ }
+ };
+
+ const handlePassword = (event: ChangeEvent<HTMLInputElement>) => {
+ const password: string = event.target.value;
+ setPasswordValue(password);
+
+ if (password.length > 0) {
+ const checkResult = checkPasswordStrength(password);
+ setPasswordStatus(checkResult.valueCode);
+ } else {
+ setPasswordStatus('default');
+ }
+ };
+
+ const login = (event: MouseEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ props.login();
+ };
+
+ const handleSubmit = async (event: FormEvent) => {
+ event.preventDefault();
+ const canCreate = usernameSuccess && passwordSuccess;
+
+ if (canCreate) {
+ setIsCreatingUser(true);
+ // TODO: Replace with registration logic (https://git.jami.net/savoirfairelinux/jami-web/-/issues/75).
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ console.log('Account created');
+ setIsCreatingUser(false);
+ } else {
+ if (usernameError || usernameValue.length === 0) {
+ setUsernameStatus('registration_failed');
+ }
+ if (!passwordSuccess) {
+ setPasswordStatus('registration_failed');
+ }
+ }
+ };
+
+ const isMobile: boolean = useMediaQuery(theme.breakpoints.only('xs'));
+
+ return (
+ <>
+ <ProcessingRequest open={isCreatingUser} />
+
+ <Stack
+ sx={{
+ minHeight: `${isMobile ? 'auto' : '100%'}`,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}
+ >
+ <Box sx={{ mt: theme.typography.pxToRem(50), mb: theme.typography.pxToRem(20) }}>
+ <Typography component={'span'} variant="h2">
+ REGISTRATION
+ </Typography>
+ </Box>
+
+ <Form method="post" id="register-form">
+ <div>
+ <UsernameInput
+ value={usernameValue}
+ onChange={handleUsername}
+ error={usernameError}
+ success={usernameSuccess}
+ helperText={t(`username_input_${usernameStatus}_helper_text`)}
+ sx={{ width: theme.typography.pxToRem(inputWidth) }}
+ tooltipTitle={usernameTooltipTitle}
+ />
+ </div>
+ <div>
+ <PasswordInput
+ value={passwordValue}
+ onChange={handlePassword}
+ error={passwordError}
+ success={passwordSuccess}
+ helperText={t(`password_input_${passwordStatus}_helper_text`)}
+ sx={{ width: theme.typography.pxToRem(inputWidth) }}
+ tooltipTitle={passwordTooltipTitle}
+ />
+ </div>
+
+ <Button
+ variant="contained"
+ type="submit"
+ onClick={handleSubmit}
+ sx={{ width: theme.typography.pxToRem(inputWidth), mt: theme.typography.pxToRem(20) }}
+ >
+ REGISTER
+ </Button>
+ </Form>
+
+ <Box sx={{ mt: theme.typography.pxToRem(50), mb: theme.typography.pxToRem(50) }}>
+ <Typography variant="body1">
+ Already have an account ?
+ <a href="" onClick={login}>
+ LOG IN
+ </a>
+ </Typography>
+ </Box>
+ </Stack>
+ </>
+ );
+}