diff --git a/make_post_sell/static/js/auction.js b/make_post_sell/static/js/auction.js
index ff8c1d9..f81c6ae 100644
--- a/make_post_sell/static/js/auction.js
+++ b/make_post_sell/static/js/auction.js
@@ -114,9 +114,36 @@
});
}
+ // Pay-by countdown (post-end): same fmtRemaining; written into
+ // #auction-pay-countdown if present. The human deadline string is
+ // also resolved locally from the same data attribute so the user
+ // sees a consistent wall-clock + countdown in their own timezone.
+ const payCountdownEl = document.getElementById("auction-pay-countdown");
+ const payDeadlineHumanEl = document.getElementById(
+ "auction-pay-deadline-human"
+ );
+
+ function tickPayCountdown() {
+ if (!payCountdownEl) return;
+ const raw = payCountdownEl.getAttribute("data-pay-deadline");
+ if (!raw) return;
+ const deadline = parseInt(raw, 10);
+ if (isNaN(deadline)) return;
+ payCountdownEl.textContent = fmtRemaining(deadline - Date.now());
+ if (payDeadlineHumanEl && payDeadlineHumanEl.textContent === "computing…") {
+ try {
+ payDeadlineHumanEl.textContent = new Date(deadline).toLocaleString();
+ } catch (e) {
+ /* leave the placeholder */
+ }
+ }
+ }
+
// Countdown ticks locally every second.
setInterval(tickCountdown, 1000);
+ setInterval(tickPayCountdown, 1000);
tickCountdown();
+ tickPayCountdown();
// Live state: prefer a bounded SSE feed (the server closes it after
// ~25s; EventSource reconnects on its own), and fall back to polling
diff --git a/make_post_sell/static/js/offer.js b/make_post_sell/static/js/offer.js
index e0c6cc3..69bcbb9 100644
--- a/make_post_sell/static/js/offer.js
+++ b/make_post_sell/static/js/offer.js
@@ -118,9 +118,46 @@
});
}
+ /* Pay-by deadline countdown.
+ *
+ * Any element carrying [data-pay-deadline] (an absolute ms timestamp)
+ * with a child .offer-pay-countdown-value will tick once a second
+ * showing how long the buyer has left to pay. When the window
+ * elapses the offer auto-expires server-side; we reflect that
+ * locally as "expired — refresh to update."
+ */
+ function fmtRemaining(ms) {
+ if (ms <= 0) return "expired";
+ var totalSeconds = Math.floor(ms / 1000);
+ var days = Math.floor(totalSeconds / 86400);
+ var hours = Math.floor((totalSeconds % 86400) / 3600);
+ var minutes = Math.floor((totalSeconds % 3600) / 60);
+ var seconds = totalSeconds % 60;
+ if (days > 0) return days + "d " + hours + "h " + minutes + "m";
+ if (hours > 0) return hours + "h " + minutes + "m " + seconds + "s";
+ if (minutes > 0) return minutes + "m " + seconds + "s";
+ return seconds + "s";
+ }
+
+ function tickPayCountdown() {
+ var nodes = document.querySelectorAll("[data-pay-deadline]");
+ if (!nodes.length) return;
+ var now = Date.now();
+ Array.prototype.forEach.call(nodes, function (node) {
+ var raw = node.getAttribute("data-pay-deadline");
+ var deadline = parseInt(raw, 10);
+ if (isNaN(deadline)) return;
+ var value = node.querySelector(".offer-pay-countdown-value");
+ if (!value) return;
+ value.textContent = fmtRemaining(deadline - now);
+ });
+ }
+
function init() {
wire();
watchOfferState();
+ tickPayCountdown();
+ setInterval(tickPayCountdown, 1000);
}
if (document.readyState === "loading") {
diff --git a/make_post_sell/templates/auction.j2 b/make_post_sell/templates/auction.j2
index 06c2267..6d975a0 100644
--- a/make_post_sell/templates/auction.j2
+++ b/make_post_sell/templates/auction.j2
@@ -40,6 +40,20 @@
{% endif %}
+ {% if is_ended and user_is_winner and not is_settled and payment_deadline_timestamp %}
+
+ Complete payment by computing…
+ · computing… remaining,
+ or the auction will be released to the next-highest bidder.
+ You won this auction!
+
You have until {{ pay_deadline_human }} to complete payment, after which this offer auto-expires.
+You have until {{ pay_deadline_human }} to complete payment, after which this offer auto-expires. + {% if pay_deadline_timestamp_ms %} + · computing… remaining + {% endif %} +
{% endif %}