481 lines
16 KiB
TypeScript
481 lines
16 KiB
TypeScript
// @ts-ignore
|
|
const {$, $a, doAfterLoad, footer, header, nav} = window.fwdekker;
|
|
|
|
import {csrfToken, getApi, postApi, sharedMessageElement} from "./API";
|
|
import {CustomEventHandler} from "./CustomEventHandler";
|
|
import {clearMessages, showError, showSuccess, showWarning} from "./Message";
|
|
|
|
|
|
/**
|
|
* Handles listeners to be invoked when the user logs in.
|
|
*/
|
|
const loginHandler = new CustomEventHandler();
|
|
/**
|
|
* Handles listeners to be invoked when the user logs out.
|
|
*/
|
|
const logoutHandler = new CustomEventHandler();
|
|
|
|
|
|
/**
|
|
* Refreshes the list of trackings in the table.
|
|
*/
|
|
function refreshTrackings(): void {
|
|
getApi(
|
|
{action: "list-trackings"},
|
|
sharedMessageElement,
|
|
(response) => {
|
|
// Remove old rows
|
|
$a("#trackings tbody tr:not(:last-child)").forEach((it: HTMLTableRowElement) => it.remove());
|
|
|
|
// Insert new rows
|
|
const tableBody = $("#trackings tbody");
|
|
const lastRow = $("#trackings tbody tr:last-child");
|
|
response.payload.forEach((tracking: any) => {
|
|
const row = document.createElement("tr");
|
|
|
|
const nameCell = document.createElement("td");
|
|
const nameLink = document.createElement("a");
|
|
nameLink.href = "https://en.wikipedia.org/wiki/" + tracking.name;
|
|
nameLink.innerText = tracking.name;
|
|
if (tracking.deleted)
|
|
nameLink.classList.add("redLink");
|
|
nameCell.append(nameLink);
|
|
row.append(nameCell);
|
|
|
|
const statusCell = document.createElement("td");
|
|
statusCell.innerText =
|
|
tracking.deleted
|
|
? "page not found"
|
|
: (tracking.name === "Adolf Hitler" ? "deceased 🥳" : tracking.status);
|
|
row.append(statusCell);
|
|
|
|
const deleteCell = document.createElement("td");
|
|
const deleteForm = document.createElement("form");
|
|
deleteForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{action: "remove-tracking", token: csrfToken, person_name: tracking.name},
|
|
deleteForm,
|
|
() => refreshTrackings()
|
|
)
|
|
});
|
|
const deleteButton = document.createElement("button");
|
|
deleteButton.innerText = "remove";
|
|
deleteForm.append(deleteButton);
|
|
deleteCell.append(deleteForm);
|
|
row.append(deleteCell);
|
|
|
|
tableBody.insertBefore(row, lastRow);
|
|
});
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Refreshes displays of the user's data.
|
|
*/
|
|
function refreshUserData(): void {
|
|
getApi(
|
|
{action: "get-user-data"},
|
|
sharedMessageElement,
|
|
(response) => {
|
|
const userData = response.payload;
|
|
|
|
// Email
|
|
$("#emailCurrent").innerText = userData.email;
|
|
$("#emailVerified").innerText = userData.email_verified ? "yes" : "no";
|
|
if (!userData.email_verified) {
|
|
showWarning(
|
|
sharedMessageElement,
|
|
"You will not receive any email notifications until you verify your email address. " +
|
|
"Check your inbox for further instructions."
|
|
);
|
|
$("#resendEmailVerificationButton").classList.remove("hidden");
|
|
}
|
|
|
|
// Password update time
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0)
|
|
const updateTime = new Date(userData.password_last_change * 1000);
|
|
updateTime.setHours(0, 0, 0, 0);
|
|
const diff = (+today - +updateTime) / 86400000;
|
|
$("#passwordLastChanged").innerText = diff === 0 ? "today" : diff + " days ago";
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Redirects the user to `target` after `seconds` seconds, calling `doEachSecond` after every second.
|
|
*
|
|
* @param target the location to redirect the user to after the timeout
|
|
* @param seconds the number of seconds before redirecting the user
|
|
* @param doEachSecond the function to invoke each second; the only argument is the number of seconds left at that time
|
|
*/
|
|
function redirectWithTimeout(target: string, seconds: number, doEachSecond: (secondsLeft: number) => void): void {
|
|
let secondsLeft = seconds;
|
|
const update = () => {
|
|
doEachSecond(secondsLeft);
|
|
secondsLeft--;
|
|
setTimeout(update, 1000);
|
|
};
|
|
update();
|
|
|
|
setTimeout(() => window.location.href = target, seconds * 1000);
|
|
}
|
|
|
|
|
|
// Initialize template
|
|
doAfterLoad(() => {
|
|
$("#nav").appendChild(nav("/Tools/Death-Notifier/"));
|
|
$("#header").appendChild(header({
|
|
title: "Death Notifier",
|
|
description: "Get notified when a famous person dies"
|
|
}));
|
|
$("#footer").appendChild(footer({
|
|
vcsURL: "https://git.fwdekker.com/tools/death-notifier/",
|
|
version: "v%%VERSION_NUMBER%%"
|
|
}));
|
|
$("main").classList.remove("hidden");
|
|
});
|
|
|
|
// Register event handlers
|
|
doAfterLoad(() => {
|
|
loginHandler.addListener(() => {
|
|
refreshUserData();
|
|
refreshTrackings();
|
|
|
|
$("#loginRow").classList.add("hidden")
|
|
$("#trackingRow").classList.remove("hidden");
|
|
$("#accountRow").classList.remove("hidden");
|
|
});
|
|
logoutHandler.addListener(() => {
|
|
$("#loginRow").classList.remove("hidden")
|
|
$("#trackingRow").classList.add("hidden");
|
|
$("#accountRow").classList.add("hidden");
|
|
});
|
|
|
|
|
|
// Login
|
|
const loginForm = $("#loginForm");
|
|
loginForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{
|
|
action: "login",
|
|
token: csrfToken,
|
|
email: $("#loginEmail").value,
|
|
password: $("#loginPassword").value,
|
|
},
|
|
event.target as HTMLFormElement,
|
|
() => loginHandler.invokeListeners()
|
|
);
|
|
});
|
|
loginHandler.addListener(() => {
|
|
loginForm.reset();
|
|
clearMessages(loginForm);
|
|
});
|
|
|
|
const registerForm = $("#registerForm");
|
|
registerForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{
|
|
action: "register",
|
|
token: csrfToken,
|
|
email: $("#registerEmail").value,
|
|
password: $("#registerPassword").value,
|
|
password_confirm: $("#registerPasswordConfirm").value,
|
|
},
|
|
registerForm,
|
|
() => {
|
|
// TODO: Add client-side form validation
|
|
registerForm.reset();
|
|
showSuccess(
|
|
$(".formValidationInfo", registerForm),
|
|
"Account created successfully! You may now log in."
|
|
);
|
|
}
|
|
);
|
|
});
|
|
loginHandler.addListener(() => {
|
|
registerForm.reset();
|
|
clearMessages(registerForm);
|
|
});
|
|
|
|
const logoutForm = $("#logoutForm");
|
|
logoutForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{
|
|
action: "logout",
|
|
token: csrfToken,
|
|
},
|
|
logoutForm,
|
|
() => {
|
|
logoutForm.reset();
|
|
logoutHandler.invokeListeners();
|
|
}
|
|
);
|
|
});
|
|
|
|
|
|
// Forgot password
|
|
const sendPasswordResetForm = $("#sendPasswordResetForm");
|
|
sendPasswordResetForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{
|
|
action: "send-password-reset",
|
|
token: csrfToken,
|
|
email: $("#sendPasswordResetEmail").value,
|
|
},
|
|
sendPasswordResetForm,
|
|
() => {
|
|
sendPasswordResetForm.reset();
|
|
showSuccess(
|
|
$(".formValidationInfo", sendPasswordResetForm),
|
|
"Password reset email sent successfully!"
|
|
);
|
|
}
|
|
);
|
|
});
|
|
|
|
const resetPasswordForm = $("#resetPasswordForm");
|
|
resetPasswordForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{
|
|
action: "reset-password",
|
|
token: csrfToken,
|
|
email: $("#resetPasswordEmail").value,
|
|
reset_token: $("#resetPasswordToken").value,
|
|
password: $("#resetPasswordPassword").value,
|
|
password_confirm: $("#resetPasswordPasswordConfirm").value,
|
|
},
|
|
resetPasswordForm,
|
|
() => {
|
|
redirectWithTimeout(
|
|
"./", 3, (secondsLeft) => {
|
|
showSuccess(
|
|
$(".formValidationInfo", resetPasswordForm),
|
|
`Your password has been updated. You will be redirected after ${secondsLeft} seconds.`
|
|
);
|
|
}
|
|
)
|
|
}
|
|
);
|
|
});
|
|
|
|
$("#forgotPasswordGoTo").addEventListener("click", (event: MouseEvent) => {
|
|
event.preventDefault();
|
|
|
|
$("#sendPasswordResetEmail").value = $("#loginEmail").value;
|
|
|
|
$("#loginRow").classList.add("hidden");
|
|
$("#sendForgotPasswordRow").classList.remove("hidden");
|
|
});
|
|
$("#forgotPasswordGoBack").addEventListener("click", (event: MouseEvent) => {
|
|
event.preventDefault();
|
|
|
|
$("#loginEmail").value = $("#sendPasswordResetEmail").value;
|
|
|
|
$("#sendPasswordResetForm").reset();
|
|
$("#sendForgotPasswordRow").classList.add("hidden");
|
|
$("#loginRow").classList.remove("hidden");
|
|
});
|
|
|
|
|
|
// Account management
|
|
const updateEmailForm = $("#updateEmailForm");
|
|
updateEmailForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{
|
|
action: "update-email",
|
|
token: csrfToken,
|
|
email: $("#updateEmailEmail").value,
|
|
},
|
|
updateEmailForm,
|
|
() => {
|
|
updateEmailForm.reset();
|
|
refreshUserData();
|
|
showSuccess(
|
|
$(".formValidationInfo", updateEmailForm),
|
|
"Email address updated successfully! " +
|
|
"Check your inbox for the verification email. " +
|
|
"You will not receive notifications until you verify your email address."
|
|
);
|
|
}
|
|
);
|
|
});
|
|
logoutHandler.addListener(() => {
|
|
updateEmailForm.reset();
|
|
clearMessages(updateEmailForm);
|
|
});
|
|
|
|
const resendEmailVerificationForm = $("#resendEmailVerificationForm");
|
|
resendEmailVerificationForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{action: "resend-verify-email", token: csrfToken},
|
|
resendEmailVerificationForm,
|
|
() => {
|
|
resendEmailVerificationForm.reset();
|
|
refreshUserData();
|
|
showSuccess(
|
|
$(".formValidationInfo", resendEmailVerificationForm),
|
|
"Email verification resent successfully!"
|
|
);
|
|
}
|
|
);
|
|
});
|
|
logoutHandler.addListener(() => {
|
|
resendEmailVerificationForm.reset();
|
|
clearMessages(resendEmailVerificationForm);
|
|
});
|
|
|
|
const updatePasswordForm = $("#updatePasswordForm");
|
|
updatePasswordForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{
|
|
action: "update-password",
|
|
token: csrfToken,
|
|
password_old: $("#updatePasswordPasswordOld").value,
|
|
password_new: $("#updatePasswordPasswordNew").value,
|
|
password_confirm: $("#updatePasswordPasswordConfirm").value,
|
|
},
|
|
event.target as HTMLFormElement,
|
|
() => {
|
|
updatePasswordForm.reset();
|
|
refreshUserData();
|
|
showSuccess($(".formValidationInfo", updatePasswordForm), "Password updated successfully!");
|
|
}
|
|
);
|
|
});
|
|
logoutHandler.addListener(() => {
|
|
updatePasswordForm.reset();
|
|
clearMessages(updatePasswordForm);
|
|
});
|
|
|
|
|
|
// Tracking management
|
|
const addTrackingForm = $("#addTrackingForm");
|
|
addTrackingForm.addEventListener("submit", (event: SubmitEvent) => {
|
|
event.preventDefault();
|
|
|
|
postApi(
|
|
{
|
|
action: "add-tracking",
|
|
token: csrfToken,
|
|
person_name: $("#addTrackingPersonName").value,
|
|
},
|
|
addTrackingForm,
|
|
() => {
|
|
addTrackingForm.reset();
|
|
refreshTrackings();
|
|
}
|
|
);
|
|
});
|
|
logoutHandler.addListener(() => {
|
|
addTrackingForm.reset();
|
|
clearMessages(addTrackingForm);
|
|
});
|
|
});
|
|
|
|
// Run initialization code
|
|
doAfterLoad(() => {
|
|
const get_params = new URLSearchParams(window.location.search);
|
|
getApi(
|
|
{action: "start-session"},
|
|
sharedMessageElement,
|
|
() => {
|
|
if (!get_params.has("action"))
|
|
loginHandler.invokeListeners();
|
|
},
|
|
() => {
|
|
if (!get_params.has("action"))
|
|
logoutHandler.invokeListeners();
|
|
},
|
|
() => {
|
|
// Do nothing
|
|
},
|
|
() => {
|
|
let valid_action_params = true;
|
|
switch (get_params.get("action")) {
|
|
case "verify-email":
|
|
if (get_params.has("email") && get_params.has("token")) {
|
|
postApi(
|
|
{
|
|
action: "verify-email",
|
|
token: csrfToken,
|
|
email: get_params.get("email"),
|
|
verify_token: get_params.get("token"),
|
|
},
|
|
sharedMessageElement,
|
|
() => {
|
|
redirectWithTimeout(
|
|
"./", 3, (secondsLeft) => {
|
|
showSuccess(
|
|
sharedMessageElement,
|
|
`Your email address has been verified. ` +
|
|
`You will be redirected after ${secondsLeft} seconds.`
|
|
);
|
|
});
|
|
},
|
|
() => $("#sharedHomeLink").classList.remove("hidden")
|
|
);
|
|
} else {
|
|
valid_action_params = false;
|
|
}
|
|
break;
|
|
case "reset-password":
|
|
if (get_params.has("email") && get_params.has("token")) {
|
|
getApi(
|
|
{
|
|
action: "validate-password-reset-token",
|
|
token: csrfToken ?? "",
|
|
email: get_params.get("email") ?? "",
|
|
reset_token: get_params.get("token") ?? "",
|
|
},
|
|
sharedMessageElement,
|
|
() => {
|
|
$("#resetPasswordEmail").value = get_params.get("email");
|
|
$("#resetPasswordToken").value = get_params.get("token");
|
|
$("#resetPasswordRow").classList.remove("hidden");
|
|
}
|
|
);
|
|
} else {
|
|
valid_action_params = false;
|
|
}
|
|
break;
|
|
case null:
|
|
break;
|
|
default:
|
|
valid_action_params = false;
|
|
break;
|
|
}
|
|
|
|
if (!valid_action_params) {
|
|
redirectWithTimeout(
|
|
"./", 3, (secondsLeft) => {
|
|
showError(
|
|
sharedMessageElement,
|
|
`Invalid URL. You will be redirected after ${secondsLeft} seconds.`
|
|
);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
});
|