blob: e85136a98235f05fbf325f2e9d95dc4c1a2b6a4c [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 */
Gabriel Rochon7057b4f2022-11-21 13:28:01 -050018import {
19 Box,
20 Button,
21 FormControl,
22 FormControlLabel,
23 Radio,
24 RadioGroup,
25 Stack,
26 Typography,
27 useMediaQuery,
28} from '@mui/material';
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -040029import { Theme, useTheme } from '@mui/material/styles';
Ziwei Wang9b4e2c12023-02-06 14:45:37 -050030import { HttpStatusCode } from 'jami-web-common';
Ziwei Wang3ce1ac02023-02-03 11:59:03 -050031import { ChangeEvent, FormEvent, ReactNode, useEffect, useState } from 'react';
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -040032import { useTranslation } from 'react-i18next';
Ziwei Wang3ce1ac02023-02-03 11:59:03 -050033import { Form, Link, useNavigate } from 'react-router-dom';
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -040034
Michelle Sepkap Simee580f422022-10-31 23:27:04 -040035import { AlertSnackbar } from '../components/AlertSnackbar';
simonab4eec82022-11-08 20:32:43 -050036import { NameStatus, PasswordInput, PasswordStatus, UsernameInput } from '../components/Input';
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -040037import ProcessingRequest from '../components/ProcessingRequest';
Ziwei Wang3ce1ac02023-02-03 11:59:03 -050038import withAuthUI from '../components/WithAuthUI';
Ziwei Wang9b4e2c12023-02-06 14:45:37 -050039import {
40 checkIfUserameIsRegistered,
41 checkPasswordStrength,
42 loginUser,
43 registerUser,
44 setAccessToken,
45} from '../utils/auth';
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -040046import { inputWidth, jamiUsernamePattern } from '../utils/constants';
Ziwei Wang3ce1ac02023-02-03 11:59:03 -050047function RegistrationForm() {
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -040048 const theme: Theme = useTheme();
Michelle Sepkap Simee580f422022-10-31 23:27:04 -040049 const navigate = useNavigate();
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -040050 const { t } = useTranslation();
51
Ziwei Wang9b4e2c12023-02-06 14:45:37 -050052 const [loading, setLoading] = useState(false);
Michelle Sepkap Simee580f422022-10-31 23:27:04 -040053
Ziwei Wang9b4e2c12023-02-06 14:45:37 -050054 const [username, setUsername] = useState<string>('');
55 const [password, setPassword] = useState<string>('');
56 const [isJams, setIsJams] = useState<boolean>(false);
Michelle Sepkap Simee580f422022-10-31 23:27:04 -040057
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -040058 const [usernameStatus, setUsernameStatus] = useState<NameStatus>('default');
59 const [passwordStatus, setPasswordStatus] = useState<PasswordStatus>('default');
60
Michelle Sepkap Simee580f422022-10-31 23:27:04 -040061 const [errorAlertContent, setErrorAlertContent] = useState<ReactNode>(undefined);
62 const [successAlertContent, setSuccessAlertContent] = useState<ReactNode>(undefined);
63
Ziwei Wang9b4e2c12023-02-06 14:45:37 -050064 const isMobile: boolean = useMediaQuery(theme.breakpoints.only('xs'));
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -040065
66 useEffect(() => {
Ziwei Wang9b4e2c12023-02-06 14:45:37 -050067 let timetoutId: ReturnType<typeof setTimeout>;
68 if (usernameStatus === 'valid') {
69 timetoutId = setTimeout(() => {
70 // useCheckIfUsernameIsRegisteredMutation.mutate(username);
71 checkIfUserameIsRegistered(username)
72 .then((response) => {
73 //server responds 200 if name IS found
74 if (response.status === HttpStatusCode.Ok) {
75 setUsernameStatus('taken');
76 }
77 })
78 .catch((e) => {
79 // console.log(e.response.data);
80 const { status } = e.response;
81 if (status === HttpStatusCode.BadRequest) {
82 //TODO: server sends invalid username as the message, add it to the locale
83 // console.log(e.response.data);
84 setUsernameStatus('invalid');
85 } else if (status === HttpStatusCode.NotFound) {
86 //TODO: server didn't find this username, therefore it can be registered, add the message to the locale
87 // console.log(e.response.data);
88 setUsernameStatus('success');
89 } else {
90 setErrorAlertContent(t('unknown_error_alert'));
91 }
92 });
93 }, 1000);
94 }
95
96 return () => {
97 clearTimeout(timetoutId);
98 };
99 }, [t, username, usernameStatus]);
100
101 const login = async () => {
102 setLoading(true);
103 loginUser(username, password, isJams)
104 .then((response) => {
105 if (response.status === HttpStatusCode.Ok) {
106 setAccessToken(response.data.accessToken);
107 navigate('/conversation', { replace: true });
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400108 }
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500109 })
110 .catch((e) => {
111 console.log(e);
112 const { status } = e.response;
113 if (status === HttpStatusCode.BadRequest) {
114 //TODO: the only bad request response defined in the server is missing credentials. add the response message to the locale.
115 console.log(e.response.data);
116 setErrorAlertContent(t('unknown_error_alert'));
117 } else if (status === HttpStatusCode.NotFound) {
118 //TODO: there are two different not found responses that could be returned by the server, use message to differentiate them?
119 console.log(e.response.data);
120 // setErrorAlertContent(t('login_username_not_found'));
121 } else if (status === HttpStatusCode.Unauthorized) {
122 setErrorAlertContent(t('login_invalid_password'));
123 } else {
124 setErrorAlertContent(t('unknown_error_alert'));
125 }
126 })
127 .finally(() => {
128 setLoading(false);
129 });
Michelle Sepkap Simee580f422022-10-31 23:27:04 -0400130 };
131
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500132 const createAccount = () => {
133 setLoading(true);
134 registerUser(username, password, isJams)
135 .then((response) => {
136 if (response.status === HttpStatusCode.Created) {
137 setSuccessAlertContent(t('registration_success'));
138 login();
139 }
140 })
141 .catch((e) => {
142 console.log(e);
143 const { status } = e.response;
144 if (status === HttpStatusCode.BadRequest) {
145 //TODO: more than one bad request response defined in the server is missing credentials. add the response message to the locale.
146 console.log(e.response.data);
147 // setErrorAlertContent(t('unknown_error_alert'));
148 } else if (status === HttpStatusCode.Conflict) {
149 //TODO: there are two different conflict responses that could be returned by the server, use message to differentiate them?
150 console.log(e.response.data);
151 // setErrorAlertContent(t('login_username_not_found'));
152 } else if (status === HttpStatusCode.Unauthorized) {
153 //TODO: this is a response for JAMS, add message to the locale
154 } else {
155 setErrorAlertContent(t('unknown_error_alert'));
156 }
157 })
158 .finally(() => {
159 setLoading(false);
160 });
Michelle Sepkap Simee580f422022-10-31 23:27:04 -0400161 };
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400162
163 const handleUsername = async (event: ChangeEvent<HTMLInputElement>) => {
Michelle Sepkap Simee580f422022-10-31 23:27:04 -0400164 const usernameValue: string = event.target.value;
165 setUsername(usernameValue);
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400166
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500167 if (usernameValue === '') {
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400168 setUsernameStatus('default');
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500169 return;
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400170 }
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500171
172 if (!jamiUsernamePattern.test(usernameValue)) {
173 setUsernameStatus('invalid');
174 return;
175 }
176 //The valid state is when the username passes the Regex test and is ready for availability check.
177 setUsernameStatus('valid');
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400178 };
179
180 const handlePassword = (event: ChangeEvent<HTMLInputElement>) => {
Michelle Sepkap Simee580f422022-10-31 23:27:04 -0400181 const passwordValue: string = event.target.value;
182 setPassword(passwordValue);
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400183
Michelle Sepkap Simee580f422022-10-31 23:27:04 -0400184 if (passwordValue.length > 0) {
185 const checkResult = checkPasswordStrength(passwordValue);
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400186 setPasswordStatus(checkResult.valueCode);
187 } else {
188 setPasswordStatus('default');
189 }
190 };
191
Gabriel Rochon7057b4f2022-11-21 13:28:01 -0500192 const handleIsJams = (event: ChangeEvent<HTMLInputElement>) => {
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500193 setIsJams(event.target.value === 'jams');
Gabriel Rochon7057b4f2022-11-21 13:28:01 -0500194 };
195
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400196 const handleSubmit = async (event: FormEvent) => {
197 event.preventDefault();
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500198 const usernameOk = usernameStatus === 'success';
199 const passwordOk = passwordStatus === 'strong';
200
201 const canCreate = usernameOk && passwordOk;
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400202
203 if (canCreate) {
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500204 createAccount();
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400205 } else {
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500206 if (!usernameOk) {
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400207 setUsernameStatus('registration_failed');
208 }
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500209 if (!passwordOk) {
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400210 setPasswordStatus('registration_failed');
211 }
212 }
213 };
214
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400215 return (
216 <>
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500217 <ProcessingRequest open={loading} />
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400218
Michelle Sepkap Simee580f422022-10-31 23:27:04 -0400219 <AlertSnackbar
220 severity={'success'}
221 open={!!successAlertContent}
222 onClose={() => setSuccessAlertContent(undefined)}
223 >
224 {successAlertContent}
225 </AlertSnackbar>
226
227 <AlertSnackbar severity={'error'} open={!!errorAlertContent} onClose={() => setErrorAlertContent(undefined)}>
228 {errorAlertContent}
229 </AlertSnackbar>
230
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400231 <Stack
232 sx={{
233 minHeight: `${isMobile ? 'auto' : '100%'}`,
234 display: 'flex',
235 alignItems: 'center',
236 justifyContent: 'center',
237 }}
238 >
239 <Box sx={{ mt: theme.typography.pxToRem(50), mb: theme.typography.pxToRem(20) }}>
240 <Typography component={'span'} variant="h2">
Michelle Sepkap Sime559cc802022-11-05 12:06:40 -0400241 {t('registration_form_title')}
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400242 </Typography>
243 </Box>
244
245 <Form method="post" id="register-form">
246 <div>
247 <UsernameInput
Michelle Sepkap Simee580f422022-10-31 23:27:04 -0400248 value={username}
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400249 onChange={handleUsername}
simonab4eec82022-11-08 20:32:43 -0500250 status={usernameStatus}
Michelle Sepkap Sime559cc802022-11-05 12:06:40 -0400251 tooltipTitle={t('registration_form_username_tooltip')}
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500252 sx={{ width: theme.typography.pxToRem(inputWidth) }}
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400253 />
254 </div>
255 <div>
256 <PasswordInput
Michelle Sepkap Simee580f422022-10-31 23:27:04 -0400257 value={password}
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400258 onChange={handlePassword}
simonab4eec82022-11-08 20:32:43 -0500259 status={passwordStatus}
Michelle Sepkap Sime559cc802022-11-05 12:06:40 -0400260 tooltipTitle={t('registration_form_password_tooltip')}
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500261 sx={{ width: theme.typography.pxToRem(inputWidth) }}
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400262 />
263 </div>
Gabriel Rochon7057b4f2022-11-21 13:28:01 -0500264 <div>
265 <FormControl
266 sx={{
267 width: theme.typography.pxToRem(inputWidth),
268 alignItems: 'center',
269 justifyContent: 'space-between',
270 }}
271 >
Ziwei Wang9b4e2c12023-02-06 14:45:37 -0500272 <RadioGroup row onChange={handleIsJams} defaultValue="jami">
273 <FormControlLabel value="jami" control={<Radio />} label={t('jami')} />
274 <FormControlLabel value="jams" control={<Radio />} label={t('jams')} />
Gabriel Rochon7057b4f2022-11-21 13:28:01 -0500275 </RadioGroup>
276 </FormControl>
277 </div>
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400278
279 <Button
280 variant="contained"
281 type="submit"
282 onClick={handleSubmit}
283 sx={{ width: theme.typography.pxToRem(inputWidth), mt: theme.typography.pxToRem(20) }}
284 >
Michelle Sepkap Sime559cc802022-11-05 12:06:40 -0400285 {t('registration_form_submit_button')}
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400286 </Button>
287 </Form>
288
289 <Box sx={{ mt: theme.typography.pxToRem(50), mb: theme.typography.pxToRem(50) }}>
290 <Typography variant="body1">
Michelle Sepkap Sime559cc802022-11-05 12:06:40 -0400291 {t('registration_form_to_login_text')} &nbsp;
Ziwei Wang3ce1ac02023-02-03 11:59:03 -0500292 <Link to={'/login'}>{t('registration_form_to_login_link')}</Link>
Michelle Sepkap Sime51c00452022-10-31 21:26:38 -0400293 </Typography>
294 </Box>
295 </Stack>
296 </>
297 );
298}
Ziwei Wang3ce1ac02023-02-03 11:59:03 -0500299
300export default withAuthUI(RegistrationForm);