diff --git a/frontend/src/styles/core.scss b/frontend/src/styles/core.scss index 880c922b2..f7d32b807 100644 --- a/frontend/src/styles/core.scss +++ b/frontend/src/styles/core.scss @@ -391,3 +391,37 @@ key { color: var(--bg-color); } } + +.inputAndIndicator { + input { + width: 100%; + } + position: relative; + .statusIndicator { + width: 2.25rem; + height: 2.25rem; + position: absolute; + right: 0; + top: 0; + /* background: red; */ + display: grid; + grid-template-columns: 2.25rem; + grid-template-rows: 2.25rem; + place-items: center center; + cursor: pointer; + + .indicator { + grid-column: 1/2; + grid-row: 1/2; + &.level-1 { + color: var(--error-color); + } + &.level0 { + color: var(--sub-color); + } + &.level1 { + color: var(--main-color); + } + } + } +} diff --git a/frontend/src/styles/login.scss b/frontend/src/styles/login.scss index b98c46a7f..dd22c673c 100644 --- a/frontend/src/styles/login.scss +++ b/frontend/src/styles/login.scss @@ -34,45 +34,6 @@ grid-area: form; } } - &.register { - .inputAndIndicator { - input { - width: 100%; - } - position: relative; - .checkStatus { - width: 2.25rem; - height: 2.25rem; - position: absolute; - right: 0; - top: 0; - /* background: red; */ - display: grid; - grid-template-columns: 2.25rem; - grid-template-rows: 2.25rem; - place-items: center center; - cursor: pointer; - - .checking, - .available, - .unavailable, - .taken { - grid-column: 1/2; - grid-row: 1/2; - } - .checking { - color: var(--sub-color); - } - .available { - color: var(--main-color); - } - .unavailable, - .taken { - color: var(--error-color); - } - } - } - } } form { diff --git a/frontend/src/ts/elements/input-indicator.ts b/frontend/src/ts/elements/input-indicator.ts new file mode 100644 index 000000000..756225d03 --- /dev/null +++ b/frontend/src/ts/elements/input-indicator.ts @@ -0,0 +1,71 @@ +interface InputIndicatorOption { + icon: string; + spinIcon?: true; + message?: string; + level: -1 | 0 | 1; +} + +export class InputIndicator { + private parentElement: JQuery; + private options: Record; + + constructor( + parentElement: JQuery, + options: Record + ) { + if (!parentElement.hasClass("inputAndIndicator")) { + throw new Error("Parent element must have class 'inputAndIndicator'"); + } + this.parentElement = parentElement; + this.options = options; + + let indicator = `
`; + + for (const [optionId, option] of Object.entries(options)) { + indicator += ` + + `; + } + + indicator += `
`; + + parentElement.append(indicator); + } + + hide(): void { + this.parentElement.find(".statusIndicator div").addClass("hidden"); + } + + show(optionId: keyof typeof this.options, messageOverride?: string): void { + this.hide(); + + const indicator = this.parentElement.find(`[data-option-id="${optionId}"]`); + + indicator.removeClass("hidden"); + + if (messageOverride) { + if (messageOverride.length > 20) { + indicator.attr("data-balloon-length", "large"); + } else { + indicator.removeAttr("data-balloon-length"); + } + indicator.attr("aria-label", messageOverride); + } + } +} diff --git a/frontend/src/ts/pages/login.ts b/frontend/src/ts/pages/login.ts index 0e672dde1..8a4d51e98 100644 --- a/frontend/src/ts/pages/login.ts +++ b/frontend/src/ts/pages/login.ts @@ -2,6 +2,7 @@ import { debounce } from "throttle-debounce"; import Ape from "../ape"; import Page from "./page"; import * as Notifications from "../elements/notifications"; +import { InputIndicator } from "../elements/input-indicator"; export function enableInputs(): void { $(".pageLogin .button").removeClass("disabled"); @@ -21,34 +22,28 @@ export function hidePreloader(): void { $(".pageLogin .preloader").addClass("hidden"); } -function updateIndicator( - state: "checking" | "available" | "unavailable" | "taken" | "none", - balloon?: string -): void { - $(".page.pageLogin .register.side .checkStatus .checking").addClass("hidden"); - $(".page.pageLogin .register.side .checkStatus .available").addClass( - "hidden" - ); - $(".page.pageLogin .register.side .checkStatus .unavailable").addClass( - "hidden" - ); - $(".page.pageLogin .register.side .checkStatus .taken").addClass("hidden"); - if (state !== "none") { - $(".page.pageLogin .register.side .checkStatus ." + state).removeClass( - "hidden" - ); - if (balloon) { - $(".page.pageLogin .register.side .checkStatus ." + state).attr( - "aria-label", - balloon - ); - } else { - $(".page.pageLogin .register.side .checkStatus ." + state).removeAttr( - "aria-label" - ); - } +const nameIndicator = new InputIndicator( + $(".page.pageLogin .register.side .username.inputAndIndicator"), + { + available: { + icon: "fa-check", + level: 1, + }, + unavailable: { + icon: "fa-times", + level: -1, + }, + taken: { + icon: "fa-times", + level: -1, + }, + checking: { + icon: "fa-circle-notch", + spinIcon: true, + level: 0, + }, } -} +); const checkNameDebounced = debounce(1000, async () => { const val = $( @@ -58,22 +53,22 @@ const checkNameDebounced = debounce(1000, async () => { const response = await Ape.users.getNameAvailability(val); if (response.status === 200) { - updateIndicator("available", response.message); + nameIndicator.show("available", response.message); return; } if (response.status == 422) { - updateIndicator("unavailable", response.message); + nameIndicator.show("unavailable", response.message); return; } if (response.status == 409) { - updateIndicator("taken", response.message); + nameIndicator.show("taken", response.message); return; } if (response.status !== 200) { - updateIndicator("unavailable"); + nameIndicator.show("unavailable", response.message); return Notifications.add( "Failed to check name availability: " + response.message, -1 @@ -87,9 +82,9 @@ $(".page.pageLogin .register.side .usernameInput").on("input", () => { ".page.pageLogin .register.side .usernameInput" ).val() as string; if (val === "") { - return updateIndicator("none"); + return nameIndicator.hide(); } else { - updateIndicator("checking"); + nameIndicator.show("checking"); checkNameDebounced(); } }, 1); diff --git a/frontend/src/ts/popups/google-sign-up-popup.ts b/frontend/src/ts/popups/google-sign-up-popup.ts index b95de7f49..71db3ce72 100644 --- a/frontend/src/ts/popups/google-sign-up-popup.ts +++ b/frontend/src/ts/popups/google-sign-up-popup.ts @@ -15,6 +15,7 @@ import * as TestLogic from "../test/test-logic"; import * as DB from "../db"; import * as Loader from "../elements/loader"; import { subscribe as subscribeToSignUpEvent } from "../observables/google-sign-up-event"; +import { InputIndicator } from "../elements/input-indicator"; let signedInUser: UserCredential | undefined = undefined; @@ -130,27 +131,6 @@ async function apply(): Promise { } } -function updateIndicator( - state: "checking" | "available" | "unavailable" | "taken" | "none", - balloon?: string -): void { - $("#googleSignUpPopup .checkStatus .checking").addClass("hidden"); - $("#googleSignUpPopup .checkStatus .available").addClass("hidden"); - $("#googleSignUpPopup .checkStatus .unavailable").addClass("hidden"); - $("#googleSignUpPopup .checkStatus .taken").addClass("hidden"); - if (state !== "none") { - $("#googleSignUpPopup .checkStatus ." + state).removeClass("hidden"); - if (balloon) { - $("#googleSignUpPopup .checkStatus ." + state).attr( - "aria-label", - balloon - ); - } else { - $("#googleSignUpPopup .checkStatus ." + state).removeAttr("aria-label"); - } - } -} - function enableButton(): void { $("#googleSignUpPopup .button").removeClass("disabled"); } @@ -173,29 +153,52 @@ $("#googleSignUpPopupWrapper").on("mousedown", (e) => { } }); +const nameIndicator = new InputIndicator( + $("#googleSignUpPopup .inputAndIndicator"), + { + available: { + icon: "fa-check", + level: 1, + }, + unavailable: { + icon: "fa-times", + level: -1, + }, + taken: { + icon: "fa-times", + level: -1, + }, + checking: { + icon: "fa-circle-notch", + spinIcon: true, + level: 0, + }, + } +); + const checkNameDebounced = debounce(1000, async () => { const val = $("#googleSignUpPopup input").val() as string; if (!val) return; const response = await Ape.users.getNameAvailability(val); if (response.status === 200) { - updateIndicator("available", response.message); + nameIndicator.show("available", response.message); enableButton(); return; } if (response.status == 422) { - updateIndicator("unavailable", response.message); + nameIndicator.show("unavailable", response.message); return; } if (response.status == 409) { - updateIndicator("taken", response.message); + nameIndicator.show("taken", response.message); return; } if (response.status !== 200) { - updateIndicator("unavailable"); + nameIndicator.show("unavailable"); return Notifications.add( "Failed to check name availability: " + response.message, -1 @@ -208,9 +211,9 @@ $("#googleSignUpPopup input").on("input", () => { disableButton(); const val = $("#googleSignUpPopup input").val() as string; if (val === "") { - return updateIndicator("none"); + return nameIndicator.hide(); } else { - updateIndicator("checking"); + nameIndicator.show("checking"); checkNameDebounced(); } }, 1); diff --git a/frontend/static/html/pages/login.html b/frontend/static/html/pages/login.html index 91b6df9d1..47fa4a262 100644 --- a/frontend/static/html/pages/login.html +++ b/frontend/static/html/pages/login.html @@ -5,35 +5,13 @@
register
-
+
-
- - - - -
You need to choose a username before continuing
-
- - - - -
Sign up