Implemented user registration flows

Implemented password reset for admins (when no notification provider is enabled)
Major refactoring
This commit is contained in:
grimsi
2024-09-27 02:10:41 +02:00
parent c864a6a491
commit 913ff9d289
27 changed files with 815 additions and 269 deletions
+59 -132
View File
@@ -1,41 +1,25 @@
import {useAuth} from "Frontend/util/auth";
import {useEffect, useState} from "react";
import {WarningCircle, XCircle} from "@phosphor-icons/react";
import {
Button,
Card,
CardBody,
CardHeader,
Input,
Link,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
useDisclosure
} from "@nextui-org/react";
import {Alert, AlertDescription, AlertTitle} from "Frontend/@/components/ui/alert";
import {Button, Card, CardBody, CardHeader, Link, useDisclosure} from "@nextui-org/react";
import {useNavigate} from "react-router-dom";
import {MessageEndpoint, PasswordResetEndpoint} from "Frontend/generated/endpoints";
import {toast} from "sonner";
import {Form, Formik} from "formik";
import Input from "Frontend/components/general/Input";
import PasswordResetModal from "Frontend/components/general/PasswordResetModal";
import SignUpModal from "Frontend/components/general/SignUpModal";
import {RegistrationEndpoint} from "Frontend/generated/endpoints";
export default function LoginView() {
const {state, login} = useAuth();
const {isOpen, onOpen, onOpenChange} = useDisclosure();
const [hasError, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [username, setUsername] = useState<string>();
const [password, setPassword] = useState<string>();
const [canResetPassword, setCanResetPassword] = useState(false);
const [url, setUrl] = useState<string>();
const [resetEmail, setResetEmail] = useState<string>();
const navigate = useNavigate();
const passwordResetModal = useDisclosure();
const signUpModal = useDisclosure();
const [url, setUrl] = useState<string>();
const [signUpAllowed, setSignUpAllowed] = useState<boolean>(false);
useEffect(() => {
MessageEndpoint.isEnabled().then(setCanResetPassword);
RegistrationEndpoint.isSelfRegistrationAllowed().then(setSignUpAllowed);
}, []);
useEffect(() => {
@@ -45,9 +29,14 @@ export default function LoginView() {
}
}, [state.user]);
async function resetPassword() {
await PasswordResetEndpoint.requestPasswordReset(resetEmail);
toast.success("If the email address is registered, you will receive a message with further instructions.");
async function tryLogin(values: any, formik: any) {
const {defaultUrl, error, redirectUrl} = await login(values.username, values.password);
if (!error) {
setUrl(redirectUrl ?? defaultUrl ?? '/');
} else {
formik.setFieldError("username", " "); // Mark the field red, but don't show an error message
formik.setFieldError("password", "Invalid username and/or password.");
}
}
return (
@@ -61,109 +50,47 @@ export default function LoginView() {
/>
</CardHeader>
<CardBody className="mt-8 mb-2 w-80 max-w-screen-lg sm:w-96">
{hasError &&
<Alert className="mb-4" variant="destructive">
<XCircle weight="fill" className="size-4"/>
<AlertTitle>Error</AlertTitle>
<AlertDescription>Wrong username and/or password</AlertDescription>
</Alert>
}
<form
className="mb-1 flex flex-col gap-6"
onSubmit={async e => {
e.preventDefault();
if (typeof username === "string" && password != null) {
setLoading(true);
const {defaultUrl, error, redirectUrl} = await login(username, password);
if (error) {
setError(true);
} else {
setUrl(redirectUrl ?? defaultUrl ?? '/');
}
setLoading(false);
}
}}
>
<label htmlFor="username">
<h6 color="blue-gray" className="-mb-3">
Username
</h6>
</label>
<Input
onChange={(event: any) => {
setUsername(event.target.value);
}}
id="username"
type="text"
autoComplete="username"
placeholder=""
/>
<label htmlFor="current-password">
<h6 color="blue-gray" className="-mb-3">
Password
</h6>
</label>
<Input
onChange={(event: any) => {
setPassword(event.target.value);
}}
id="current-password"
type="password"
autoComplete="current-password"
placeholder=""
/>
<div className="flex justify-between items-center">
<Link color="foreground" underline="always" onPress={onOpen}>
Forgot password?
</Link>
<Button color="primary" type="submit" isLoading={loading}>
{loading ? "" : "Log in"}
</Button>
</div>
</form>
<Formik
initialValues={{}}
onSubmit={tryLogin}>
{(formik: { isSubmitting: any; }) => (
<Form className="mb-1 flex flex-col gap-6">
<Input
name="username"
label="Username"
autoComplete="username"
/>
<Input
name="password"
label="Password"
autoComplete="current-password"
type="password"
/>
<div className="flex justify-between items-center">
<Link color="foreground" underline="always" href="#"
onPress={passwordResetModal.onOpen}>
Forgot password?
</Link>
<div className="flex flex-row gap-2">
{signUpAllowed &&
<Button color="default" variant="light"
onPress={signUpModal.onOpen}>
Sign up
</Button>
}
<Button color="primary" type="submit" isLoading={formik.isSubmitting}>
{formik.isSubmitting ? "" : "Log in"}
</Button>
</div>
</div>
</Form>
)}
</Formik>
</CardBody>
</Card>
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="xl">
<ModalContent>
{(onClose) => (
<>
<ModalHeader className="flex flex-col gap-1">Request a password reset</ModalHeader>
<ModalBody>
{canResetPassword ?
<Input
onChange={(event: any) => {
setResetEmail(event.target.value);
}}
type="email"
placeholder="Email"
/> :
<div className="flex flex-row items-center gap-4 text-warning">
<WarningCircle size={40}/>
<p>
Password self-service is disabled.<br/>
To reset your password please contact your administrator.
</p>
</div>
}
</ModalBody>
<ModalFooter>
<Button color="danger" variant="light" onPress={onClose}>
Cancel
</Button>
<Button color="primary"
isDisabled={!canResetPassword}
onPress={async () => {
await resetPassword();
onClose();
}}>
Send request
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
<PasswordResetModal isOpen={passwordResetModal.isOpen} onOpenChange={passwordResetModal.onOpenChange}/>
<SignUpModal isOpen={signUpModal.isOpen} onOpenChange={signUpModal.onOpenChange}/>
</div>
);
}