diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx
index b0ff229480..b0f6b5f0e5 100644
--- a/src/frontend/src/components/forms/AuthenticationForm.tsx
+++ b/src/frontend/src/components/forms/AuthenticationForm.tsx
@@ -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 (
-
-
- Welcome, log in below
-
-
+ );
+}
+
+export function RegistrationForm() {
+ const registrationForm = useForm({
+ initialValues: { username: '', email: '', password1: '', password2: '' }
+ });
+ const navigate = useNavigate();
+ const [isRegistering, setIsRegistering] = useState(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:
+ });
+ 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 (
+
+ );
+}
+
+export function ModeSelector({
+ loginMode,
+ setMode
+}: {
+ loginMode: boolean;
+ setMode: any;
+}) {
+ return (
+
+ {loginMode ? (
+ <>
+ Don't have an account?{' '}
setMode.toggle()}
>
- {classicLoginMode ? (
- Send me an email
- ) : (
- I will use username and password
- )}
+ Register
-
-
-
-
+ >
+ ) : (
+ setMode.toggle()}
+ >
+ Go back to login
+
+ )}
+
);
}
diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx
index d5f676c73a..24388bec07 100644
--- a/src/frontend/src/enums/ApiEndpoints.tsx
+++ b/src/frontend/src/enums/ApiEndpoints.tsx
@@ -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',
diff --git a/src/frontend/src/pages/Auth/Login.tsx b/src/frontend/src/pages/Auth/Login.tsx
index 00abb1f5b0..16de1b7509 100644
--- a/src/frontend/src/pages/Auth/Login.tsx
+++ b/src/frontend/src/pages/Auth/Login.tsx
@@ -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() {
/>
) : (
<>
-
+
+
+ Welcome, log in below
+
+ {loginMode ? : }
+
+
>
)}
diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx
index 473e84bb6d..e4a17fd4c1 100644
--- a/src/frontend/src/states/ApiState.tsx
+++ b/src/frontend/src/states/ApiState.tsx
@@ -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: