Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 4 additions & 15 deletions efile_app/efile/middleware.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django.contrib.auth import logout
from django.http import JsonResponse
from django.shortcuts import render
from django.urls import reverse
from django.utils.deprecation import MiddlewareMixin

from efile.models import FilingDraft
Expand Down Expand Up @@ -43,27 +42,17 @@ def process_exception(self, request, exception):
def process_response(self, request, response):
draft = getattr(request, "filing_draft", None)
if draft is not None:
# A targeted edit opened from the interview handoff returns to its
# missing-details list, instead of re-asking completed later steps.
targeted = request.GET.get("return_to") == "handoff" or request.POST.get("return_to") == "handoff"
destination = None
if (
targeted
and request.method == "POST"
and response.status_code < 400
and draft.status == FilingDraft.Status.DRAFT
):
destination = reverse("handoff_review", args=[draft.pk])
# Where a step goes next, including back to a detour's origin, is
# the step's own decision (efile.workflow.continue_url). This only
# keeps the filing named in the URL it chose.
if response.has_header("Location"):
if destination:
response["Location"] = destination
response["Location"] = draft_url(response["Location"], draft.pk)
elif isinstance(response, JsonResponse):
import json

payload = json.loads(response.content)
if isinstance(payload, dict) and isinstance(payload.get("redirect_url"), str):
payload["redirect_url"] = destination or draft_url(payload["redirect_url"], draft.pk)
payload["redirect_url"] = draft_url(payload["redirect_url"], draft.pk)
response.content = json.dumps(payload)
return response

