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