- 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).
139 lines
4.9 KiB
HTML
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">✉</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>
|