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).
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Submit a Claim - Debt Recovery Experts</title>
|
||||
<meta name="description" content="Submit a commercial claim for recovery review. Debt Recovery Experts helps businesses recover what they are owed through professional, compliant collection services.">
|
||||
<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">Commercial Claim Recovery</span>
|
||||
</span>
|
||||
</a>
|
||||
<div class="dre-header-actions">
|
||||
<a href="login.html">Client Login</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="dre-main dre-narrow">
|
||||
|
||||
<div class="dre-intro">
|
||||
<h1>Submit a Claim</h1>
|
||||
<p>Tell us about the outstanding balance you would like our team to review. Submission
|
||||
takes about three minutes. There is no obligation, and a member of our team will follow
|
||||
up after reviewing your claim.</p>
|
||||
</div>
|
||||
|
||||
<div id="intake-alert" class="dre-alert dre-alert-error" role="alert"></div>
|
||||
|
||||
<!-- ============================== FORM STATE ============================== -->
|
||||
<form id="intake-form" class="dre-card" novalidate>
|
||||
|
||||
<div class="dre-form-section">
|
||||
<h3>Your Information</h3>
|
||||
|
||||
<div class="dre-row">
|
||||
<div class="dre-field">
|
||||
<label for="company_name">Company Name <span class="dre-required">*</span></label>
|
||||
<input type="text" id="company_name" name="company_name" maxlength="200" required autocomplete="organization">
|
||||
<span class="dre-field-error" data-error-for="company_name"></span>
|
||||
</div>
|
||||
<div class="dre-field">
|
||||
<label for="contact_name">Your Full Name <span class="dre-required">*</span></label>
|
||||
<input type="text" id="contact_name" name="contact_name" maxlength="200" required autocomplete="name">
|
||||
<span class="dre-field-error" data-error-for="contact_name"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dre-row">
|
||||
<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">
|
||||
<span class="dre-hint">We will send your secure client-portal login link here.</span>
|
||||
<span class="dre-field-error" data-error-for="email"></span>
|
||||
</div>
|
||||
<div class="dre-field">
|
||||
<label for="phone">Phone Number</label>
|
||||
<input type="tel" id="phone" name="phone" maxlength="50" autocomplete="tel">
|
||||
<span class="dre-field-error" data-error-for="phone"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dre-form-section">
|
||||
<h3>Debtor Information</h3>
|
||||
<p class="dre-small" style="margin-bottom:1rem;">The business or individual that owes the balance.</p>
|
||||
|
||||
<div class="dre-field">
|
||||
<label for="debtor_name">Debtor Name <span class="dre-required">*</span></label>
|
||||
<input type="text" id="debtor_name" name="debtor_name" maxlength="200" required>
|
||||
<span class="dre-field-error" data-error-for="debtor_name"></span>
|
||||
</div>
|
||||
|
||||
<div class="dre-field">
|
||||
<label for="debtor_business_type">Debtor Entity Type <span class="dre-required">*</span></label>
|
||||
<select id="debtor_business_type" name="debtor_business_type" required>
|
||||
<option value="INDIVIDUAL">Individual</option>
|
||||
<option value="SOLE_PROPRIETORSHIP">Sole Proprietorship</option>
|
||||
<option value="LLC" selected>LLC</option>
|
||||
<option value="CORPORATION">Corporation</option>
|
||||
<option value="PARTNERSHIP">Partnership</option>
|
||||
<option value="OTHER">Other</option>
|
||||
</select>
|
||||
<span class="dre-field-error" data-error-for="debtor_business_type"></span>
|
||||
</div>
|
||||
|
||||
<div class="dre-row">
|
||||
<div class="dre-field">
|
||||
<label for="debtor_contact_email">Debtor Email</label>
|
||||
<input type="email" id="debtor_contact_email" name="debtor_contact_email" maxlength="254">
|
||||
<span class="dre-field-error" data-error-for="debtor_contact_email"></span>
|
||||
</div>
|
||||
<div class="dre-field">
|
||||
<label for="debtor_contact_phone">Debtor Phone</label>
|
||||
<input type="tel" id="debtor_contact_phone" name="debtor_contact_phone" maxlength="50">
|
||||
<span class="dre-field-error" data-error-for="debtor_contact_phone"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dre-field">
|
||||
<label for="debtor_physical_address">Debtor Address</label>
|
||||
<input type="text" id="debtor_physical_address" name="debtor_physical_address" maxlength="500" placeholder="Street, City, State, ZIP">
|
||||
<span class="dre-field-error" data-error-for="debtor_physical_address"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dre-form-section">
|
||||
<h3>Claim Details</h3>
|
||||
|
||||
<div class="dre-row">
|
||||
<div class="dre-field">
|
||||
<label for="amount">Amount Owed (USD) <span class="dre-required">*</span></label>
|
||||
<input type="number" id="amount" name="amount" min="0.01" max="1000000" step="0.01" required placeholder="15000.00">
|
||||
<span class="dre-hint">Maximum $1,000,000 per claim.</span>
|
||||
<span class="dre-field-error" data-error-for="amount"></span>
|
||||
</div>
|
||||
<div class="dre-field">
|
||||
<label for="invoice_date">Invoice / Debt Date</label>
|
||||
<input type="date" id="invoice_date" name="invoice_date">
|
||||
<span class="dre-field-error" data-error-for="invoice_date"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dre-field">
|
||||
<label for="client_reference">Your Invoice / PO Number</label>
|
||||
<input type="text" id="client_reference" name="client_reference" maxlength="200" placeholder="INV-2048">
|
||||
<span class="dre-field-error" data-error-for="client_reference"></span>
|
||||
</div>
|
||||
|
||||
<div class="dre-field">
|
||||
<label for="description">Description of the Debt</label>
|
||||
<textarea id="description" name="description" maxlength="5000" placeholder="What is the debt for? (service, goods, contract, etc.)"></textarea>
|
||||
<span class="dre-field-error" data-error-for="description"></span>
|
||||
</div>
|
||||
|
||||
<div class="dre-field" style="background:#fdf3e7; border:1px solid #f2ddb8; border-radius:8px; padding:0.85rem 1rem;">
|
||||
<span class="dre-small" style="color:#92600a;">
|
||||
Important: Do not enter Social Security numbers, full bank account numbers, or card
|
||||
numbers anywhere on this form. Submissions containing this information will be rejected.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dre-form-section">
|
||||
<div class="dre-checkbox-field">
|
||||
<input type="checkbox" id="tos_accepted" name="tos_accepted" required>
|
||||
<label for="tos_accepted">
|
||||
I confirm the information provided is accurate and I authorize Debt Recovery Experts
|
||||
to review and pursue recovery of this claim on my behalf. I have read and agree to the
|
||||
Terms of Service. <span class="dre-required">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<span class="dre-field-error" data-error-for="tos_accepted"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="intake-submit" class="dre-btn dre-btn-primary dre-btn-block">
|
||||
Submit Claim
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- ============================== SUCCESS STATE ============================== -->
|
||||
<div id="intake-success" class="dre-card dre-state dre-hidden">
|
||||
<div class="dre-state-icon ok">✓</div>
|
||||
<h2>Claim Received</h2>
|
||||
<p id="intake-success-message">Our team will review and contact you shortly.</p>
|
||||
<dl class="dre-kv">
|
||||
<dt>Claim Number</dt>
|
||||
<dd id="intake-claim-number">--</dd>
|
||||
<dt>Client Number</dt>
|
||||
<dd id="intake-client-number">--</dd>
|
||||
</dl>
|
||||
<p class="dre-mt">
|
||||
<strong>Check your email.</strong> We will send a secure login link to access your client
|
||||
portal, where you can track claim status, upload documents, and message our team.
|
||||
</p>
|
||||
<a href="login.html" class="dre-btn dre-btn-secondary dre-mt">Go to Client Login</a>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="dre-footer">
|
||||
Debt Recovery Experts operates in compliance with the FDCPA and applicable state debt collection
|
||||
laws. This form is for submitting claims for review only.
|
||||
</footer>
|
||||
|
||||
<script src="js/dre-api.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var form = document.getElementById("intake-form");
|
||||
var alertEl = document.getElementById("intake-alert");
|
||||
var submitBtn = document.getElementById("intake-submit");
|
||||
var successEl = document.getElementById("intake-success");
|
||||
|
||||
function clearFieldErrors() {
|
||||
var errs = form.querySelectorAll(".dre-field-error");
|
||||
for (var i = 0; i < errs.length; i++) {
|
||||
errs[i].classList.remove("is-visible");
|
||||
errs[i].textContent = "";
|
||||
}
|
||||
var inputs = form.querySelectorAll("[aria-invalid]");
|
||||
for (var j = 0; j < inputs.length; j++) {
|
||||
inputs[j].removeAttribute("aria-invalid");
|
||||
}
|
||||
}
|
||||
|
||||
function showFieldError(fieldKey, message) {
|
||||
var el = form.querySelector('[data-error-for="' + fieldKey + '"]');
|
||||
if (el) {
|
||||
el.textContent = message;
|
||||
el.classList.add("is-visible");
|
||||
}
|
||||
}
|
||||
|
||||
// Map a backend validation error location like "client.email" or
|
||||
// "debtor.name" or "claim.amount_cents" to a local field key.
|
||||
function mapLocToField(loc) {
|
||||
var map = {
|
||||
"client.company_name": "company_name",
|
||||
"client.contact_name": "contact_name",
|
||||
"client.email": "email",
|
||||
"client.phone": "phone",
|
||||
"debtor.name": "debtor_name",
|
||||
"debtor.business_type": "debtor_business_type",
|
||||
"debtor.contact_email": "debtor_contact_email",
|
||||
"debtor.contact_phone": "debtor_contact_phone",
|
||||
"debtor.physical_address": "debtor_physical_address",
|
||||
"claim.amount_cents": "amount",
|
||||
"claim.description": "description",
|
||||
"claim.client_reference": "client_reference",
|
||||
"claim.invoice_date": "invoice_date",
|
||||
"tos_accepted": "tos_accepted",
|
||||
};
|
||||
return map[loc] || null;
|
||||
}
|
||||
|
||||
function distributeValidationMessage(message) {
|
||||
// Backend joins multiple errors with "; ", each like "loc: msg"
|
||||
var parts = message.split("; ");
|
||||
var matched = false;
|
||||
parts.forEach(function (part) {
|
||||
var idx = part.indexOf(": ");
|
||||
if (idx === -1) return;
|
||||
var loc = part.slice(0, idx);
|
||||
var msg = part.slice(idx + 2);
|
||||
var field = mapLocToField(loc);
|
||||
if (field) {
|
||||
showFieldError(field, msg);
|
||||
var input = form.querySelector('[name="' + field + '"]');
|
||||
if (input) input.setAttribute("aria-invalid", "true");
|
||||
matched = true;
|
||||
}
|
||||
});
|
||||
return matched;
|
||||
}
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
DRE.hideAlert(alertEl);
|
||||
clearFieldErrors();
|
||||
|
||||
var amountRaw = document.getElementById("amount").value;
|
||||
var amountCents = Math.round(parseFloat(amountRaw || "0") * 100);
|
||||
|
||||
var payload = {
|
||||
client: {
|
||||
company_name: document.getElementById("company_name").value.trim(),
|
||||
contact_name: document.getElementById("contact_name").value.trim(),
|
||||
email: document.getElementById("email").value.trim(),
|
||||
phone: document.getElementById("phone").value.trim() || null,
|
||||
},
|
||||
debtor: {
|
||||
name: document.getElementById("debtor_name").value.trim(),
|
||||
business_type: document.getElementById("debtor_business_type").value,
|
||||
contact_email: document.getElementById("debtor_contact_email").value.trim() || null,
|
||||
contact_phone: document.getElementById("debtor_contact_phone").value.trim() || null,
|
||||
physical_address: document.getElementById("debtor_physical_address").value.trim() || null,
|
||||
},
|
||||
claim: {
|
||||
amount_cents: amountCents,
|
||||
description: document.getElementById("description").value.trim() || null,
|
||||
client_reference: document.getElementById("client_reference").value.trim() || null,
|
||||
invoice_date: document.getElementById("invoice_date").value || null,
|
||||
},
|
||||
tos_accepted: document.getElementById("tos_accepted").checked,
|
||||
};
|
||||
|
||||
if (!payload.tos_accepted) {
|
||||
showFieldError("tos_accepted", "You must accept the Terms of Service to submit a claim.");
|
||||
return;
|
||||
}
|
||||
if (!amountCents || amountCents <= 0) {
|
||||
showFieldError("amount", "Enter a valid amount greater than $0.");
|
||||
return;
|
||||
}
|
||||
|
||||
DRE.setLoading(submitBtn, true, "Submitting...");
|
||||
|
||||
DRE.apiFetch("/api/intake", { method: "POST", body: payload }).then(function (result) {
|
||||
DRE.setLoading(submitBtn, false, null, "Submit Claim");
|
||||
|
||||
if (result.ok) {
|
||||
var data = result.data;
|
||||
document.getElementById("intake-claim-number").textContent = data.claim_number || "--";
|
||||
document.getElementById("intake-client-number").textContent = data.client_number || "--";
|
||||
document.getElementById("intake-success-message").textContent =
|
||||
data.message || "Our team will review and contact you shortly.";
|
||||
form.classList.add("dre-hidden");
|
||||
successEl.classList.remove("dre-hidden");
|
||||
successEl.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
return;
|
||||
}
|
||||
|
||||
var code = DRE.getErrorCode(result);
|
||||
var message = DRE.getErrorMessage(result, "We could not submit your claim. Please try again.");
|
||||
|
||||
if (code === "validation_error") {
|
||||
var matched = distributeValidationMessage(message);
|
||||
if (!matched) {
|
||||
DRE.showAlert(alertEl, message);
|
||||
} else {
|
||||
DRE.showAlert(alertEl, "Please correct the highlighted fields below.");
|
||||
}
|
||||
} else if (code === "rate_limited") {
|
||||
DRE.showAlert(alertEl, "Too many submissions from this connection. Please try again in a few minutes.");
|
||||
} else {
|
||||
DRE.showAlert(alertEl, message);
|
||||
}
|
||||
alertEl.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user