Expand Down
1 change: 1 addition & 0 deletions efile_app/efile/services/draft_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"case_questions",
"payment",
"waiver_documents",
"document_checks",
"case_review",
"filing_confirmation",
"expert_form",
Expand Down
9 changes: 8 additions & 1 deletion efile_app/efile/static/js/document-checklist.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
(function() {
// New files are checked on this page, so come back to them after a change.
const reloadAtChecks = () => {
window.history.replaceState(window.history.state, "", "#document-checks");
window.location.reload();
};
document.getElementById("document-checks")?.addEventListener("document-checks:change", reloadAtChecks);

const form = document.getElementById("checklist-upload-form");
if (!form) return;
const state = document.getElementById("checklist-upload-state");
Expand All @@ -24,7 +31,7 @@
}
const result = await response.json();
if (!response.ok || !result.success) throw new Error(result.error || "Could not add documents.");
window.location.reload();
reloadAtChecks();
} catch (error) {
state.hidden = true;
errorBox.textContent = error.message;
Expand Down
66 changes: 66 additions & 0 deletions efile_app/efile/static/js/document-checks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* Confirm or remove a newly prepared copy without leaving the page it was added on.
*
* Markup: a [data-document-checks] container (data-url: the document-checks
* endpoint) holding [data-document-check] blocks. Each finished action fires
* "document-checks:change" on the container with {action, data}, so the page
* can update whatever depends on its documents.
*/
const DocumentChecks = {
pending(root = document) {
return Boolean(root.querySelector("[data-document-check]"));
},

add(container, html) {
const template = document.createElement("template");
template.innerHTML = html.trim();
const check = template.content.firstElementChild;
container.appendChild(check);
check.querySelectorAll("[data-pdf-preview]").forEach((details) => window.attachPdfPreview?.(details));
return check;
},

async submit(button) {
const container = button.closest("[data-document-checks]");
const check = button.closest("[data-document-check]");
const action = button.hasAttribute("data-document-remove") ? "remove" : "confirm";
const status = check.querySelector("[data-document-check-status]");
const buttons = check.querySelectorAll("button");
const body = new FormData();
body.append("action", action);
body.append("document_id", check.dataset.documentId);
body.append("preview_fingerprint", check.dataset.fingerprint);
body.append("csrfmiddlewaretoken", apiUtils.getCSRFToken());
buttons.forEach((element) => {
element.disabled = true;
});
status.textContent = action === "remove" ? gettext("Removing document…") : gettext("Saving…");
try {
const response = await fetch(window.withFilingDraft(container.dataset.url), {
method: "POST",
body,
credentials: "same-origin"
});
const data = await response.json();
if (!response.ok || !data.success) throw new Error(data.error || gettext("That did not work. Try again."));
check.remove();
container.dispatchEvent(new CustomEvent("document-checks:change", {
bubbles: true,
detail: {
action,
data
}
}));
} catch (error) {
status.textContent = error.message;
buttons.forEach((element) => {
element.disabled = false;
});
}
}
};
window.DocumentChecks = DocumentChecks;

document.addEventListener("click", (event) => {
const button = event.target.closest("[data-document-checks] [data-document-confirm], [data-document-checks] [data-document-remove]");
if (button) DocumentChecks.submit(button);
});
9 changes: 7 additions & 2 deletions efile_app/efile/static/js/document-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@
status.textContent = gettext("The PDF did not load. Download it or close and reopen this view.");
}
}
document.querySelectorAll("[data-pdf-preview]").forEach((details) => {

function attach(details) {
details.addEventListener("toggle", () => openPreview(details));
});
// Previews added after load, already open, may never fire a toggle.
if (details.open) openPreview(details);
}
window.attachPdfPreview = attach;
document.querySelectorAll("[data-pdf-preview]").forEach(attach);
})();
27 changes: 26 additions & 1 deletion efile_app/efile/static/js/payment.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* global DocumentChecks */
const PAYMENT_URLS = {
accounts: "/api/payment-accounts/",
accountTypes: "/api/payment-account-types/",
Expand Down Expand Up @@ -57,7 +58,30 @@ const PaymentPage = {

setFeesState(loading) {
document.getElementById("loadingSpinner").style.display = loading ? "block" : "none";
document.getElementById("submitButton").disabled = loading || this.removingAccount || this.waiverUploading || !this.feeQuoteReady || !document.getElementById("selected-payment-account").value;
document.getElementById("submitButton").disabled = loading || this.removingAccount || this.waiverUploading || DocumentChecks.pending() || !this.feeQuoteReady || !document.getElementById("selected-payment-account").value;
},

// A copy added on this page is confirmed here before Review.
async onDocumentCheck({
action,
data
}) {
document.getElementById("fee-inputs-token").textContent = JSON.stringify(data.fee_inputs_token);
const confirmation = document.getElementById("waiver-upload-confirmation");
if (action === "remove") {
if (confirmation) confirmation.hidden = true;
const upload = document.getElementById("waiver-upload-required");
if (upload) upload.hidden = false;
document.getElementById("add-waiver-document")?.focus();
paymentMessages.showSuccess(gettext("Document removed. You can upload a different file."));
// The removed document no longer counts toward fees.
await this.chooseIntent();
} else {
if (confirmation && !confirmation.hidden) confirmation.textContent = gettext("Fee waiver document added and checked.");
const next = document.querySelector("[data-document-check]") || document.querySelector('input[name="paymentIntent"]:checked') || document.getElementById("submitButton");
next.focus();
}
this.setFeesState(false);
},

async loadAccountTypes() {
Expand Down Expand Up @@ -318,6 +342,7 @@ const PaymentPage = {
document.querySelectorAll('input[name="paymentIntent"]').forEach((input) => {
input.addEventListener("change", () => this.chooseIntent());
});
document.getElementById("document-checks").addEventListener("document-checks:change", (event) => this.onDocumentCheck(event.detail));
await this.loadAccountTypes();
this.loadAccounts().catch(() => paymentMessages.showError(gettext("We could not load payment methods.")));
}
Expand Down
11 changes: 6 additions & 5 deletions efile_app/efile/static/js/waiver-upload.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global PaymentPage, paymentJSON */
/* global DocumentChecks, PaymentPage, paymentJSON */
/* Add a supporting waiver PDF without leaving payment or changing the lead. */
document.addEventListener("DOMContentLoaded", () => {
const open = document.getElementById("add-waiver-document");
Expand Down Expand Up @@ -83,14 +83,15 @@ document.addEventListener("DOMContentLoaded", () => {
});
const data = await response.json();
if (!response.ok || !data.success) throw new Error(data.error || gettext("The upload failed. Try again."));
if (data.preview_url) {
window.location.assign(window.withFilingDraft(data.preview_url));
return;
}
document.getElementById("fee-inputs-token").textContent = JSON.stringify(data.fee_inputs_token);
document.getElementById("waiver-upload-required").hidden = true;
file.value = "";
status.textContent = "";
const confirmation = document.getElementById("waiver-upload-confirmation");
confirmation.hidden = false;
// The filer checks the prepared copy here, without leaving Fees.
DocumentChecks.add(document.getElementById("document-checks"), data.check_html);
PaymentPage.setFeesState(false);
confirmation.focus();
// Old requests and quotes described a different set of documents.
await PaymentPage.chooseIntent();
Expand Down
1 change: 1 addition & 0 deletions efile_app/efile/templates/efile/case_confirmation.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ <h1>{% translate "Is this your court case?" %}</h1>
{% if availability_message %}<div class="alert alert-warning" role="alert">{{ availability_message }}</div>{% endif %}
<form method="post">
{% csrf_token %}
<input type="hidden" name="return_to" value="{{ return_to }}" />
<div class="confirmation-question">
<strong>{% translate "Does this match your case?" %}</strong>
<p>{% translate "If you choose Yes, we will attach your documents to this case." %}</p>
Expand Down
27 changes: 27 additions & 0 deletions efile_app/efile/templates/efile/components/document_check.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{% load i18n %}
<div class="document-check mb-3"
data-document-check
data-document-id="{{ document.pk }}"
data-fingerprint="{{ fingerprint }}"
tabindex="-1">
<p>
<strong>{% translate "Check this copy before you continue." %}</strong>
{% translate "The court will get this PDF. Open it and check every page." %}
</p>
{% if not document.preparation %}
<p>{% translate "Not ready. Remove this file and upload it again." %}</p>
{% endif %}
{% include "efile/components/document_preview.html" with document=document open_preview=True %}
<div class="d-flex flex-wrap gap-2">
<button type="button"
class="btn btn-primary"
data-document-confirm
{% if not document.preparation %}disabled{% endif %}>{% translate "This copy looks right" %}</button>
{% if removable %}
<button type="button" class="btn btn-outline-secondary" data-document-remove>
{% translate "Remove and upload a different file" %}
</button>
{% endif %}
</div>
<p data-document-check-status role="status" aria-live="polite"></p>
</div>
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{% load i18n %}
<details class="document-preview" data-pdf-preview>
<details class="document-preview"
data-pdf-preview
{% if open_preview %}open{% endif %}>
<summary>{% translate "View PDF:" %} {{ document.name|default:document.original_filename }}</summary>
<div class="document-preview__body">
<p>
Expand Down
19 changes: 17 additions & 2 deletions efile_app/efile/templates/efile/document_checklist.html
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,15 @@ <h2 id="document-plan-heading">{% translate "Your document plan" %}</h2>
{% endfor %}
{% endif %}
<h2 class="checklist-files__heading">{% translate "Files you have added" %}</h2>
<div id="document-checks"
data-document-checks
data-url="{{ document_checks_url }}">
{% for check in document_checks %}
{% include "efile/components/document_check.html" with document=check.document fingerprint=check.fingerprint removable=check.removable %}
{% endfor %}
</div>
<div class="checklist-files">
{% for document in documents %}
{% for document in checked_documents %}
<article class="checklist-file">
<span class="checklist-file__check"><i class="fa-solid fa-check" aria-hidden="true"></i></span>
<span class="checklist-file__icon"><i class="fa-solid fa-file-pdf" aria-hidden="true"></i></span>
Expand Down Expand Up @@ -236,10 +243,17 @@ <h2 class="checklist-files__heading">{% translate "Files you have added" %}</h2>
<form method="post" id="checklist-confirm-form">
{% csrf_token %}
<input type="hidden" name="return_to" value="{{ return_to }}" />
{% if document_checks %}
<p id="checks-pending" class="checklist-checks-pending">
{% translate "Check each new file above before you continue." %}
</p>
{% endif %}
<div class="workflow-actions">
<a class="btn btn-outline-secondary"
href="{% url 'extraction_review' jurisdiction %}"><i class="fa-solid fa-arrow-left" aria-hidden="true"></i> {% translate "Back" %}</a>
<button class="btn btn-primary" type="submit">
<button class="btn btn-primary"
type="submit"
{% if document_checks %}disabled aria-describedby="checks-pending"{% endif %}>
{% if return_to == "review" %}
{% translate "Back to review" %}
{% else %}
Expand All @@ -259,5 +273,6 @@ <h2 class="checklist-files__heading">{% translate "Files you have added" %}</h2>
{% endblock workflow_content %}
{% block extra_js %}
<script src="{% static 'js/checklist-status.js' %}"></script>
<script src="{% static 'js/document-checks.js' %}"></script>
<script src="{% static 'js/document-checklist.js' %}"></script>
{% endblock extra_js %}
Loading
Loading