SignIn.tsx
⎇
Raw
1import { type Accessor, createEffect, type Setter } from "solid-js";
2import { ClearSVG } from "./SVG";
3import "./SignIn.css";
4import { toast } from "../utils";
5import { LoadingSpinner } from "./LoadingScreen";
6
7interface SignInProps {
8 showSignIn: Accessor<boolean>;
9 setShowSignIn: Setter<boolean>;
10 onLoggedIn: () => void;
11}
12
13export function SignIn(props: SignInProps) {
14 let dialogElement!: HTMLDialogElement;
15 let formElement!: HTMLFormElement;
16 let usernameInput!: HTMLInputElement;
17 let passwordInput!: HTMLInputElement;
18 let loadingElement!: HTMLDivElement;
19 let submitButton!: HTMLButtonElement;
20
21 createEffect(() => {
22 if (props.showSignIn()) {
23 dialogElement.showModal();
24 }
25 });
26
27 function handleClose() {
28 dialogElement.close();
29 usernameInput.value = "";
30 passwordInput.value = "";
31 props.setShowSignIn(false);
32 }
33
34 async function doSignIn(e: Event) {
35 e.preventDefault();
36 usernameInput.setCustomValidity("");
37 if (!formElement.reportValidity()) return;
38 if (usernameInput.value.includes(":")) {
39 usernameInput.setCustomValidity('Username can\'t contain a colon ":"');
40 usernameInput.reportValidity();
41 return;
42 }
43 loadingElement.style.display = "block";
44 submitButton.disabled = true;
45 try {
46 const response = await fetch("/login", {
47 method: "POST",
48 body: `${usernameInput.value}:${passwordInput.value}`,
49 });
50 if (response.status === 200) {
51 toast("Login successful", "success");
52 props.onLoggedIn();
53 handleClose();
54 } else if (response.status === 401) {
55 toast("Login failed: wrong credentials", "error");
56 } else {
57 toast(`Login failed: unknown error, code: ${response.status}`, "error", await (await response.blob()).text());
58 }
59 } finally {
60 loadingElement.style.display = "none";
61 submitButton.disabled = false;
62 }
63 }
64
65 return (
66 <dialog ref={dialogElement} onClose={handleClose} onclick={(e) => e.target.nodeName === "DIALOG" && handleClose()}>
67 <form ref={formElement} onSubmit={doSignIn}>
68 <div class="signin-header title">
69 Sign in
70 <button type="button" title="Close dialog" onClick={handleClose}>
71 <ClearSVG />
72 </button>
73 </div>
74 <hr />
75 <label for="username-input">Username:</label>
76 <input
77 ref={usernameInput}
78 id="username-input"
79 type="text"
80 minlength={1}
81 required
82 value=""
83 onChange={() => {}}
84 />
85 <label for="password-input">Password:</label>
86 <input
87 ref={passwordInput}
88 id="password-input"
89 type="password"
90 minlength={1}
91 required
92 value=""
93 onChange={() => {}}
94 />
95 <button ref={submitButton} type="submit" class="signin-button" title="Submit sign in">
96 Submit
97 <div ref={loadingElement} style={{ display: "none", width: "24px", height: "24px" }}>
98 <LoadingSpinner center />
99 </div>
100 </button>
101 </form>
102 </dialog>
103 );
104}
105