Public Access
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:
1 parent
29ff175e89
commit
59ffde396f
1 file changed
+66
-315
+66
-315
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.
|
||||
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/" />
|
||||
<meta property="og:url" content="https://odoo4projects.com/feedback/" />
|
||||
<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" />
|
||||
<link rel="icon" href="/images/favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
@@ -61,7 +61,7 @@
|
||||
letter-spacing: 0.02em;
|
||||
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 {
|
||||
background: var(--ivory);
|
||||
border: 1px solid var(--line);
|
||||
@@ -81,13 +81,6 @@
|
||||
.state-warn, .state-err { color: var(--brass-deep); }
|
||||
.state-ok { color: var(--success); }
|
||||
.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 ---- */
|
||||
.field { margin: 0 0 1.1rem; }
|
||||
@@ -135,17 +128,7 @@
|
||||
.status.state-warn, .status.state-err { color: var(--brass-deep); }
|
||||
.status.state-ok { color: var(--success); }
|
||||
|
||||
/* ---- Claimed ---- */
|
||||
.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 bar (two steps) ---- */
|
||||
.progress { margin: 0 0 1.15rem; }
|
||||
.steps-bar {
|
||||
list-style: none;
|
||||
@@ -171,11 +154,10 @@
|
||||
font-size: 0.8rem;
|
||||
line-height: 1;
|
||||
}
|
||||
/* The current step is the brass pill; a done step carries the ✓ mark (see the script) —
|
||||
the tick is the non-colour cue, so a done step is never colour alone. */
|
||||
/* The current step is the brass pill and the bolder label, and it is said out loud
|
||||
("(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 .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; }
|
||||
/* Visually hidden, still read aloud. */
|
||||
.vh {
|
||||
@@ -189,57 +171,20 @@
|
||||
white-space: nowrap;
|
||||
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>
|
||||
|
||||
<!-- Every wire name and shape lives here, in ONE place. Two constants, one per verb:
|
||||
GET reads the free-seat count and the collected feedback; POST claims a seat, and posts
|
||||
the feedback form. SHAPES (this is the contract to match in the n8n workflow):
|
||||
<!-- The one wire name and shape this page uses, in ONE place. POST only — the page reads
|
||||
nothing on load. SHAPE (match this in the n8n workflow):
|
||||
|
||||
GET statusUrl (no query string)
|
||||
-> 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.
|
||||
POST submitUrl { "form": "feedback", <one key per question, the answer> }
|
||||
-> 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
|
||||
not printed. -->
|
||||
The body carries the answers and nothing else: no name, no email, no identifier of any
|
||||
kind. An unexpected shape is named, never guessed. -->
|
||||
<script>
|
||||
window.O4F = {
|
||||
// The one route that answers the count + feedback (read-only).
|
||||
statusUrl: "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"]
|
||||
// The one route that receives the feedback form.
|
||||
submitUrl: "https://n8n.odoo4projects.com/webhook/feedback"
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -262,18 +207,31 @@
|
||||
<div class="card-wrap">
|
||||
<h1>Freelancer signup feedback</h1>
|
||||
<p class="lede">
|
||||
We opened a free freelancer portal on ODOO4projects.com. Claim a seat here, then finish
|
||||
the signup and tell us how it went — every answer helps us improve the process.
|
||||
We opened a free freelancer portal on ODOO4projects.com. Sign up there, then tell us how
|
||||
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>
|
||||
|
||||
<!-- 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
|
||||
noscript note below is the honest picture. -->
|
||||
there is no form to render, so an empty container plus the noscript note below is
|
||||
the honest picture. -->
|
||||
<div id="app"></div>
|
||||
|
||||
<noscript>
|
||||
<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">
|
||||
Turn JavaScript on in your browser and open this page again — or reply to the message
|
||||
that brought you here.
|
||||
@@ -288,10 +246,9 @@
|
||||
(function () {
|
||||
var C = window.O4F;
|
||||
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"
|
||||
// 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) {
|
||||
var n = document.createElement(tag);
|
||||
@@ -304,35 +261,12 @@
|
||||
for (var i = 0; i < nodes.length; i++) app.appendChild(nodes[i]);
|
||||
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) {
|
||||
var p = el("p", "status" + (kind ? " state-" + kind : ""), text);
|
||||
p.setAttribute("role", "status");
|
||||
p.setAttribute("aria-live", "polite");
|
||||
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) {
|
||||
return fetch(url, opts).then(function (r) {
|
||||
return r.text().then(function (t) {
|
||||
@@ -347,40 +281,41 @@
|
||||
var m = body.message || body.error || body.detail;
|
||||
return typeof m === "string" ? m : "";
|
||||
}
|
||||
function retryButton(label, fn) {
|
||||
var b = el("button", "btn", label);
|
||||
b.type = "button";
|
||||
b.addEventListener("click", fn);
|
||||
return b;
|
||||
// A link to the freelancer portal. Opens in a new window and says so, so a screen-reader
|
||||
// user is not surprised by a new tab.
|
||||
function portalLink() {
|
||||
var p = el("p", "detail");
|
||||
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) ---------------------------------------------------------
|
||||
// Which step is current follows the page state — it is not decoration. It renders only
|
||||
// where there is something to do: seats available (step 1 current) and seat claimed
|
||||
// (step 1 done, step 2 current, step 3 visible as what comes next). The cohort-full and
|
||||
// count-unavailable states render no bar at all. The bar states no count and no deadline:
|
||||
// both come from the wire and are printed elsewhere, from the wire alone.
|
||||
var STEP_LABELS = ["Claim seat", "Sign up", "Fill out feedback"];
|
||||
// ---- Progress bar (two steps) ---------------------------------------------------------
|
||||
// Step 1 is the signup on the portal, step 2 is this form. The page has no honest way to
|
||||
// know the visitor has used the portal link, so step 1 stays current and nothing is
|
||||
// guessed: the number, the bolder label and the spoken "(current step)" say which one is
|
||||
// current, so it is never colour alone.
|
||||
var STEP_LABELS = ["Sign up", "Give feedback"];
|
||||
function progressBar(current) {
|
||||
var nav = el("nav", "progress");
|
||||
nav.setAttribute("aria-label", "Signup progress");
|
||||
nav.setAttribute("aria-label", "Progress");
|
||||
var ol = el("ol", "steps-bar");
|
||||
STEP_LABELS.forEach(function (label, i) {
|
||||
var n = i + 1;
|
||||
var li = el("li", "step");
|
||||
var mark = el("span", "step-mark");
|
||||
mark.setAttribute("aria-hidden", "true");
|
||||
// A done step shows the ✓ mark; the current and upcoming steps show their number.
|
||||
mark.textContent = n < current ? "\u2713" : String(n);
|
||||
mark.textContent = String(n);
|
||||
li.appendChild(mark);
|
||||
li.appendChild(el("span", "step-label", label));
|
||||
if (n === current) {
|
||||
li.className = "step step-current";
|
||||
li.setAttribute("aria-current", "step");
|
||||
} else if (n < current) {
|
||||
// Done is the ✓ mark AND a word for screen readers — never colour alone.
|
||||
li.className = "step step-done";
|
||||
li.appendChild(el("span", "vh", " (completed)"));
|
||||
li.appendChild(el("span", "vh", " (current step)"));
|
||||
} else {
|
||||
li.className = "step step-next";
|
||||
}
|
||||
@@ -390,176 +325,19 @@
|
||||
return nav;
|
||||
}
|
||||
|
||||
// ---- The four states -----------------------------------------------------------------
|
||||
// 1. Seats available. The number is read from the wire; a cap is not a remaining count.
|
||||
function stateAvailable(seats, total) {
|
||||
var h = el("p", "seat");
|
||||
h.appendChild(el("strong", "", String(seats)));
|
||||
h.appendChild(document.createTextNode(
|
||||
total != null ? " of " + total + " seats left" : (seats === 1 ? " seat left" : " seats left")
|
||||
));
|
||||
|
||||
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)]);
|
||||
// ---- Step 1 — the portal link, again, in the step's own copy -------------------------
|
||||
function signupPanel() {
|
||||
var box = el("div", "panel");
|
||||
box.appendChild(el("h2", "", "Step 1 — sign up on the freelancer portal"));
|
||||
box.appendChild(el("p", "detail",
|
||||
"Create your free freelancer account and add your profile. The directory opens on 1 December."));
|
||||
box.appendChild(portalLink());
|
||||
return box;
|
||||
}
|
||||
|
||||
// ---- 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 = [
|
||||
{ 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 },
|
||||
@@ -672,11 +450,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the body. The claimed Upwork name rides along ONLY when a seat was claimed
|
||||
// this visit — state the page already holds. With no claim the submission carries no
|
||||
// name and is anonymous (nothing is invented).
|
||||
// Build the body. The answers and nothing else — no name, no email, no identifier.
|
||||
var body = { form: "feedback" };
|
||||
if (claimedUpwork) body.upwork = claimedUpwork;
|
||||
FIELDS.forEach(function (spec) {
|
||||
if (spec.type === "radio") {
|
||||
var checked = form.querySelector('input[name="' + spec.id + '"]:checked');
|
||||
@@ -719,33 +494,9 @@
|
||||
return box;
|
||||
}
|
||||
|
||||
// ---- Load -----------------------------------------------------------------------------
|
||||
function load() {
|
||||
show([panel([status("Reading the seat count…", "")])]);
|
||||
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();
|
||||
// ---- Render ---------------------------------------------------------------------------
|
||||
// One state, always: the form. Nothing is read on load, so there is no fetch here.
|
||||
show([progressBar(1), signupPanel(), feedbackPanel()]);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in new issue
Block a user