refactor(account): simplify passkey/token JS and modernize syntax
Replace DOMContentLoaded event-listener block with global arrow functions and inline onclick handlers. Convert string concatenation to template literals throughout.
This commit is contained in:
parent
de3e1d4bcb
commit
5acbddd65b
1 changed files with 123 additions and 144 deletions
|
|
@ -59,13 +59,14 @@
|
||||||
<button
|
<button
|
||||||
id="add-passkey-btn"
|
id="add-passkey-btn"
|
||||||
type="button"
|
type="button"
|
||||||
|
onclick="registerPasskey()"
|
||||||
class="shrink-0 rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-amber-50 transition hover:bg-amber-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
class="shrink-0 rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-amber-50 transition hover:bg-amber-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Register
|
Register
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="cancel-add-passkey"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
onclick="hidePasskeyForm()"
|
||||||
class="shrink-0 rounded-md px-3 py-2 text-sm font-medium text-stone-500 transition hover:text-stone-800"
|
class="shrink-0 rounded-md px-3 py-2 text-sm font-medium text-stone-500 transition hover:text-stone-800"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|
@ -80,6 +81,7 @@
|
||||||
<button
|
<button
|
||||||
id="show-add-passkey"
|
id="show-add-passkey"
|
||||||
type="button"
|
type="button"
|
||||||
|
onclick="showPasskeyForm()"
|
||||||
class="mt-3 rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-amber-50 transition hover:bg-amber-500"
|
class="mt-3 rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-amber-50 transition hover:bg-amber-500"
|
||||||
>
|
>
|
||||||
Add Passkey
|
Add Passkey
|
||||||
|
|
@ -139,13 +141,14 @@
|
||||||
<button
|
<button
|
||||||
id="create-token-btn"
|
id="create-token-btn"
|
||||||
type="button"
|
type="button"
|
||||||
|
onclick="createToken()"
|
||||||
class="shrink-0 rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-amber-50 transition hover:bg-amber-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
class="shrink-0 rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-amber-50 transition hover:bg-amber-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Create
|
Create
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="cancel-create-token"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
onclick="hideTokenForm()"
|
||||||
class="shrink-0 rounded-md px-3 py-2 text-sm font-medium text-stone-500 transition hover:text-stone-800"
|
class="shrink-0 rounded-md px-3 py-2 text-sm font-medium text-stone-500 transition hover:text-stone-800"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|
@ -159,16 +162,16 @@
|
||||||
<div class="mt-3 flex items-center gap-2">
|
<div class="mt-3 flex items-center gap-2">
|
||||||
<code id="token-value" class="flex-1 rounded bg-white px-3 py-2 text-sm font-mono text-stone-800 border border-green-200 break-all select-all"></code>
|
<code id="token-value" class="flex-1 rounded bg-white px-3 py-2 text-sm font-mono text-stone-800 border border-green-200 break-all select-all"></code>
|
||||||
<button
|
<button
|
||||||
id="copy-token-btn"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
onclick="copyToken(this)"
|
||||||
class="shrink-0 rounded-md bg-green-600 px-3 py-1.5 text-sm font-medium text-green-50 transition hover:bg-green-500"
|
class="shrink-0 rounded-md bg-green-600 px-3 py-1.5 text-sm font-medium text-green-50 transition hover:bg-green-500"
|
||||||
>
|
>
|
||||||
Copy
|
Copy
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
id="dismiss-token"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
onclick="window.location.reload()"
|
||||||
class="mt-2 text-sm text-stone-500 hover:text-stone-700"
|
class="mt-2 text-sm text-stone-500 hover:text-stone-700"
|
||||||
>
|
>
|
||||||
Dismiss
|
Dismiss
|
||||||
|
|
@ -178,6 +181,7 @@
|
||||||
<button
|
<button
|
||||||
id="show-create-token"
|
id="show-create-token"
|
||||||
type="button"
|
type="button"
|
||||||
|
onclick="showTokenForm()"
|
||||||
class="mt-3 rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-amber-50 transition hover:bg-amber-500"
|
class="mt-3 rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-amber-50 transition hover:bg-amber-500"
|
||||||
>
|
>
|
||||||
Create Token
|
Create Token
|
||||||
|
|
@ -247,87 +251,76 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
// --- Passkey form ---
|
||||||
// --- Passkey management ---
|
|
||||||
const showAddPasskey = document.getElementById("show-add-passkey");
|
|
||||||
const addPasskeyForm = document.getElementById("add-passkey-form");
|
|
||||||
const cancelAddPasskey = document.getElementById("cancel-add-passkey");
|
|
||||||
const addPasskeyBtn = document.getElementById("add-passkey-btn");
|
|
||||||
const passkeyNameInput = document.getElementById("passkey-name");
|
|
||||||
const addPasskeyError = document.getElementById("add-passkey-error");
|
|
||||||
const addPasskeyLoading = document.getElementById("add-passkey-loading");
|
|
||||||
|
|
||||||
showAddPasskey.addEventListener("click", function () {
|
const showPasskeyForm = () => {
|
||||||
addPasskeyForm.classList.remove("hidden");
|
document.getElementById("add-passkey-form").classList.remove("hidden");
|
||||||
showAddPasskey.classList.add("hidden");
|
document.getElementById("show-add-passkey").classList.add("hidden");
|
||||||
passkeyNameInput.focus();
|
document.getElementById("passkey-name").focus();
|
||||||
});
|
};
|
||||||
|
|
||||||
cancelAddPasskey.addEventListener("click", function () {
|
const hidePasskeyForm = () => {
|
||||||
addPasskeyForm.classList.add("hidden");
|
document.getElementById("add-passkey-form").classList.add("hidden");
|
||||||
showAddPasskey.classList.remove("hidden");
|
document.getElementById("show-add-passkey").classList.remove("hidden");
|
||||||
addPasskeyError.classList.add("hidden");
|
document.getElementById("add-passkey-error").classList.add("hidden");
|
||||||
passkeyNameInput.value = "";
|
document.getElementById("passkey-name").value = "";
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const registerPasskey = async () => {
|
||||||
|
const name = document.getElementById("passkey-name").value.trim();
|
||||||
|
const errorEl = document.getElementById("add-passkey-error");
|
||||||
|
const loadingEl = document.getElementById("add-passkey-loading");
|
||||||
|
const btn = document.getElementById("add-passkey-btn");
|
||||||
|
|
||||||
addPasskeyBtn.addEventListener("click", async function () {
|
|
||||||
const name = passkeyNameInput.value.trim();
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
addPasskeyError.textContent = "Please enter a name for this passkey.";
|
errorEl.textContent = "Please enter a name for this passkey.";
|
||||||
addPasskeyError.classList.remove("hidden");
|
errorEl.classList.remove("hidden");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPasskeyError.classList.add("hidden");
|
errorEl.classList.add("hidden");
|
||||||
addPasskeyLoading.classList.remove("hidden");
|
loadingEl.classList.remove("hidden");
|
||||||
addPasskeyBtn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await addPasskey(name);
|
await addPasskey(name);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addPasskeyError.textContent = err.message;
|
errorEl.textContent = err.message;
|
||||||
addPasskeyError.classList.remove("hidden");
|
errorEl.classList.remove("hidden");
|
||||||
addPasskeyLoading.classList.add("hidden");
|
loadingEl.classList.add("hidden");
|
||||||
addPasskeyBtn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
// --- Token management ---
|
// --- Token form ---
|
||||||
const showCreateToken = document.getElementById("show-create-token");
|
|
||||||
const createTokenForm = document.getElementById("create-token-form");
|
|
||||||
const cancelCreateToken = document.getElementById("cancel-create-token");
|
|
||||||
const createTokenBtn = document.getElementById("create-token-btn");
|
|
||||||
const tokenNameInput = document.getElementById("token-name");
|
|
||||||
const createTokenError = document.getElementById("create-token-error");
|
|
||||||
const tokenCreated = document.getElementById("token-created");
|
|
||||||
const tokenValue = document.getElementById("token-value");
|
|
||||||
const copyTokenBtn = document.getElementById("copy-token-btn");
|
|
||||||
const dismissToken = document.getElementById("dismiss-token");
|
|
||||||
|
|
||||||
showCreateToken.addEventListener("click", function () {
|
const showTokenForm = () => {
|
||||||
createTokenForm.classList.remove("hidden");
|
document.getElementById("create-token-form").classList.remove("hidden");
|
||||||
showCreateToken.classList.add("hidden");
|
document.getElementById("show-create-token").classList.add("hidden");
|
||||||
tokenNameInput.focus();
|
document.getElementById("token-name").focus();
|
||||||
});
|
};
|
||||||
|
|
||||||
cancelCreateToken.addEventListener("click", function () {
|
const hideTokenForm = () => {
|
||||||
createTokenForm.classList.add("hidden");
|
document.getElementById("create-token-form").classList.add("hidden");
|
||||||
showCreateToken.classList.remove("hidden");
|
document.getElementById("show-create-token").classList.remove("hidden");
|
||||||
createTokenError.classList.add("hidden");
|
document.getElementById("create-token-error").classList.add("hidden");
|
||||||
tokenNameInput.value = "";
|
document.getElementById("token-name").value = "";
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const createToken = async () => {
|
||||||
|
const name = document.getElementById("token-name").value.trim();
|
||||||
|
const errorEl = document.getElementById("create-token-error");
|
||||||
|
const btn = document.getElementById("create-token-btn");
|
||||||
|
|
||||||
createTokenBtn.addEventListener("click", async function () {
|
|
||||||
const name = tokenNameInput.value.trim();
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
createTokenError.textContent = "Please enter a name for this token.";
|
errorEl.textContent = "Please enter a name for this token.";
|
||||||
createTokenError.classList.remove("hidden");
|
errorEl.classList.remove("hidden");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
createTokenError.classList.add("hidden");
|
errorEl.classList.add("hidden");
|
||||||
createTokenBtn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/v1/tokens", {
|
const response = await fetch("/api/v1/tokens", {
|
||||||
|
|
@ -337,54 +330,40 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to create token (HTTP " + response.status + ").");
|
throw new Error(`Failed to create token (HTTP ${response.status}).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Show the one-time token value
|
document.getElementById("create-token-form").classList.add("hidden");
|
||||||
createTokenForm.classList.add("hidden");
|
document.getElementById("token-value").textContent = data.token;
|
||||||
tokenValue.textContent = data.token;
|
document.getElementById("token-created").classList.remove("hidden");
|
||||||
tokenCreated.classList.remove("hidden");
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
createTokenError.textContent = err.message;
|
errorEl.textContent = err.message;
|
||||||
createTokenError.classList.remove("hidden");
|
errorEl.classList.remove("hidden");
|
||||||
createTokenBtn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
copyTokenBtn.addEventListener("click", function () {
|
const copyToken = (btn) => {
|
||||||
const token = tokenValue.textContent;
|
const token = document.getElementById("token-value").textContent;
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
navigator.clipboard.writeText(token).then(function () {
|
navigator.clipboard.writeText(token).then(() => {
|
||||||
copyTokenBtn.textContent = "Copied!";
|
btn.textContent = "Copied!";
|
||||||
setTimeout(function () {
|
setTimeout(() => { btn.textContent = "Copy"; }, 2000);
|
||||||
copyTokenBtn.textContent = "Copy";
|
|
||||||
}, 2000);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Fallback: select the text
|
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
range.selectNodeContents(tokenValue);
|
range.selectNodeContents(document.getElementById("token-value"));
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
dismissToken.addEventListener("click", function () {
|
// --- Data management ---
|
||||||
tokenCreated.classList.add("hidden");
|
|
||||||
showCreateToken.classList.remove("hidden");
|
|
||||||
tokenNameInput.value = "";
|
|
||||||
createTokenBtn.disabled = false;
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
const restoreFromFile = async (input) => {
|
||||||
|
|
||||||
// --- Global functions for inline onclick/onchange handlers ---
|
|
||||||
|
|
||||||
async function restoreFromFile(input) {
|
|
||||||
const file = input.files[0];
|
const file = input.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
input.value = "";
|
input.value = "";
|
||||||
|
|
@ -412,7 +391,7 @@
|
||||||
throw new Error("Database is not empty. Restore requires an empty database.");
|
throw new Error("Database is not empty. Restore requires an empty database.");
|
||||||
}
|
}
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Restore failed (HTTP " + response.status + ").");
|
throw new Error(`Restore failed (HTTP ${response.status}).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
status.textContent = "Backup restored successfully.";
|
status.textContent = "Backup restored successfully.";
|
||||||
|
|
@ -421,13 +400,13 @@
|
||||||
error.textContent = err instanceof SyntaxError ? "Invalid JSON file." : err.message;
|
error.textContent = err instanceof SyntaxError ? "Invalid JSON file." : err.message;
|
||||||
error.classList.remove("hidden");
|
error.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
async function deletePasskey(id, name) {
|
const deletePasskey = async (id, name) => {
|
||||||
if (!confirm('Delete passkey "' + name + '"? This cannot be undone.')) return;
|
if (!confirm(`Delete passkey "${name}"? This cannot be undone.`)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/v1/passkeys/" + id, { method: "DELETE" });
|
const response = await fetch(`/api/v1/passkeys/${id}`, { method: "DELETE" });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else if (response.status === 409) {
|
} else if (response.status === 409) {
|
||||||
|
|
@ -436,23 +415,23 @@
|
||||||
alert("Failed to delete passkey.");
|
alert("Failed to delete passkey.");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert("Failed to delete passkey: " + err.message);
|
alert(`Failed to delete passkey: ${err.message}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
async function revokeToken(id, name) {
|
const revokeToken = async (id, name) => {
|
||||||
if (!confirm('Revoke token "' + name + '"? This cannot be undone.')) return;
|
if (!confirm(`Revoke token "${name}"? This cannot be undone.`)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/v1/tokens/" + id + "/revoke", { method: "POST" });
|
const response = await fetch(`/api/v1/tokens/${id}/revoke`, { method: "POST" });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to revoke token.");
|
alert("Failed to revoke token.");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert("Failed to revoke token: " + err.message);
|
alert(`Failed to revoke token: ${err.message}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue