blob: ebfc5fd89f52f18b9daf1c5551977abe2e672b07 [file] [log] [blame]
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -04001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
18import { Box, Button, Stack, Typography, useMediaQuery } from '@mui/material';
19import { Theme, useTheme } from '@mui/material/styles';
20import { ChangeEvent, FormEvent, MouseEvent, useEffect, useState } from 'react';
21import { useTranslation } from 'react-i18next';
22import { Form } from 'react-router-dom';
23
24import { PasswordInput, UsernameInput } from '../components/Input';
25import ProcessingRequest from '../components/ProcessingRequest';
26import { checkPasswordStrength, isNameRegistered, StrengthValueCode } from '../utils/auth';
27import { inputWidth, jamiUsernamePattern } from '../utils/constants';
28
29const usernameTooltipTitle =
30 'Choose a password hard to guess for others but easy to remember for you, ' +
31 'it must be at least 8 characters. ' +
32 "Your account won't be recovered if you forget it!\n\n" +
33 'Click for more details';
34
35const passwordTooltipTitle =
36 'Username may be from 3 to 32 chraracters long and contain a-z, A-Z, -, _\n\n' + 'Click for more details';
37
38type NameStatus = 'default' | 'success' | 'taken' | 'invalid' | 'registration_failed';
39type PasswordStatus = StrengthValueCode | 'registration_failed';
40
41type JamiRegistrationProps = {
42 login: () => void;
43};
44
45export default function JamiRegistration(props: JamiRegistrationProps) {
46 const theme: Theme = useTheme();
47 const { t } = useTranslation();
48
49 const [isCreatingUser, setIsCreatingUser] = useState(false);
50 const [usernameValue, setUsernameValue] = useState('');
51 const [passwordValue, setPasswordValue] = useState('');
52 const [usernameStatus, setUsernameStatus] = useState<NameStatus>('default');
53 const [passwordStatus, setPasswordStatus] = useState<PasswordStatus>('default');
54
55 const usernameError = usernameStatus !== 'success' && usernameStatus !== 'default';
56 const usernameSuccess = usernameStatus === 'success';
57 const passwordError = passwordStatus !== 'strong' && passwordStatus !== 'default';
58 const passwordSuccess = passwordStatus === 'strong';
59
60 useEffect(() => {
61 // To prevent lookup if field is empty, in error state or lookup already done
62 if (usernameValue.length > 0 && usernameStatus === 'default') {
63 const validateUsername = async () => {
64 if (await isNameRegistered(usernameValue)) {
65 setUsernameStatus('taken');
66 } else {
67 setUsernameStatus('success');
68 }
69 };
70 const timeout = setTimeout(validateUsername, 1000);
71
72 return () => clearTimeout(timeout);
73 }
74 }, [usernameValue, usernameStatus]);
75
76 const handleUsername = async (event: ChangeEvent<HTMLInputElement>) => {
77 const username: string = event.target.value;
78 setUsernameValue(username);
79
80 if (username.length > 0 && !jamiUsernamePattern.test(username)) {
81 setUsernameStatus('invalid');
82 } else {
83 setUsernameStatus('default');
84 }
85 };
86
87 const handlePassword = (event: ChangeEvent<HTMLInputElement>) => {
88 const password: string = event.target.value;
89 setPasswordValue(password);
90
91 if (password.length > 0) {
92 const checkResult = checkPasswordStrength(password);
93 setPasswordStatus(checkResult.valueCode);
94 } else {
95 setPasswordStatus('default');
96 }
97 };
98
99 const login = (event: MouseEvent<HTMLAnchorElement>) => {
100 event.preventDefault();
101 props.login();
102 };
103
104 const handleSubmit = async (event: FormEvent) => {
105 event.preventDefault();
106 const canCreate = usernameSuccess && passwordSuccess;
107
108 if (canCreate) {
109 setIsCreatingUser(true);
110 // TODO: Replace with registration logic (https://git.jami.net/savoirfairelinux/jami-web/-/issues/75).
111 await new Promise((resolve) => setTimeout(resolve, 2000));
112 console.log('Account created');
113 setIsCreatingUser(false);
114 } else {
115 if (usernameError || usernameValue.length === 0) {
116 setUsernameStatus('registration_failed');
117 }
118 if (!passwordSuccess) {
119 setPasswordStatus('registration_failed');
120 }
121 }
122 };
123
124 const isMobile: boolean = useMediaQuery(theme.breakpoints.only('xs'));
125
126 return (
127 <>
128 <ProcessingRequest open={isCreatingUser} />
129
130 <Stack
131 sx={{
132 minHeight: `${isMobile ? 'auto' : '100%'}`,
133 display: 'flex',
134 alignItems: 'center',
135 justifyContent: 'center',
136 }}
137 >
138 <Box sx={{ mt: theme.typography.pxToRem(50), mb: theme.typography.pxToRem(20) }}>
139 <Typography component={'span'} variant="h2">
140 REGISTRATION
141 </Typography>
142 </Box>
143
144 <Form method="post" id="register-form">
145 <div>
146 <UsernameInput
147 value={usernameValue}
148 onChange={handleUsername}
149 error={usernameError}
150 success={usernameSuccess}
151 helperText={t(`username_input_${usernameStatus}_helper_text`)}
152 sx={{ width: theme.typography.pxToRem(inputWidth) }}
153 tooltipTitle={usernameTooltipTitle}
154 />
155 </div>
156 <div>
157 <PasswordInput
158 value={passwordValue}
159 onChange={handlePassword}
160 error={passwordError}
161 success={passwordSuccess}
162 helperText={t(`password_input_${passwordStatus}_helper_text`)}
163 sx={{ width: theme.typography.pxToRem(inputWidth) }}
164 tooltipTitle={passwordTooltipTitle}
165 />
166 </div>
167
168 <Button
169 variant="contained"
170 type="submit"
171 onClick={handleSubmit}
172 sx={{ width: theme.typography.pxToRem(inputWidth), mt: theme.typography.pxToRem(20) }}
173 >
174 REGISTER
175 </Button>
176 </Form>
177
178 <Box sx={{ mt: theme.typography.pxToRem(50), mb: theme.typography.pxToRem(50) }}>
179 <Typography variant="body1">
180 Already have an account ? &nbsp;
181 <a href="" onClick={login}>
182 LOG IN
183 </a>
184 </Typography>
185 </Box>
186 </Stack>
187 </>
188 );
189}