login
This commit is contained in:
@@ -132,8 +132,8 @@ function switchAuthTab(tab) {
|
||||
document.getElementById("signin-error").style.display = "none";
|
||||
document.getElementById("signup-error").style.display = "none";
|
||||
document.getElementById("signup-success").style.display = "none";
|
||||
const _rpw = document.getElementById("reset-pw-wrap");
|
||||
if (_rpw) _rpw.style.display = "none";
|
||||
const note = document.getElementById("signin-reset-note");
|
||||
if (note) note.style.display = "none";
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
@@ -1294,3 +1294,144 @@ function exportInvoicesCSV() {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
HELPERS
|
||||
═══════════════════════════════════════════════════════════════ */
|
||||
function escHtml(s) {
|
||||
if (typeof s !== "string" && typeof s !== "number") return "";
|
||||
return String(s)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
function escAttr(s) {
|
||||
if (typeof s !== "string" && typeof s !== "number") return "";
|
||||
return String(s)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function isValidEmail(e) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e);
|
||||
}
|
||||
|
||||
function formatBackupDate(s) {
|
||||
if (!s) return "—";
|
||||
const d = new Date(s);
|
||||
if (isNaN(d.getTime())) return s;
|
||||
return d.toLocaleString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
function formatDate(s) {
|
||||
if (!s) return "—";
|
||||
const d = new Date(s);
|
||||
if (isNaN(d.getTime())) return s;
|
||||
const month = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
const year = d.getFullYear();
|
||||
const hour = String(d.getHours()).padStart(2, "0");
|
||||
const minute = String(d.getMinutes()).padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hour}:${minute}`;
|
||||
}
|
||||
|
||||
function show(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.style.display = "";
|
||||
}
|
||||
|
||||
function hide(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.style.display = "none";
|
||||
}
|
||||
|
||||
function btnLoad(btn, label) {
|
||||
if (!btn) return;
|
||||
btn._origHTML = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML =
|
||||
label +
|
||||
' <span class="spinner-sm" style="display:inline-block;vertical-align:middle;margin-left:4px"></span>';
|
||||
}
|
||||
|
||||
function btnReset(btn) {
|
||||
if (!btn) return;
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = btn._origHTML || btn.innerHTML;
|
||||
}
|
||||
|
||||
function showAuthError(id, msg) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.innerHTML = `<span>${escHtml(msg)}</span>`;
|
||||
el.style.display = "flex";
|
||||
}
|
||||
|
||||
function hideAuthError(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.style.display = "none";
|
||||
}
|
||||
|
||||
function showResetOption() {
|
||||
const note = document.getElementById("signin-reset-note");
|
||||
if (note) note.style.display = "";
|
||||
}
|
||||
|
||||
async function sendPasswordReset() {
|
||||
const email = document.getElementById("signin-email").value.trim();
|
||||
if (!isValidEmail(email)) {
|
||||
toast("Please enter a valid email address.", "warning");
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById("signin-reset-btn");
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
await fetch(PW_RESET_URL, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
// Always show success — never reveal whether address exists
|
||||
const note = document.getElementById("signin-reset-note");
|
||||
if (note) note.style.display = "";
|
||||
} catch (_) {
|
||||
// silently ignore
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function setCookie(name, value, days) {
|
||||
const expires = new Date(Date.now() + days * 864e5).toUTCString();
|
||||
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
const k = `${name}=`;
|
||||
const parts = document.cookie.split("; ");
|
||||
for (const p of parts) {
|
||||
if (p.startsWith(k)) return decodeURIComponent(p.slice(k.length));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function deleteCookie(name) {
|
||||
document.cookie = `${name}=; max-age=0; path=/`;
|
||||
}
|
||||
|
||||
async function apiFetch(url, opts) {
|
||||
const headers = { ...(opts?.headers || {}) };
|
||||
if (currentSession) headers["X-Session-Id"] = currentSession;
|
||||
return fetch(url, { ...opts, headers });
|
||||
}
|
||||
|
||||
+41
-82
@@ -132,93 +132,52 @@
|
||||
</div>
|
||||
<div id="signin-error" class="auth-error"></div>
|
||||
|
||||
<!-- Password reset — shown only after a failed sign-in -->
|
||||
<div
|
||||
id="reset-pw-wrap"
|
||||
class="reset-pw-wrap"
|
||||
style="display: none"
|
||||
id="signin-footer"
|
||||
class="signin-footer"
|
||||
style="
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: stretch;
|
||||
"
|
||||
>
|
||||
<div id="reset-pw-idle" class="reset-pw-idle">
|
||||
<span class="reset-pw-hint"
|
||||
>Forgot your password?</span
|
||||
>
|
||||
<button
|
||||
id="reset-pw-btn"
|
||||
class="reset-pw-btn"
|
||||
onclick="sendPasswordReset()"
|
||||
>
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect
|
||||
x="2"
|
||||
y="4"
|
||||
width="20"
|
||||
height="16"
|
||||
rx="2"
|
||||
/>
|
||||
<polyline points="22,4 12,13 2,4" />
|
||||
</svg>
|
||||
Send reset email
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
id="reset-pw-sent"
|
||||
class="reset-pw-sent"
|
||||
style="display: none"
|
||||
<button
|
||||
type="submit"
|
||||
id="signin-btn"
|
||||
class="btn btn-primary"
|
||||
style="flex: 1"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect
|
||||
x="2"
|
||||
y="4"
|
||||
width="20"
|
||||
height="16"
|
||||
rx="2"
|
||||
/>
|
||||
<polyline points="22,4 12,13 2,4" />
|
||||
<polyline
|
||||
points="2,20 9,13"
|
||||
style="opacity: 0.5"
|
||||
/>
|
||||
<polyline
|
||||
points="22,20 15,13"
|
||||
style="opacity: 0.5"
|
||||
/>
|
||||
</svg>
|
||||
<div class="reset-pw-sent-text">
|
||||
<span class="reset-pw-sent-title"
|
||||
>Reset link sent</span
|
||||
>
|
||||
<span class="reset-pw-sent-sub"
|
||||
>Check your inbox — link expires
|
||||
in 15 minutes.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
Sign In
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="signin-reset-btn"
|
||||
class="btn btn-ghost btn-sm"
|
||||
onclick="sendPasswordReset()"
|
||||
style="
|
||||
flex: 1;
|
||||
white-space: normal;
|
||||
font-size: 12px;
|
||||
padding: 8px 10px;
|
||||
"
|
||||
>
|
||||
Send password reset
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
id="signin-btn"
|
||||
class="btn btn-primary btn-block"
|
||||
<div
|
||||
id="signin-reset-note"
|
||||
style="
|
||||
display: none;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
margin-top: 2px;
|
||||
"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
First time customer? A password reset email has
|
||||
already been sent to your address.
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- /auth-pane signin -->
|
||||
|
||||
@@ -410,6 +410,10 @@ body::after {
|
||||
padding: 20px 24px 28px;
|
||||
}
|
||||
|
||||
.signin-footer {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.auth-error {
|
||||
display: none;
|
||||
align-items: flex-start;
|
||||
|
||||
Reference in New Issue
Block a user