1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-22 17:10:10 +00:00

form: reliably disable submit button, communicate if account linking is required

This commit is contained in:
Harvey Tindall 2022-01-27 16:48:46 +00:00
parent 42dbc04ff9
commit dbefb80f63
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
6 changed files with 117 additions and 54 deletions

View File

@ -116,13 +116,13 @@
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label> <label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
<input type="email" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}"> <input type="email" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
{{ if .telegramEnabled }} {{ if .telegramEnabled }}
<span class="button ~info @low full-width center mb-4" id="link-telegram">{{ .strings.linkTelegram }}</span> <span class="button ~info @low full-width center mb-4" id="link-telegram">{{ .strings.linkTelegram }} {{ if .telegramRequired }}({{ .strings.required }}){{ end }}</span>
{{ end }} {{ end }}
{{ if .discordEnabled }} {{ if .discordEnabled }}
<span class="button ~info @low full-width center mb-4" id="link-discord">{{ .strings.linkDiscord }}</span> <span class="button ~info @low full-width center mb-4" id="link-discord">{{ .strings.linkDiscord }} {{ if .discordRequired }}({{ .strings.required }}){{ end }}</span>
{{ end }} {{ end }}
{{ if .matrixEnabled }} {{ if .matrixEnabled }}
<span class="button ~info @low full-width center mb-4" id="link-matrix">{{ .strings.linkMatrix }}</span> <span class="button ~info @low full-width center mb-4" id="link-matrix">{{ .strings.linkMatrix }} {{ if .matrixRequired }}({{ .strings.required }}){{ end }}</span>
{{ end }} {{ end }}
{{ if or (.telegramEnabled) (or .discordEnabled .matrixEnabled) }} {{ if or (.telegramEnabled) (or .discordEnabled .matrixEnabled) }}
<div id="contact-via" class="unfocused"> <div id="contact-via" class="unfocused">

View File

@ -23,6 +23,7 @@
"linkMatrix": "Link Matrix", "linkMatrix": "Link Matrix",
"contactDiscord": "Contact through Discord", "contactDiscord": "Contact through Discord",
"theme": "Theme", "theme": "Theme",
"refresh": "Refresh" "refresh": "Refresh",
"required": "Required"
} }
} }

View File

