Public Access
feedback: remove identity block, heard_about and the publish consent question
- Delete the 'About you' fieldset (name, email, anonymous tick) and its validation: the claimed Upwork name now identifies the sender, and the feedback payload carries it only when a seat was claimed this visit. - Remove the heard_about question. - Remove the publish consent question. That question WAS the consent, so no feedback may be published: the cohort-full state now renders the closed-cohort line only, and the feedback-rendering branch (entry keys, publish constants, isPublishable, the .fb styles) is gone rather than left dead. The GET's 'feedback' array is no longer consumed. - recommend: first label 'Yes' -> 'Yes, good idea' (value yes unchanged).
This commit is contained in:
1 parent
bb963850b1
commit
a6ada39784
1 file changed
+27
-136
+27
-136
@@ -190,11 +190,7 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Feedback list (cohort full) ---- */
|
/* ---- Cohort full (no feedback is ever rendered) ---- */
|
||||||
.fb { border-top: 1px solid var(--line); padding: 1rem 0 0; margin: 1rem 0 0; }
|
|
||||||
.fb:first-of-type { border-top: 0; padding-top: 0.2rem; margin-top: 0.4rem; }
|
|
||||||
.fb p { margin: 0 0 0.5rem; }
|
|
||||||
.fb cite { font-style: normal; font-size: 0.85rem; color: var(--brass-deep); }
|
|
||||||
.who {
|
.who {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
@@ -211,26 +207,26 @@
|
|||||||
GET statusUrl (no query string)
|
GET statusUrl (no query string)
|
||||||
-> 200 { "seats_left": 7, "seats_total": 10 } <- the intended shape
|
-> 200 { "seats_left": 7, "seats_total": 10 } <- the intended shape
|
||||||
or { "free": 9 } <- what the live route answers today
|
or { "free": 9 } <- what the live route answers today
|
||||||
or { "free": 0,
|
or { "free": 0, "feedback": [ ... ] }
|
||||||
"feedback": [ { "text": "...", "name": "Jane", "publish": "yes" } ] }
|
|
||||||
A count arrives under the documented names first, then the live route's
|
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
|
"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). "feedback" (aka "entries")
|
(so "free": "" is skipped and the next key is read). A "feedback" array in the
|
||||||
carries the cohort's words: each entry is published only when its "publish" is
|
reply is NOT consumed by this page and nothing from it is ever rendered — the
|
||||||
"yes" (words with the name) or "name" (words without it) — "no", missing or
|
page reads the count only. With no seats, the cohort-full state shows the
|
||||||
unknown is never rendered. With no seats and no "feedback", the cohort-full
|
closed-cohort line alone: no quotes, no excerpts, no paraphrases.
|
||||||
state states plainly that nothing is published yet (never a blank list).
|
|
||||||
|
|
||||||
POST submitUrl { "form": "claim", "upwork": "<their Upwork name>" }
|
POST submitUrl { "form": "claim", "upwork": "<their Upwork name>" }
|
||||||
-> 200 { "seats_left": 6, "seats_total": 10, "held_until": "2026-11-03T18:40:00Z" }
|
-> 200 { "seats_left": 6, "seats_total": 10, "held_until": "2026-11-03T18:40:00Z" }
|
||||||
|
|
||||||
POST submitUrl { "form": "feedback", "upwork": "<their Upwork name>", "name": "...",
|
POST submitUrl { "form": "feedback", "upwork": "<their claimed Upwork name>",
|
||||||
"email": "...", "anonymous": false, "finalized": "yes", ... ,
|
"finalized": "yes", ... }
|
||||||
"publish": "yes" | "name" | "no" }
|
"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
|
An unexpected shape is named, never guessed: a count that did not come off the wire is
|
||||||
not printed, and an entry whose "publish" is missing or unknown is not rendered. -->
|
not printed. -->
|
||||||
<script>
|
<script>
|
||||||
window.O4F = {
|
window.O4F = {
|
||||||
// The one route that answers the count + feedback (read-only).
|
// The one route that answers the count + feedback (read-only).
|
||||||
@@ -242,16 +238,6 @@
|
|||||||
// shape still wins if it ever arrives. "free": "" is skipped by pick() below.
|
// shape still wins if it ever arrives. "free": "" is skipped by pick() below.
|
||||||
leftKeys: ["seats_left", "seatsLeft", "Free", "free"],
|
leftKeys: ["seats_left", "seatsLeft", "Free", "free"],
|
||||||
totalKeys: ["seats_total", "seatsTotal"],
|
totalKeys: ["seats_total", "seatsTotal"],
|
||||||
feedbackKeys: ["feedback", "entries"],
|
|
||||||
// One feedback entry. A missing "" means "not on the wire", never a value.
|
|
||||||
entryTextKeys: ["text", "feedback", "message"],
|
|
||||||
entryNameKeys: ["name", "Name"],
|
|
||||||
entryPublishKeys: ["publish", "consent"],
|
|
||||||
// The three publish answers, character for character as the form sends them. ONLY "yes"
|
|
||||||
// and "name" are rendered; anything else — "no", missing, unknown — is never rendered.
|
|
||||||
PUBLISH_YES: "yes",
|
|
||||||
PUBLISH_NAME: "name",
|
|
||||||
PUBLISH_NO: "no",
|
|
||||||
// The POST reply key that carries the seat deadline (ISO 8601).
|
// The POST reply key that carries the seat deadline (ISO 8601).
|
||||||
heldUntilKeys: ["held_until", "heldUntil", "deadline"]
|
heldUntilKeys: ["held_until", "heldUntil", "deadline"]
|
||||||
};
|
};
|
||||||
@@ -540,27 +526,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Cohort full — consented feedback only.
|
// 3. Cohort full — the closed-cohort line only. No feedback is rendered: the consent
|
||||||
function stateFull(entries) {
|
// question (the old "publish" field) is gone, so there is no record of anyone agreeing
|
||||||
var shown = entries.filter(isPublishable);
|
// 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")]);
|
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. Thank you — here is what the freelancers who took part told us."));
|
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."));
|
||||||
if (shown.length) {
|
|
||||||
shown.forEach(function (e) {
|
|
||||||
var fig = el("figure", "fb");
|
|
||||||
fig.appendChild(el("p", "", String(pick(e, C.entryTextKeys) || "")));
|
|
||||||
var nm = pick(e, C.entryNameKeys);
|
|
||||||
var consent = pick(e, C.entryPublishKeys);
|
|
||||||
if (consent === C.PUBLISH_YES && nm) {
|
|
||||||
fig.appendChild(el("cite", "", "— " + String(nm)));
|
|
||||||
} else {
|
|
||||||
fig.appendChild(el("cite", "", "— published without a name"));
|
|
||||||
}
|
|
||||||
box.appendChild(fig);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
box.appendChild(el("p", "detail", "No feedback has been published yet."));
|
|
||||||
}
|
|
||||||
show([box]);
|
show([box]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,11 +558,6 @@
|
|||||||
show([panel(nodes)]);
|
show([panel(nodes)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPublishable(entry) {
|
|
||||||
var c = pick(entry, C.entryPublishKeys);
|
|
||||||
return c === C.PUBLISH_YES || c === C.PUBLISH_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 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, plus the fields the owner asked for.
|
||||||
var FIELDS = [
|
var FIELDS = [
|
||||||
@@ -605,10 +572,8 @@
|
|||||||
{ id: "design_feedback", label: "What can we improve about the profile design?", type: "textarea", required: true },
|
{ id: "design_feedback", label: "What can we improve about the profile design?", type: "textarea", required: true },
|
||||||
{ id: "will_stay", label: "Will you stay on the platform?", type: "select", options: [["yes", "Yes"], ["no", "No"]], required: true },
|
{ id: "will_stay", label: "Will you stay on the platform?", type: "select", options: [["yes", "Yes"], ["no", "No"]], required: true },
|
||||||
{ id: "improve_offer", label: "How can we improve what we offer you?", type: "textarea", required: true },
|
{ id: "improve_offer", label: "How can we improve what we offer you?", type: "textarea", required: true },
|
||||||
{ id: "recommend", label: "Would you recommend this platform to a freelance colleague?", type: "select", options: [["yes", "Yes"], ["maybe", "Maybe"], ["no", "Preferably not"]], required: true },
|
{ id: "recommend", label: "Would you recommend this platform to a freelance colleague?", type: "select", options: [["yes", "Yes, good idea"], ["maybe", "Maybe"], ["no", "Preferably not"]], required: true },
|
||||||
{ id: "heard_about", label: "Where did you first hear about this platform?", type: "text", required: true },
|
{ id: "expect_next", label: "What do you expect to happen next — and how soon?", type: "textarea", required: true }
|
||||||
{ id: "expect_next", label: "What do you expect to happen next — and how soon?", type: "textarea", required: true },
|
|
||||||
{ id: "publish", label: "May we publish your feedback with your name?", type: "radio", options: [["yes", "Yes, with my name"], ["name", "Yes, but not with my name"], ["no", "No"]], required: true }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function fieldBlock(spec) {
|
function fieldBlock(spec) {
|
||||||
@@ -663,47 +628,10 @@
|
|||||||
function feedbackPanel() {
|
function feedbackPanel() {
|
||||||
var box = el("div", "panel");
|
var box = el("div", "panel");
|
||||||
box.appendChild(el("h2", "", "How did it go?"));
|
box.appendChild(el("h2", "", "How did it go?"));
|
||||||
box.appendChild(el("p", "detail", "Answer in your own words. Nothing here is published unless you say so, and you can ask us to leave your name off."));
|
box.appendChild(el("p", "detail", "Answer in your own words. Your answers come to us — nothing here is published."));
|
||||||
var form = el("form");
|
var form = el("form");
|
||||||
form.setAttribute("novalidate", "novalidate");
|
form.setAttribute("novalidate", "novalidate");
|
||||||
|
|
||||||
// Name + email, or an explicit anonymous tick.
|
|
||||||
var ident = el("fieldset");
|
|
||||||
ident.setAttribute("data-field", "identity");
|
|
||||||
ident.appendChild(el("legend", "", "About you"));
|
|
||||||
var nf = el("div", "field");
|
|
||||||
var nl = el("label", "", "Your name");
|
|
||||||
nl.setAttribute("for", "fb-name");
|
|
||||||
var ni = el("input");
|
|
||||||
ni.type = "text";
|
|
||||||
ni.id = "fb-name";
|
|
||||||
ni.name = "name";
|
|
||||||
ni.autocomplete = "name";
|
|
||||||
nf.appendChild(nl); nf.appendChild(ni);
|
|
||||||
ident.appendChild(nf);
|
|
||||||
var ef = el("div", "field");
|
|
||||||
var elab = el("label", "", "Your email");
|
|
||||||
elab.setAttribute("for", "fb-email");
|
|
||||||
var ei = el("input");
|
|
||||||
ei.type = "email";
|
|
||||||
ei.id = "fb-email";
|
|
||||||
ei.name = "email";
|
|
||||||
ei.autocomplete = "email";
|
|
||||||
ef.appendChild(elab); ef.appendChild(ei);
|
|
||||||
ident.appendChild(ef);
|
|
||||||
var anonLab = el("label", "choice");
|
|
||||||
var anon = el("input");
|
|
||||||
anon.type = "checkbox";
|
|
||||||
anon.id = "fb-anonymous";
|
|
||||||
anon.name = "anonymous";
|
|
||||||
anonLab.appendChild(anon);
|
|
||||||
anonLab.appendChild(el("span", "", "Send this anonymously — do not record my name or email."));
|
|
||||||
ident.appendChild(anonLab);
|
|
||||||
var idErr = el("p", "err", "");
|
|
||||||
idErr.hidden = true;
|
|
||||||
ident.appendChild(idErr);
|
|
||||||
form.appendChild(ident);
|
|
||||||
|
|
||||||
FIELDS.forEach(function (spec) { form.appendChild(fieldBlock(spec)); });
|
FIELDS.forEach(function (spec) { form.appendChild(fieldBlock(spec)); });
|
||||||
|
|
||||||
var line = status("", "");
|
var line = status("", "");
|
||||||
@@ -712,23 +640,6 @@
|
|||||||
form.appendChild(btn);
|
form.appendChild(btn);
|
||||||
form.appendChild(line);
|
form.appendChild(line);
|
||||||
|
|
||||||
// The anonymous tick hides the name/email and removes "with my name" as an option.
|
|
||||||
function syncAnon() {
|
|
||||||
var on = anon.checked;
|
|
||||||
nf.hidden = on;
|
|
||||||
ef.hidden = on;
|
|
||||||
var yesName = form.querySelector('input[name="publish"][value="yes"]');
|
|
||||||
if (yesName) {
|
|
||||||
yesName.disabled = on;
|
|
||||||
if (on && yesName.checked) {
|
|
||||||
var alt = form.querySelector('input[name="publish"][value="name"]');
|
|
||||||
if (alt) alt.checked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anon.addEventListener("change", syncAnon);
|
|
||||||
syncAnon();
|
|
||||||
|
|
||||||
form.addEventListener("submit", function (ev) {
|
form.addEventListener("submit", function (ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
var bad = false;
|
var bad = false;
|
||||||
@@ -753,22 +664,6 @@
|
|||||||
err.hidden = true;
|
err.hidden = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var anonymous = anon.checked;
|
|
||||||
if (!anonymous) {
|
|
||||||
var nm = ni.value.trim(), em = ei.value.trim();
|
|
||||||
if (!nm || !em) {
|
|
||||||
ident.setAttribute("data-bad", "1");
|
|
||||||
idErr.textContent = "Add your name and email — or tick “Send this anonymously”.";
|
|
||||||
idErr.hidden = false;
|
|
||||||
bad = true;
|
|
||||||
} else {
|
|
||||||
ident.removeAttribute("data-bad");
|
|
||||||
idErr.hidden = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ident.removeAttribute("data-bad");
|
|
||||||
idErr.hidden = true;
|
|
||||||
}
|
|
||||||
if (bad) {
|
if (bad) {
|
||||||
line.className = "status state-warn";
|
line.className = "status state-warn";
|
||||||
line.textContent = "Some answers are missing — see the notes above.";
|
line.textContent = "Some answers are missing — see the notes above.";
|
||||||
@@ -777,14 +672,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the body. An anonymous sender sends no name and no email.
|
// Build the body. The claimed Upwork name rides along ONLY when a seat was claimed
|
||||||
var body = {
|
// this visit — state the page already holds. With no claim the submission carries no
|
||||||
form: "feedback",
|
// name and is anonymous (nothing is invented).
|
||||||
upwork: claimedUpwork,
|
var body = { form: "feedback" };
|
||||||
name: anonymous ? "" : ni.value.trim(),
|
if (claimedUpwork) body.upwork = claimedUpwork;
|
||||||
email: anonymous ? "" : ei.value.trim(),
|
|
||||||
anonymous: anonymous
|
|
||||||
};
|
|
||||||
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');
|
||||||
@@ -838,14 +730,13 @@
|
|||||||
}
|
}
|
||||||
var seats = count(res.body, C.leftKeys);
|
var seats = count(res.body, C.leftKeys);
|
||||||
var total = count(res.body, C.totalKeys);
|
var total = count(res.body, C.totalKeys);
|
||||||
var entries = pick(res.body, C.feedbackKeys);
|
|
||||||
if (seats === null) {
|
if (seats === null) {
|
||||||
// No count came off the wire: print no number.
|
// No count came off the wire: print no number.
|
||||||
stateUnavailable(null, "the reply carried no seat count");
|
stateUnavailable(null, "the reply carried no seat count");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (seats === 0) {
|
if (seats === 0) {
|
||||||
stateFull(Array.isArray(entries) ? entries : []);
|
stateFull();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stateAvailable(seats, total);
|
stateAvailable(seats, total);
|
||||||
|
|||||||
Reference in new issue
Block a user