Wire account verification end-to-end and fix an RFC 5322 mail bug (#1)
This commit is contained in:
parent
f4651e2c8d
commit
4e52a9c418
2 changed files with 55 additions and 0 deletions
|
|
@ -45,6 +45,8 @@ async function readJson(res) {
|
|||
if (detail && typeof detail === "object" && detail.reason) msg = detail.reason;
|
||||
else if (detail === "LOGIN_BAD_CREDENTIALS") msg = "Wrong email or password.";
|
||||
else if (detail === "REGISTER_USER_ALREADY_EXISTS") msg = "That email is already registered.";
|
||||
else if (detail === "VERIFY_USER_BAD_TOKEN") msg = "That verification link is invalid or expired.";
|
||||
else if (detail === "VERIFY_USER_ALREADY_VERIFIED") msg = "This account is already verified.";
|
||||
else if (typeof detail === "string") msg = detail.replace(/_/g, " ").toLowerCase();
|
||||
else msg = `Request failed (${res.status}).`;
|
||||
throw new Error(msg);
|
||||
|
|
@ -84,6 +86,10 @@ async function register(email, password) {
|
|||
const res = await apiFetch("api/v2/auth/register", { method: "POST", json: { email, password } });
|
||||
await readJson(res);
|
||||
}
|
||||
async function verifyEmail(token) {
|
||||
const res = await apiFetch("api/v2/auth/verify", { method: "POST", json: { token } });
|
||||
await readJson(res);
|
||||
}
|
||||
export async function logout() {
|
||||
try { await apiFetch("api/v2/auth/logout", { method: "POST" }); } catch (e) {}
|
||||
currentUser = null;
|
||||
|
|
@ -375,6 +381,27 @@ function escapeHtml(s) {
|
|||
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
||||
}
|
||||
|
||||
// --- toast ---------------------------------------------------------------
|
||||
// One-off status messages (e.g. "email verified") that aren't tied to the auth
|
||||
// modal, which may not even be open. Success/failure share the same neutral
|
||||
// style — the text says which, not the color; isError only changes the
|
||||
// live-region role (alert = assertive, status = polite).
|
||||
let toastTimer = null;
|
||||
|
||||
function showToast(msg, isError = false) {
|
||||
let el = document.querySelector(".acct-toast");
|
||||
if (!el) {
|
||||
el = document.createElement("div");
|
||||
el.className = "acct-toast";
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
el.textContent = msg;
|
||||
el.setAttribute("role", isError ? "alert" : "status");
|
||||
el.hidden = false;
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => { el.hidden = true; }, 6000);
|
||||
}
|
||||
|
||||
// --- boot --------------------------------------------------------------------
|
||||
(async function initAccount() {
|
||||
ensureAcctEl();
|
||||
|
|
@ -382,4 +409,21 @@ function escapeHtml(s) {
|
|||
await refreshUser(); // then confirm session from the cookie
|
||||
renderHeader();
|
||||
emitAuth();
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const token = params.get("verify_token");
|
||||
if (token) {
|
||||
params.delete("verify_token");
|
||||
const clean = location.pathname + (params.toString() ? `?${params}` : "") + location.hash;
|
||||
history.replaceState(null, "", clean);
|
||||
try {
|
||||
await verifyEmail(token);
|
||||
await refreshUser();
|
||||
renderHeader();
|
||||
emitAuth();
|
||||
showToast("Email verified — you're all set.");
|
||||
} catch (err) {
|
||||
showToast(err.message || "Verification link is invalid or expired.", true);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -402,6 +402,17 @@ main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; }
|
|||
font: inherit; font-weight: 600; padding: 0; text-decoration: underline;
|
||||
}
|
||||
|
||||
/* One-off status toast (e.g. "email verified") — neutral for both success and
|
||||
failure; the text says which, not the color. */
|
||||
.acct-toast {
|
||||
position: fixed; left: 50%; bottom: 24px; z-index: 1200;
|
||||
transform: translateX(-50%);
|
||||
max-width: calc(100vw - 32px); padding: 12px 18px; border-radius: 10px;
|
||||
background: var(--surface); border: 1px solid var(--border); color: var(--text);
|
||||
font-size: 14px; box-shadow: 0 12px 30px rgba(0, 0, 0, .4);
|
||||
}
|
||||
.acct-toast[hidden] { display: none; }
|
||||
|
||||
/* --- alerts page (subscriptions) --- */
|
||||
.alerts-loading { color: var(--muted); }
|
||||
.alerts-gate {
|
||||
|
|
|
|||
Loading…
Reference in a new issue