@ -31,6 +31,8 @@
"errorUnknown": "Unknown error.", "errorUnknown": "Unknown error.",
"errorNoEmail": "Email required.", "errorNoEmail": "Email required.",
"errorCaptcha": "Captcha incorrect.", "errorCaptcha": "Captcha incorrect.",
"errorPassword": "Check password requirements.",
"errorNoMatch": "Passwords don't match.",
"verified": "Account verified." "verified": "Account verified."
}, },
"validationStrings": { "validationStrings": {

View File

@ -72,6 +72,7 @@ if (window.telegramEnabled) {
document.getElementById("contact-via").classList.remove("unfocused"); document.getElementById("contact-via").classList.remove("unfocused");
const radio = document.getElementById("contact-via-telegram") as HTMLInputElement; const radio = document.getElementById("contact-via-telegram") as HTMLInputElement;
radio.checked = true; radio.checked = true;
validatorFunc();
} else if (!modalClosed) { } else if (!modalClosed) {
setTimeout(checkVerified, 1500); setTimeout(checkVerified, 1500);
} }
@ -132,6 +133,7 @@ if (window.discordEnabled) {
document.getElementById("contact-via").classList.remove("unfocused"); document.getElementById("contact-via").classList.remove("unfocused");
const radio = document.getElementById("contact-via-discord") as HTMLInputElement; const radio = document.getElementById("contact-via-discord") as HTMLInputElement;
radio.checked = true; radio.checked = true;
validatorFunc();
} else if (!modalClosed) { } else if (!modalClosed) {
setTimeout(checkVerified, 1500); setTimeout(checkVerified, 1500);
} }
@ -190,6 +192,7 @@ if (window.matrixEnabled) {
document.getElementById("contact-via").classList.remove("unfocused"); document.getElementById("contact-via").classList.remove("unfocused");
const radio = document.getElementById("contact-via-discord") as HTMLInputElement; const radio = document.getElementById("contact-via-discord") as HTMLInputElement;
radio.checked = true; radio.checked = true;
validatorFunc();
} else { } else {
window.notifications.customError("errorInvalidPIN", window.messages["errorInvalidPIN"]); window.notifications.customError("errorInvalidPIN", window.messages["errorInvalidPIN"]);
submitButton.classList.add("~critical"); submitButton.classList.add("~critical");
@ -227,25 +230,85 @@ if (window.userExpiryEnabled) {
const form = document.getElementById("form-create") as HTMLFormElement; const form = document.getElementById("form-create") as HTMLFormElement;
const submitButton = form.querySelector("input[type=submit]") as HTMLInputElement; const submitButton = form.querySelector("input[type=submit]") as HTMLInputElement;
const submitSpan = form.querySelector("span.submit") as HTMLSpanElement; const submitSpan = form.querySelector("span.submit") as HTMLSpanElement;
const submitText = submitSpan.textContent;
let usernameField = document.getElementById("create-username") as HTMLInputElement; let usernameField = document.getElementById("create-username") as HTMLInputElement;
const emailField = document.getElementById("create-email") as HTMLInputElement; const emailField = document.getElementById("create-email") as HTMLInputElement;
if (!window.usernameEnabled) { usernameField.parentElement.remove(); usernameField = emailField; } if (!window.usernameEnabled) { usernameField.parentElement.remove(); usernameField = emailField; }
const passwordField = document.getElementById("create-password") as HTMLInputElement; const passwordField = document.getElementById("create-password") as HTMLInputElement;
const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement; const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement;
if (window.emailRequired) { let captchaVerified = false;
emailField.addEventListener("keyup", () => { let captchaID = "";
if (emailField.value.includes("@")) { let captchaInput = document.getElementById("captcha-input") as HTMLInputElement;
submitButton.disabled = false; const captchaCheckbox = document.getElementById("captcha-success") as HTMLSpanElement;
submitSpan.removeAttribute("disabled"); let prevCaptcha = "";
function baseValidator(oncomplete: (valid: boolean) => void): void {
let captchaChecked = false;
let captchaChange = false;
if (window.captcha) {
captchaChange = captchaInput.value != prevCaptcha;
if (captchaChange) {
prevCaptcha = captchaInput.value;
_post("/captcha/verify/" + window.code + "/" + captchaID + "/" + captchaInput.value, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status == 204) {
captchaCheckbox.innerHTML = `<i class="ri-check-line"></i>`;
captchaCheckbox.classList.add("~positive");
captchaCheckbox.classList.remove("~critical");
captchaVerified = true;
captchaChecked = true;
} else { } else {
submitButton.disabled = true; captchaCheckbox.innerHTML = `<i class="ri-close-line"></i>`;
submitSpan.setAttribute("disabled", ""); captchaCheckbox.classList.add("~critical");
captchaCheckbox.classList.remove("~positive");
captchaVerified = false;
captchaChecked = true;
return;
}
} }
}); });
}
}
if (window.emailRequired) {
if (!emailField.value.includes("@")) {
oncomplete(false);
return;
}
}
if (window.discordEnabled && window.discordRequired && !discordVerified) {
oncomplete(false);
return;
}
if (window.telegramEnabled && window.telegramRequired && !telegramVerified) {
oncomplete(false);
return;
}
if (window.matrixEnabled && window.matrixRequired && !matrixVerified) {
oncomplete(false);
return;
}
if (window.captcha) {
if (!captchaChange) {
oncomplete(captchaVerified);
return;
}
while (!captchaChecked) {
continue;
}
oncomplete(captchaVerified);
} else {
oncomplete(true);
}
} }
var requirements = initValidator(passwordField, rePasswordField, submitButton, submitSpan) let r = initValidator(passwordField, rePasswordField, submitButton, submitSpan, baseValidator);
var requirements = r[0];
var validatorFunc = r[1] as () => void;
if (window.emailRequired) {
emailField.addEventListener("keyup", validatorFunc)
}
interface respDTO { interface respDTO {
response: boolean; response: boolean;
@ -267,10 +330,6 @@ interface sendDTO {
captcha_text?: string; captcha_text?: string;
} }
let captchaVerified = false;
let captchaID = "";
let captchaInput = document.getElementById("captcha-input") as HTMLInputElement;
const genCaptcha = () => { const genCaptcha = () => {
_get("/captcha/gen/"+window.code, null, (req: XMLHttpRequest) => { _get("/captcha/gen/"+window.code, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
@ -288,27 +347,7 @@ const genCaptcha = () => {
if (window.captcha) { if (window.captcha) {
genCaptcha(); genCaptcha();
(document.getElementById("captcha-regen") as HTMLSpanElement).onclick = genCaptcha; (document.getElementById("captcha-regen") as HTMLSpanElement).onclick = genCaptcha;
const input = document.querySelector("input[type=submit]") as HTMLInputElement; captchaInput.onkeyup = validatorFunc;
const checkbox = document.getElementById("captcha-success") as HTMLSpanElement;
captchaInput.onkeyup = () => _post("/captcha/verify/" + window.code + "/" + captchaID + "/" + captchaInput.value, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status == 204) {
input.disabled = false;
input.nextElementSibling.removeAttribute("disabled");
checkbox.innerHTML = `<i class="ri-check-line"></i>`;
checkbox.classList.add("~positive");
checkbox.classList.remove("~critical");
} else {
input.disabled = true;
input.nextElementSibling.setAttribute("disabled", "true");
checkbox.innerHTML = `<i class="ri-close-line"></i>`;
checkbox.classList.add("~critical");
checkbox.classList.remove("~positive");
}
}
}, );
input.disabled = true;
input.nextElementSibling.setAttribute("disabled", "true");
} }
const create = (event: SubmitEvent) => { const create = (event: SubmitEvent) => {
@ -361,9 +400,15 @@ const create = (event: SubmitEvent) => {
} else { } else {
submitSpan.classList.add("~critical"); submitSpan.classList.add("~critical");
submitSpan.classList.remove("~urge"); submitSpan.classList.remove("~urge");
if (req.response["error"] as string) {
submitSpan.textContent = window.messages[req.response["error"]];
} else {
submitSpan.textContent = window.messages["errorPassword"];
}
setTimeout(() => { setTimeout(() => {
submitSpan.classList.add("~urge"); submitSpan.classList.add("~urge");
submitSpan.classList.remove("~critical"); submitSpan.classList.remove("~critical");
submitSpan.textContent = submitText;
}, 1000); }, 1000);
} }
} }
@ -376,17 +421,18 @@ const create = (event: SubmitEvent) => {
window.confirmationModal.show(); window.confirmationModal.show();
return; return;
} }
const old = submitSpan.textContent;
if (req.response["error"] in window.messages) { if (req.response["error"] in window.messages) {
submitSpan.textContent = window.messages[req.response["error"]]; submitSpan.textContent = window.messages[req.response["error"]];
} else { } else {
submitSpan.textContent = req.response["error"]; submitSpan.textContent = req.response["error"];
} }
setTimeout(() => { submitSpan.textContent = old; }, 1000); setTimeout(() => { submitSpan.textContent = submitText; }, 1000);
} }
} }
} }
}); });
}; };
validatorFunc();
form.onsubmit = create; form.onsubmit = create;

View File

@ -1,6 +1,7 @@
interface valWindow extends Window { interface valWindow extends Window {
validationStrings: pwValStrings; validationStrings: pwValStrings;
invalidPassword: string; invalidPassword: string;
messages: { [key: string]: string };
} }
interface pwValString { interface pwValString {
@ -59,7 +60,7 @@ class Requirement {
validate = (count: number) => { this.valid = (count >= this._minCount); } validate = (count: number) => { this.valid = (count >= this._minCount); }
} }
export function initValidator(passwordField: HTMLInputElement, rePasswordField: HTMLInputElement, submitButton: HTMLInputElement, submitSpan: HTMLSpanElement): { [category: string]: Requirement } { export function initValidator(passwordField: HTMLInputElement, rePasswordField: HTMLInputElement, submitButton: HTMLInputElement, submitSpan: HTMLSpanElement, validatorFunc?: (oncomplete: (valid: boolean) => void) => void): ({ [category: string]: Requirement }|(() => void))[] {
var defaultPwValStrings: pwValStrings = { var defaultPwValStrings: pwValStrings = {
length: { length: {
singular: "Must have at least {n} character", singular: "Must have at least {n} character",
@ -84,18 +85,30 @@ export function initValidator(passwordField: HTMLInputElement, rePasswordField:
} }
const checkPasswords = () => { const checkPasswords = () => {
if (passwordField.value != rePasswordField.value) { return passwordField.value == rePasswordField.value;
}
const checkValidity = () => {
const pw = checkPasswords();
validatorFunc((valid: boolean) => {
if (pw && valid) {
rePasswordField.setCustomValidity("");
submitButton.disabled = false;
submitSpan.removeAttribute("disabled");
} else if (!pw) {
rePasswordField.setCustomValidity(window.invalidPassword); rePasswordField.setCustomValidity(window.invalidPassword);
submitButton.disabled = true; submitButton.disabled = true;
submitSpan.setAttribute("disabled", ""); submitSpan.setAttribute("disabled", "");
} else { } else {
rePasswordField.setCustomValidity(""); rePasswordField.setCustomValidity("");
submitButton.disabled = false; submitButton.disabled = true;
submitSpan.removeAttribute("disabled"); submitSpan.setAttribute("disabled", "");
} }
});
}; };
rePasswordField.addEventListener("keyup", checkPasswords);
passwordField.addEventListener("keyup", checkPasswords); rePasswordField.addEventListener("keyup", checkValidity);
passwordField.addEventListener("keyup", checkValidity);
// Incredible code right here // Incredible code right here
@ -150,5 +163,5 @@ export function initValidator(passwordField: HTMLInputElement, rePasswordField:
} }
} }
} }
return requirements return [requirements, checkValidity]
} }

View File

@ -316,6 +316,7 @@ func (app *appContext) GenCaptcha(gc *gin.Context) {
captchaID := genAuthToken() captchaID := genAuthToken()
inv.Captchas[captchaID] = capt inv.Captchas[captchaID] = capt
app.storage.invites[code] = inv app.storage.invites[code] = inv
app.storage.storeInvites()
gc.JSON(200, genCaptchaDTO{captchaID}) gc.JSON(200, genCaptchaDTO{captchaID})
return return
} }