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