Files
dre/frontend/login.html
T
root 7a62b0b340 Backend Aug 22-25: AI analysis, welcome-packet templating, LetterStream, DocuSeal, staff RBAC + tier gate
- analysis.py: deterministic claim scorer + /analyze /approve /letter /advance-tier endpoints (auto-runs on intake)
- packet.py + packet_fields.json: welcome-packet templating engine (6 onboarding docs, field catalog)
- letterstream.py + letters.py: certified-mail send pipeline + letter lifecycle (webhook verified)
- docuseal.py: DocuSeal signing integration
- staff.py/models.py/schema.sql/auth.py: approval actor from staff key, tier gate (APPROVED+ACTIVE+onboarding docs), onboarding_docs table
- frontend/: dependency-free static portal (intake, magic-link login/verify, dashboard)
- landing-mockups/: 4 design-stance mockups + favicons
- legal/: aup/privacy/sms-terms/terms HTML
- docs/: letter-queue scope, letterstream API contract, 6 welcome-packet templates
- review-dre-landing-2026-08-21.md: 3-variant landing feedback sprint
- compliance/DRE_Compliance_Manual.md: updated

Source synced from deployed /opt/dre-portal/app/ (was 4 days ahead of git).
2026-08-26 02:26:33 -04:00

139 lines
4.9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client Login - Debt Recovery Experts</title>
<meta name="description" content="Log in to your Debt Recovery Experts client portal to track claim status, upload documents, and message the recovery team.">
<link rel="stylesheet" href="css/dre.css">
</head>
<body>
<header class="dre-header">
<div class="dre-header-inner">
<a class="dre-brand" href="index.html">
<span class="dre-mark">DRE</span>
<span>
Debt Recovery Experts
<span class="dre-brand-sub">Client Portal</span>
</span>
</a>
<div class="dre-header-actions">
<a href="start.html">Submit a Claim</a>
</div>
</div>
</header>
<main class="dre-main dre-narrow">
<div class="dre-intro">
<h1>Client Login</h1>
<p>Enter the email address on file for your account. We will send you a secure, one-time
login link. No password required.</p>
<p class="dre-small">New here? <a href="start.html">Submit a claim</a> - no account needed.</p>
</div>
<div id="login-alert" class="dre-alert dre-alert-error" role="alert"></div>
<!-- ============================== REQUEST FORM ============================== -->
<form id="login-form" class="dre-card">
<div class="dre-field">
<label for="email">Email Address <span class="dre-required">*</span></label>
<input type="email" id="email" name="email" maxlength="254" required autocomplete="email" autofocus>
<span class="dre-field-error" data-error-for="email"></span>
</div>
<button type="submit" id="login-submit" class="dre-btn dre-btn-primary dre-btn-block">
Email Me a Login Link
</button>
</form>
<!-- ============================== SENT STATE ============================== -->
<div id="login-sent" class="dre-card dre-state dre-hidden">
<div class="dre-state-icon info">&#9993;</div>
<h2>Check Your Email</h2>
<p>If an account exists for that email address, we have sent a secure login link. The link
expires in 15 minutes and can only be used once.</p>
<p class="dre-small">Did not get an email? Check your spam folder, or
<a href="#" id="login-try-again">try a different email address</a>.</p>
</div>
</main>
<footer class="dre-footer">
Having trouble accessing your account? Contact your Debt Recovery Experts representative.
</footer>
<script src="js/dre-api.js"></script>
<script>
(function () {
"use strict";
// If already logged in, skip straight to the dashboard.
if (DRE.getSessionToken()) {
window.location.href = "dashboard.html";
return;
}
var form = document.getElementById("login-form");
var alertEl = document.getElementById("login-alert");
var submitBtn = document.getElementById("login-submit");
var sentEl = document.getElementById("login-sent");
var tryAgainLink = document.getElementById("login-try-again");
tryAgainLink.addEventListener("click", function (e) {
e.preventDefault();
sentEl.classList.add("dre-hidden");
form.classList.remove("dre-hidden");
document.getElementById("email").focus();
});
form.addEventListener("submit", function (e) {
e.preventDefault();
DRE.hideAlert(alertEl);
var emailField = document.getElementById("email");
var email = emailField.value.trim();
var errEl = form.querySelector('[data-error-for="email"]');
errEl.classList.remove("is-visible");
emailField.removeAttribute("aria-invalid");
if (!email) {
errEl.textContent = "Email address is required.";
errEl.classList.add("is-visible");
emailField.setAttribute("aria-invalid", "true");
return;
}
DRE.setLoading(submitBtn, true, "Sending...");
DRE.apiFetch("/api/auth/request", { method: "POST", body: { email: email } }).then(function (result) {
DRE.setLoading(submitBtn, false, null, "Email Me a Login Link");
// Backend responds 200 with a generic anti-enumeration message on
// success. Only a genuine transport/rate-limit failure should show
// an error; otherwise always show the "check your email" state.
if (result.ok) {
form.classList.add("dre-hidden");
sentEl.classList.remove("dre-hidden");
sentEl.scrollIntoView({ behavior: "smooth", block: "start" });
return;
}
var code = DRE.getErrorCode(result);
if (code === "rate_limited") {
DRE.showAlert(alertEl, DRE.getErrorMessage(result, "Too many requests. Please try again later."));
} else if (code === "validation_error") {
errEl.textContent = DRE.getErrorMessage(result, "Enter a valid email address.");
errEl.classList.add("is-visible");
emailField.setAttribute("aria-invalid", "true");
} else {
DRE.showAlert(alertEl, DRE.getErrorMessage(result, "We could not send the login link. Please try again."));
}
});
});
})();
</script>
</body>
</html>