feedback page: strip the seat claim, keep it a plain feedback form with a visible portal link

The owner asked for a plain feedback form: no Upwork name, no seat claim, and the
freelancer-portal link visible on the page. The seat-claim layer is removed whole.

- remove the GET /webhook/feedback seat-count read, its parsing and the four states
  it drove (available / claimed / full / count-unavailable) — one state now, the form
- remove the claim POST, the 1-hour hold and its clock-time copy, the leftKeys
  contract, the count-retry button and the cohort-full branch
- no capacity wording: seat/spot/claim/hold/cohort/full are gone, and "unlimited"
  is not written either — the page says nothing about capacity
- the form posts the answers only: no upwork, no email, no identifier
- progress bar is two steps (1 Sign up - 2 Give feedback); step 1 stays current
  because the page cannot honestly detect that the portal link was used
- the freelancer-portal link is visible next to the intro and again in step 1's copy,
  new window with rel=noopener noreferrer and it says so in its accessible name

Nothing on load is fetched: 0 requests to the webhook until a real submit.
This commit is contained in:
webmaster committed 2026-11-03 23:24:58 +00:00
1 parent 29ff175e89
commit 59ffde396f
1 file changed
+66 -315
+66 -315
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Freelancer signup feedback — ODOO4projects</title> <title>Freelancer signup feedback — ODOO4projects</title>
<meta name="description" content="Claim a seat in the ODOO4projects freelancer signup and tell us how it went. The link goes to a named group of freelancers, not to search." /> <meta name="description" content="Tell us how the ODOO4projects freelancer signup went — every answer helps us improve the process before the directory opens." />
<!-- Private utility page. It is reached ONLY by the link we send, never by search. <!-- Private utility page. It is reached ONLY by the link we send, never by search.
noindex+nofollow here, absent from sitemap.xml and llms.txt, and linked from nowhere noindex+nofollow here, absent from sitemap.xml and llms.txt, and linked from nowhere
@@ -16,7 +16,7 @@
<link rel="canonical" href="https://odoo4projects.com/feedback/" /> <link rel="canonical" href="https://odoo4projects.com/feedback/" />
<meta property="og:url" content="https://odoo4projects.com/feedback/" /> <meta property="og:url" content="https://odoo4projects.com/feedback/" />
<meta property="og:title" content="Freelancer signup feedback — ODOO4projects" /> <meta property="og:title" content="Freelancer signup feedback — ODOO4projects" />
<meta property="og:description" content="Claim a seat in the ODOO4projects freelancer signup and tell us how it went." /> <meta property="og:description" content="Tell us how the ODOO4projects freelancer signup went — every answer helps us improve the process." />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<link rel="icon" href="/images/favicon.ico" /> <link rel="icon" href="/images/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
@@ -61,7 +61,7 @@
letter-spacing: 0.02em; letter-spacing: 0.02em;
margin: 0 0 0.4rem; margin: 0 0 0.4rem;
} }
.lede { color: var(--ink-soft); margin: 0 0 1.5rem; } .lede { color: var(--ink-soft); margin: 0 0 1rem; }
.panel { .panel {
background: var(--ivory); background: var(--ivory);
border: 1px solid var(--line); border: 1px solid var(--line);
@@ -81,13 +81,6 @@
.state-warn, .state-err { color: var(--brass-deep); } .state-warn, .state-err { color: var(--brass-deep); }
.state-ok { color: var(--success); } .state-ok { color: var(--success); }
.detail { margin: 0.5rem 0 0; font-size: 0.9rem; color: var(--ink-soft); } .detail { margin: 0.5rem 0 0; font-size: 0.9rem; color: var(--ink-soft); }
.seat {
font-family: Marcellus, Georgia, serif;
font-size: clamp(1.6rem, 5vw, 2.4rem);
letter-spacing: 0.02em;
margin: 0 0 0.35rem;
}
.seat strong { color: var(--brass-deep); font-weight: 400; }
/* ---- Forms ---- */ /* ---- Forms ---- */
.field { margin: 0 0 1.1rem; } .field { margin: 0 0 1.1rem; }
@@ -135,17 +128,7 @@
.status.state-warn, .status.state-err { color: var(--brass-deep); } .status.state-warn, .status.state-err { color: var(--brass-deep); }
.status.state-ok { color: var(--success); } .status.state-ok { color: var(--success); }
/* ---- Claimed ---- */ /* ---- Progress bar (two steps) ---- */
.deadline {
font-family: Marcellus, Georgia, serif;
font-size: 1.35rem;
margin: 0 0 0.3rem;
}
.deadline strong { color: var(--brass-deep); font-weight: 400; }
.steps { margin: 0.4rem 0 0; padding-left: 1.2rem; font-size: 0.92rem; }
.steps li { margin: 0 0 0.3rem; }
/* ---- Progress bar (three steps) ---- */
.progress { margin: 0 0 1.15rem; } .progress { margin: 0 0 1.15rem; }
.steps-bar { .steps-bar {
list-style: none; list-style: none;
@@ -171,11 +154,10 @@
font-size: 0.8rem; font-size: 0.8rem;
line-height: 1; line-height: 1;
} }
/* The current step is the brass pill; a done step carries the ✓ mark (see the script) — /* The current step is the brass pill and the bolder label, and it is said out loud
the tick is the non-colour cue, so a done step is never colour alone. */ ("(current step)", .vh below) — weight and a word, so it is never colour alone. */
.steps-bar .step-current { color: var(--ink); font-weight: 600; } .steps-bar .step-current { color: var(--ink); font-weight: 600; }
.steps-bar .step-current .step-mark { background: var(--brass); border-color: var(--brass); color: var(--midnight); } .steps-bar .step-current .step-mark { background: var(--brass); border-color: var(--brass); color: var(--midnight); }
.steps-bar .step-done .step-mark { background: var(--success); border-color: var(--success); color: #fff; }
.link-signup { color: var(--brass-deep); font-weight: 600; } .link-signup { color: var(--brass-deep); font-weight: 600; }
/* Visually hidden, still read aloud. */ /* Visually hidden, still read aloud. */
.vh { .vh {
@@ -189,57 +171,20 @@
white-space: nowrap; white-space: nowrap;
border: 0; border: 0;
} }
/* ---- Cohort full (no feedback is ever rendered) ---- */
.who {
font-size: 0.8rem;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--brass-deep);
margin: 0 0 1rem;
}
</style> </style>
<!-- Every wire name and shape lives here, in ONE place. Two constants, one per verb: <!-- The one wire name and shape this page uses, in ONE place. POST only — the page reads
GET reads the free-seat count and the collected feedback; POST claims a seat, and posts nothing on load. SHAPE (match this in the n8n workflow):
the feedback form. SHAPES (this is the contract to match in the n8n workflow):
GET statusUrl (no query string) POST submitUrl { "form": "feedback", <one key per question, the answer> }
-> 200 { "seats_left": 7, "seats_total": 10 } <- the intended shape
or { "free": 9 } <- what the live route answers today
or { "free": 0, "feedback": [ ... ] }
A count arrives under the documented names first, then the live route's
"Free"/"free"; a count must be a whole number >= 0 or it is not on the wire
(so "free": "" is skipped and the next key is read). A "feedback" array in the
reply is NOT consumed by this page and nothing from it is ever rendered — the
page reads the count only. With no seats, the cohort-full state shows the
closed-cohort line alone: no quotes, no excerpts, no paraphrases.
POST submitUrl { "form": "claim", "upwork": "<their Upwork name>" }
-> 200 { "seats_left": 6, "seats_total": 10, "held_until": "2026-11-03T18:40:00Z" }
POST submitUrl { "form": "feedback", "upwork": "<their claimed Upwork name>",
"finalized": "yes", ... }
"upwork" is present ONLY when a seat was claimed this visit — the claimed Upwork
name, held in memory, sent to this route and nowhere else. With no claim the
submission carries no name and is anonymous.
-> any 2xx = receipt shown; a non-2xx is reported as a failure, never as success. -> any 2xx = receipt shown; a non-2xx is reported as a failure, never as success.
An unexpected shape is named, never guessed: a count that did not come off the wire is The body carries the answers and nothing else: no name, no email, no identifier of any
not printed. --> kind. An unexpected shape is named, never guessed. -->
<script> <script>
window.O4F = { window.O4F = {
// The one route that answers the count + feedback (read-only). // The one route that receives the feedback form.
statusUrl: "https://n8n.odoo4projects.com/webhook/feedback", submitUrl: "https://n8n.odoo4projects.com/webhook/feedback"
// The one route that receives a claim and the feedback form.
submitUrl: "https://n8n.odoo4projects.com/webhook/feedback",
// GET reply keys, in the order they are read. A key not listed here is never read.
// "Free"/"free" are the live route's names today; they come last so the documented
// shape still wins if it ever arrives. "free": "" is skipped by pick() below.
leftKeys: ["seats_left", "seatsLeft", "Free", "free"],
totalKeys: ["seats_total", "seatsTotal"],
// The POST reply key that carries the seat deadline (ISO 8601).
heldUntilKeys: ["held_until", "heldUntil", "deadline"]
}; };
</script> </script>
@@ -262,18 +207,31 @@
<div class="card-wrap"> <div class="card-wrap">
<h1>Freelancer signup feedback</h1> <h1>Freelancer signup feedback</h1>
<p class="lede"> <p class="lede">
We opened a free freelancer portal on ODOO4projects.com. Claim a seat here, then finish We opened a free freelancer portal on ODOO4projects.com. Sign up there, then tell us how
the signup and tell us how it went — every answer helps us improve the process. it went — every answer helps us improve the process.
</p>
<!-- The portal link, visible next to the intro and not buried in step copy. It opens in
a new window and says so. It is a plain link, not the page's primary action —
the feedback form still is. -->
<p class="detail">
The directory opens on <strong>1 December</strong>. Signing up is free for freelancers
and clients, and the feedback you give here is what we fix before then.
<a
class="link-signup"
href="https://odoo4projects.com/odoo-community-developers/#apply"
target="_blank"
rel="noopener noreferrer"
>Sign up on the freelancer portal (opens in a new window)</a>.
</p> </p>
<!-- Filled by the script at the foot of the page. Empty on purpose: with JavaScript off <!-- Filled by the script at the foot of the page. Empty on purpose: with JavaScript off
there is no way to read the seat count or to claim, so an empty container plus the there is no form to render, so an empty container plus the noscript note below is
noscript note below is the honest picture. --> the honest picture. -->
<div id="app"></div> <div id="app"></div>
<noscript> <noscript>
<div class="panel"> <div class="panel">
<p class="state state-warn">This page needs JavaScript to read the seat count and to claim a seat.</p> <p class="state state-warn">This page needs JavaScript to show the feedback form.</p>
<p class="detail"> <p class="detail">
Turn JavaScript on in your browser and open this page again — or reply to the message Turn JavaScript on in your browser and open this page again — or reply to the message
that brought you here. that brought you here.
@@ -288,10 +246,9 @@
(function () { (function () {
var C = window.O4F; var C = window.O4F;
var app = document.getElementById("app"); var app = document.getElementById("app");
var claimedUpwork = ""; // the seat claim: an identifier, kept in memory, never in the URL
// The application form on the directory page — the anchor the page's own "Get listed" // The application form on the directory page — the anchor the page's own "Get listed"
// triggers use. There is no separate login or account page, so this is the one target. // triggers use. There is no separate login or account page, so this is the one target.
var SIGNUP_URL = "https://odoo4projects.com/odoo-community-developers/#apply"; var PORTAL_URL = "https://odoo4projects.com/odoo-community-developers/#apply";
function el(tag, cls, text) { function el(tag, cls, text) {
var n = document.createElement(tag); var n = document.createElement(tag);
@@ -304,35 +261,12 @@
for (var i = 0; i < nodes.length; i++) app.appendChild(nodes[i]); for (var i = 0; i < nodes.length; i++) app.appendChild(nodes[i]);
return nodes; return nodes;
} }
function panel(children) {
var box = el("div", "panel");
for (var i = 0; i < children.length; i++) {
if (children[i]) box.appendChild(children[i]);
}
return box;
}
function status(text, kind) { function status(text, kind) {
var p = el("p", "status" + (kind ? " state-" + kind : ""), text); var p = el("p", "status" + (kind ? " state-" + kind : ""), text);
p.setAttribute("role", "status"); p.setAttribute("role", "status");
p.setAttribute("aria-live", "polite"); p.setAttribute("aria-live", "polite");
return p; return p;
} }
// Read one value by its list of accepted keys. A key not listed is never read.
function pick(obj, keys) {
if (!obj || typeof obj !== "object") return undefined;
for (var i = 0; i < keys.length; i++) {
var v = obj[keys[i]];
if (v !== undefined && v !== null && v !== "") return v;
}
return undefined;
}
// A count must come off the wire as a whole number >= 0. Anything else is "not on the wire".
function count(obj, keys) {
var v = pick(obj, keys);
if (typeof v === "number" && isFinite(v) && v >= 0) return Math.floor(v);
if (typeof v === "string" && /^\s*\d+\s*$/.test(v)) return parseInt(v, 10);
return null;
}
function fetchJson(url, opts) { function fetchJson(url, opts) {
return fetch(url, opts).then(function (r) { return fetch(url, opts).then(function (r) {
return r.text().then(function (t) { return r.text().then(function (t) {
@@ -347,40 +281,41 @@
var m = body.message || body.error || body.detail; var m = body.message || body.error || body.detail;
return typeof m === "string" ? m : ""; return typeof m === "string" ? m : "";
} }
function retryButton(label, fn) { // A link to the freelancer portal. Opens in a new window and says so, so a screen-reader
var b = el("button", "btn", label); // user is not surprised by a new tab.
b.type = "button"; function portalLink() {
b.addEventListener("click", fn); var p = el("p", "detail");
return b; var a = el("a", "link-signup");
a.href = PORTAL_URL;
a.target = "_blank";
a.rel = "noopener noreferrer";
a.textContent = "Sign up on the freelancer portal (opens in a new window)";
p.appendChild(a);
return p;
} }
// ---- Progress bar (three steps) --------------------------------------------------------- // ---- Progress bar (two steps) ---------------------------------------------------------
// Which step is current follows the page state — it is not decoration. It renders only // Step 1 is the signup on the portal, step 2 is this form. The page has no honest way to
// where there is something to do: seats available (step 1 current) and seat claimed // know the visitor has used the portal link, so step 1 stays current and nothing is
// (step 1 done, step 2 current, step 3 visible as what comes next). The cohort-full and // guessed: the number, the bolder label and the spoken "(current step)" say which one is
// count-unavailable states render no bar at all. The bar states no count and no deadline: // current, so it is never colour alone.
// both come from the wire and are printed elsewhere, from the wire alone. var STEP_LABELS = ["Sign up", "Give feedback"];
var STEP_LABELS = ["Claim seat", "Sign up", "Fill out feedback"];
function progressBar(current) { function progressBar(current) {
var nav = el("nav", "progress"); var nav = el("nav", "progress");
nav.setAttribute("aria-label", "Signup progress"); nav.setAttribute("aria-label", "Progress");
var ol = el("ol", "steps-bar"); var ol = el("ol", "steps-bar");
STEP_LABELS.forEach(function (label, i) { STEP_LABELS.forEach(function (label, i) {
var n = i + 1; var n = i + 1;
var li = el("li", "step"); var li = el("li", "step");
var mark = el("span", "step-mark"); var mark = el("span", "step-mark");
mark.setAttribute("aria-hidden", "true"); mark.setAttribute("aria-hidden", "true");
// A done step shows the ✓ mark; the current and upcoming steps show their number. mark.textContent = String(n);
mark.textContent = n < current ? "\u2713" : String(n);
li.appendChild(mark); li.appendChild(mark);
li.appendChild(el("span", "step-label", label)); li.appendChild(el("span", "step-label", label));
if (n === current) { if (n === current) {
li.className = "step step-current"; li.className = "step step-current";
li.setAttribute("aria-current", "step"); li.setAttribute("aria-current", "step");
} else if (n < current) { li.appendChild(el("span", "vh", " (current step)"));
// Done is the ✓ mark AND a word for screen readers — never colour alone.
li.className = "step step-done";
li.appendChild(el("span", "vh", " (completed)"));
} else { } else {
li.className = "step step-next"; li.className = "step step-next";
} }
@@ -390,176 +325,19 @@
return nav; return nav;
} }
// ---- The four states ----------------------------------------------------------------- // ---- Step 1 — the portal link, again, in the step's own copy -------------------------
// 1. Seats available. The number is read from the wire; a cap is not a remaining count. function signupPanel() {
function stateAvailable(seats, total) { var box = el("div", "panel");
var h = el("p", "seat"); box.appendChild(el("h2", "", "Step 1 — sign up on the freelancer portal"));
h.appendChild(el("strong", "", String(seats))); box.appendChild(el("p", "detail",
h.appendChild(document.createTextNode( "Create your free freelancer account and add your profile. The directory opens on 1 December."));
total != null ? " of " + total + " seats left" : (seats === 1 ? " seat left" : " seats left") box.appendChild(portalLink());
)); return box;
var form = el("form");
form.setAttribute("novalidate", "novalidate");
var field = el("div", "field");
var label = el("label", "", "Your Upwork name");
label.setAttribute("for", "fbUpwork");
var input = el("input");
input.type = "text";
input.id = "fbUpwork";
input.name = "upwork";
input.autocomplete = "off";
input.required = true;
var hint = el("p", "hint", "Exactly as it appears on your Upwork profile — this is how we match your seat to your signup.");
field.appendChild(label);
field.appendChild(input);
field.appendChild(hint);
var err = el("p", "err", "");
err.hidden = true;
field.appendChild(err);
form.appendChild(field);
var line = status("", "");
var btn = el("button", "btn", "Claim a seat");
btn.type = "submit";
form.appendChild(btn);
form.appendChild(line);
form.addEventListener("submit", function (ev) {
ev.preventDefault();
var name = input.value.trim();
if (!name) {
field.setAttribute("data-bad", "1");
err.textContent = "Enter your Upwork name to claim a seat.";
err.hidden = false;
input.focus();
return;
}
field.setAttribute("data-bad", "0");
err.hidden = true;
btn.disabled = true;
line.className = "status";
line.textContent = "Claiming your seat…";
// The claim. The Upwork name is an identifier: it goes to our workflow and nowhere
// else — never into the address and never into an analytics payload.
fetchJson(C.submitUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ form: "claim", upwork: name })
}).then(function (res) {
if (!res.ok) {
btn.disabled = false;
line.className = "status state-warn";
line.textContent = "We could not claim your seat — the service answered " + res.status +
(msgOf(res.body) ? " — " + msgOf(res.body) : "") + ". Try again.";
return;
}
claimedUpwork = name;
stateClaimed(count(res.body, C.leftKeys), pick(res.body, C.heldUntilKeys));
}).catch(function () {
btn.disabled = false;
line.className = "status state-warn";
line.textContent = "We could not reach the service to claim your seat. Check your connection and try again.";
});
});
show([
progressBar(1),
panel([h, el("p", "detail", "One seat per freelancer, and there is a fixed number for this cohort.")]),
panel([el("h2", "", "Claim your seat"), form])
]);
}
// 2. Seat claimed — held for one hour. The deadline is shown as a clock time when the
// workflow tells us when it started; the hour is the workflow's, not this page's.
function stateClaimed(seats, heldUntil) {
var dl = el("p", "deadline");
var t = clockTime(heldUntil);
if (t) {
dl.appendChild(document.createTextNode("Your seat is held until "));
dl.appendChild(el("strong", "", t));
dl.appendChild(document.createTextNode("."));
} else {
dl.textContent = "Your seat is held.";
}
var steps = el("ul", "steps");
steps.appendChild(el("li", "", "Create your account on the ODOO4projects freelancer portal."));
steps.appendChild(el("li", "", "Add the links to your profiles (Upwork, LinkedIn, Fiverr)."));
steps.appendChild(el("li", "", "Fill in your focus, about you and the 15-point skill profile."));
steps.appendChild(el("li", "", "Submit the application. We match your signup to the Upwork name you just gave."));
var claimed = el("div", "panel");
claimed.appendChild(el("h2", "", "Seat claimed"));
claimed.appendChild(dl);
claimed.appendChild(el("p", "detail",
"The one hour to finish your signup comes from our signup workflow, not from this page."));
if (seats != null) {
claimed.appendChild(el("p", "who", (seats === 1 ? "1 seat left" : seats + " seats left") + " after your claim"));
}
claimed.appendChild(steps);
// The one thing to do while a seat is held: go and finish the signup. A link, never a
// second button — the page keeps one primary action at a time, and the target is the
// application form the page's own "Get listed" triggers use.
var signup = el("p", "detail");
var up = el("a", "link-signup");
up.href = SIGNUP_URL;
up.target = "_blank";
up.rel = "noopener noreferrer";
up.appendChild(document.createTextNode("Sign up here"));
// Say it opens a new window, so a screen-reader user is not surprised by a new tab.
up.appendChild(el("span", "", " (opens in a new window)"));
signup.appendChild(up);
claimed.appendChild(signup);
show([progressBar(2), claimed, feedbackPanel()]);
}
function clockTime(iso) {
if (!iso) return "";
var d = new Date(iso);
if (isNaN(d.getTime())) return "";
try {
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
} catch (e) {
return "";
}
}
// 3. Cohort full — the closed-cohort line only. No feedback is rendered: the consent
// question (the old "publish" field) is gone, so there is no record of anyone agreeing
// to have their words shown in public, and anonymous words are still the author's
// words. This state names no quotes, no excerpts and no paraphrases.
function stateFull() {
var box = panel([el("h2", "", "This cohort is full")]);
box.appendChild(el("p", "detail", "All seats for this cohort have been claimed, so it is closed. We are not accepting new freelancers into this group."));
show([box]);
}
// 4. Count unavailable — today's real state. No number, no claim button that cannot work.
function stateUnavailable(httpStatus, msg) {
var nodes = [
status("We cannot read the seat count right now.", "warn"),
el("p", "detail", "The seat count could not be read from our service, so we are not showing a number and we are not offering a claim that cannot work. Please try again shortly.")
];
if (httpStatus) {
nodes.push(el("p", "detail", "The service answered " + httpStatus + (msg ? " — " + msg : "") + "."));
}
nodes.push(retryButton("Try again", load));
show([panel(nodes)]);
}
function stateLoadFailed(httpStatus, msg) {
var nodes = [
status("We could not reach the service.", "warn"),
el("p", "detail", "The seat count could not be read, so no number is shown and no seat is offered right now.")
];
if (httpStatus) nodes.push(el("p", "detail", "The service answered " + httpStatus + (msg ? " — " + msg : "") + "."));
nodes.push(retryButton("Try again", load));
show([panel(nodes)]);
} }
// ---- The feedback form ---------------------------------------------------------------- // ---- The feedback form ----------------------------------------------------------------
// The n8n form's questions, with the copy corrected, plus the fields the owner asked for. // The n8n form's questions, with the copy corrected. The identity block (name, email,
// anonymous tick) and the publish-consent question are gone — do not bring them back.
var FIELDS = [ var FIELDS = [
{ id: "finalized", label: "Did you finalize the signup?", type: "select", options: [["yes", "Yes"], ["no", "No"]], required: true }, { id: "finalized", label: "Did you finalize the signup?", type: "select", options: [["yes", "Yes"], ["no", "No"]], required: true },
{ id: "difficulty", label: "How hard was the signup?", type: "select", options: [["easy", "Easy"], ["ok", "OK"], ["hard", "Hard"]], required: true }, { id: "difficulty", label: "How hard was the signup?", type: "select", options: [["easy", "Easy"], ["ok", "OK"], ["hard", "Hard"]], required: true },
@@ -672,11 +450,8 @@
return; return;
} }
// Build the body. The claimed Upwork name rides along ONLY when a seat was claimed // Build the body. The answers and nothing else — no name, no email, no identifier.
// this visit — state the page already holds. With no claim the submission carries no
// name and is anonymous (nothing is invented).
var body = { form: "feedback" }; var body = { form: "feedback" };
if (claimedUpwork) body.upwork = claimedUpwork;
FIELDS.forEach(function (spec) { FIELDS.forEach(function (spec) {
if (spec.type === "radio") { if (spec.type === "radio") {
var checked = form.querySelector('input[name="' + spec.id + '"]:checked'); var checked = form.querySelector('input[name="' + spec.id + '"]:checked');
@@ -719,33 +494,9 @@
return box; return box;
} }
// ---- Load ----------------------------------------------------------------------------- // ---- Render ---------------------------------------------------------------------------
function load() { // One state, always: the form. Nothing is read on load, so there is no fetch here.
show([panel([status("Reading the seat count…", "")])]); show([progressBar(1), signupPanel(), feedbackPanel()]);
fetchJson(C.statusUrl, { method: "GET", cache: "no-store" }).then(function (res) {
if (!res.ok) {
// A workflow that throws is our problem, and it must not look like a closed cohort.
stateUnavailable(res.status, msgOf(res.body));
return;
}
var seats = count(res.body, C.leftKeys);
var total = count(res.body, C.totalKeys);
if (seats === null) {
// No count came off the wire: print no number.
stateUnavailable(null, "the reply carried no seat count");
return;
}
if (seats === 0) {
stateFull();
return;
}
stateAvailable(seats, total);
}).catch(function () {
stateLoadFailed(null, "");
});
}
load();
})(); })();
</script> </script>
</body> </body>