[PUI] Registration (#6309)

* optimize login layout

* move auth/reg up

* [PUI] Registration
Fixes #6282

* [PUI] Registration
Fixes #6282

* fix type

* style: cleaned imports
This commit is contained in:
Matthias Mair 2024-01-22 10:39:06 +00:00 committed by GitHub
parent 2fbb8c757f
commit d64fbfc254
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 207 additions and 70 deletions

View File

@ -4,7 +4,6 @@ import {
Button,
Group,
Loader,
Paper,
PasswordInput,
Stack,
Text,
@ -17,7 +16,10 @@ import { IconCheck } from '@tabler/icons-react';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ApiPaths } from '../../enums/ApiEndpoints';
import { doClassicLogin, doSimpleLogin } from '../../functions/auth';
import { apiUrl } from '../../states/ApiState';
export function AuthenticationForm() {
const classicForm = useForm({
@ -79,50 +81,178 @@ export function AuthenticationForm() {
}
return (
<Paper radius="md" p="xl" withBorder>
<Text size="lg" weight={500}>
<Trans>Welcome, log in below</Trans>
</Text>
<form onSubmit={classicForm.onSubmit(() => {})}>
{classicLoginMode ? (
<Stack>
<TextInput
required
label={t`Username`}
placeholder={t`Your username`}
{...classicForm.getInputProps('username')}
/>
<PasswordInput
required
label={t`Password`}
placeholder={t`Your password`}
{...classicForm.getInputProps('password')}
/>
<Group position="apart" mt="0">
<Anchor
component="button"
type="button"
color="dimmed"
size="xs"
onClick={() => navigate('/reset-password')}
>
<Trans>Reset password</Trans>
</Anchor>
</Group>
</Stack>
) : (
<Stack>
<TextInput
required
label={t`Email`}
description={t`We will send you a link to login - if you are registered`}
placeholder="email@example.org"
{...simpleForm.getInputProps('email')}
/>
</Stack>
)}
<form onSubmit={classicForm.onSubmit(() => {})}>
{classicLoginMode ? (
<Stack spacing={0}>
<TextInput
required
label={t`Username`}
placeholder={t`Your username`}
{...classicForm.getInputProps('username')}
/>
<PasswordInput
required
label={t`Password`}
placeholder={t`Your password`}
{...classicForm.getInputProps('password')}
/>
<Group position="apart" mt="0">
<Anchor
component="button"
type="button"
color="dimmed"
size="xs"
onClick={() => navigate('/reset-password')}
>
<Trans>Reset password</Trans>
</Anchor>
</Group>
</Stack>
) : (
<Stack>
<TextInput
required
label={t`Email`}
description={t`We will send you a link to login - if you are registered`}
placeholder="email@example.org"
{...simpleForm.getInputProps('email')}
/>
</Stack>
)}
<Group position="apart" mt="xl">
<Group position="apart" mt="xl">
<Anchor
component="button"
type="button"
color="dimmed"
size="xs"
onClick={() => setMode.toggle()}
>
{classicLoginMode ? (
<Trans>Send me an email</Trans>
) : (
<Trans>Use username and password</Trans>
)}
</Anchor>
<Button type="submit" disabled={isLoggingIn} onClick={handleLogin}>
{isLoggingIn ? (
<Loader size="sm" />
) : (
<>
{classicLoginMode ? (
<Trans>Log In</Trans>
) : (
<Trans>Send Email</Trans>
)}
</>
)}
</Button>
</Group>
</form>
);
}
export function RegistrationForm() {
const registrationForm = useForm({
initialValues: { username: '', email: '', password1: '', password2: '' }
});
const navigate = useNavigate();
const [isRegistering, setIsRegistering] = useState<boolean>(false);
function handleRegistration() {
setIsRegistering(true);
api
.post(apiUrl(ApiPaths.user_register), registrationForm.values, {
headers: { Authorization: '' }
})
.then((ret) => {
if (ret?.status === 204) {
setIsRegistering(false);
notifications.show({
title: t`Registration successful`,
message: t`Please confirm your email address to complete the registration`,
color: 'green',
icon: <IconCheck size="1rem" />
});
navigate('/home');
}
})
.catch((err) => {
if (err.response.status === 400) {
setIsRegistering(false);
for (const [key, value] of Object.entries(err.response.data)) {
registrationForm.setFieldError(key, value as string);
}
let err_msg = '';
if (err.response?.data?.non_field_errors) {
err_msg = err.response.data.non_field_errors;
}
notifications.show({
title: t`Input error`,
message: t`Check your input and try again. ` + err_msg,
color: 'red',
autoClose: 30000
});
}
});
}
return (
<form onSubmit={registrationForm.onSubmit(() => {})}>
<Stack spacing={0}>
<TextInput
required
label={t`Username`}
placeholder={t`Your username`}
{...registrationForm.getInputProps('username')}
/>
<TextInput
required
label={t`Email`}
description={t`This will be used for a confirmation`}
placeholder="email@example.org"
{...registrationForm.getInputProps('email')}
/>
<PasswordInput
required
label={t`Password`}
placeholder={t`Your password`}
{...registrationForm.getInputProps('password1')}
/>
<PasswordInput
required
label={t`Password repeat`}
placeholder={t`Repeat password`}
{...registrationForm.getInputProps('password2')}
/>
</Stack>
<Group position="apart" mt="xl">
<Button
type="submit"
disabled={isRegistering}
onClick={handleRegistration}
fullWidth
>
<Trans>Register</Trans>
</Button>
</Group>
</form>
);
}
export function ModeSelector({
loginMode,
setMode
}: {
loginMode: boolean;
setMode: any;
}) {
return (
<Text ta="center" size={'xs'} mt={'md'}>
{loginMode ? (
<>
<Trans>Don&apos;t have an account?</Trans>{' '}
<Anchor
component="button"
type="button"
@ -130,27 +260,20 @@ export function AuthenticationForm() {
size="xs"
onClick={() => setMode.toggle()}
>
{classicLoginMode ? (
<Trans>Send me an email</Trans>
) : (
<Trans>I will use username and password</Trans>
)}
<Trans>Register</Trans>
</Anchor>
<Button type="submit" disabled={isLoggingIn} onClick={handleLogin}>
{isLoggingIn ? (
<Loader size="sm" />
) : (
<>
{classicLoginMode ? (
<Trans>Log In</Trans>
) : (
<Trans>Send Email</Trans>
)}
</>
)}
</Button>
</Group>
</form>
</Paper>
</>
) : (
<Anchor
component="button"
type="button"
color="dimmed"
size="xs"
onClick={() => setMode.toggle()}
>
<Trans>Go back to login</Trans>
</Anchor>
)}
</Text>
);
}

View File

@ -20,6 +20,7 @@ export enum ApiPaths {
user_email_primary = 'api-user-email-primary',
user_email_remove = 'api-user-email-remove',
user_logout = 'api-user-logout',
user_register = 'api-user-register',
user_list = 'api-user-list',
group_list = 'api-group-list',

View File

@ -1,12 +1,16 @@
import { t } from '@lingui/macro';
import { Center, Container } from '@mantine/core';
import { useToggle } from '@mantine/hooks';
import { Trans, t } from '@lingui/macro';
import { Center, Container, Paper, Text } from '@mantine/core';
import { useDisclosure, useToggle } from '@mantine/hooks';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { setApiDefaults } from '../../App';
import { AuthFormOptions } from '../../components/forms/AuthFormOptions';
import { AuthenticationForm } from '../../components/forms/AuthenticationForm';
import {
AuthenticationForm,
ModeSelector,
RegistrationForm
} from '../../components/forms/AuthenticationForm';
import { InstanceOptions } from '../../components/forms/InstanceOptions';
import { defaultHostKey } from '../../defaults/defaultHostList';
import { checkLoginState } from '../../functions/auth';
@ -26,6 +30,7 @@ export default function Login() {
const hostname =
hostList[hostKey] === undefined ? t`No selection` : hostList[hostKey]?.name;
const [hostEdit, setHostEdit] = useToggle([false, true] as const);
const [loginMode, setMode] = useDisclosure(true);
const navigate = useNavigate();
// Data manipulation functions
@ -63,7 +68,13 @@ export default function Login() {
/>
) : (
<>
<AuthenticationForm />
<Paper radius="md" p="xl" withBorder>
<Text size="lg" weight={500}>
<Trans>Welcome, log in below</Trans>
</Text>
{loginMode ? <AuthenticationForm /> : <RegistrationForm />}
<ModeSelector loginMode={loginMode} setMode={setMode} />
</Paper>
<AuthFormOptions hostname={hostname} toggleHostEdit={setHostEdit} />
</>
)}

View File

@ -97,6 +97,8 @@ export function apiEndpoint(path: ApiPaths): string {
return 'auth/emails/:id/primary/';
case ApiPaths.user_logout:
return 'auth/logout/';
case ApiPaths.user_register:
return 'auth/registration/';
case ApiPaths.currency_list:
return 'currency/exchange/';
case ApiPaths.currency_refresh: