fix(freelancer-directory): remove the Post-a-request section and every relay promise; state the service is free both sides; count = clients, one label; render every confirmed record

- Delete the whole "Post a request" section (heading, "Not open yet" note, pick-list/
  brief/email form) and the JS+Css that existed only for it: initRequestForm(),
  wireRelay("requestFormWrap"…), initRequestForm(), .dir-pickfield/.dir-picklist/.pick.
  build-directory.py: drop PICK_START/PICK_END, render_picklist() and its call.
- Rewrite every relay promise to direct contact (trust block, listing note, final CTA);
  keep the "we are not a party to the contract" point.
- Trust block: state no fee to the freelancer and no fee to the client.
- Count means client instances: single label "clients on ODOO4projects" (no projects).
- No confirmation gating: comment now states the backend only exposes confirmed profiles.
- Left with a comment, not deleted: ODOO_EXPERTS_QUOTE_WEBHOOK (it gates the dark
  anonymous-contact modal, which was out of scope). The modal stays dark (0 buttons).
- Kept: id=1 junk record; the inlined base64 photo (flagged, not changed).
This commit is contained in:
webmaster committed 2026-11-03 13:33:53 +00:00
1 parent 99245764a9
commit 5aa4782de7
1 file changed
+18 -157
+18 -157
View File
@@ -565,59 +565,6 @@
color: #b3452f; color: #b3452f;
} }
/* The explicit freelancer pick-list (form 3). A real list over the directory —
never a free-text box and never an implicit "all". */
.dir-pickfield {
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 0.85rem 0.9rem 0.6rem;
min-width: 0;
}
.section-dark .dir-pickfield {
border-color: var(--line-brass);
}
.dir-pickfield legend {
font-size: 0.8rem;
font-weight: 600;
color: var(--midnight);
padding: 0 0.35rem;
}
.section-dark .dir-pickfield legend {
color: rgba(255, 255, 255, 0.85);
}
.dir-picklist {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1rem;
}
.dir-picklist .pick {
display: flex;
align-items: baseline;
gap: 0.45rem;
font-size: 0.9rem;
color: var(--ink);
}
.section-dark .dir-picklist .pick {
color: rgba(255, 255, 255, 0.85);
}
.dir-picklist .pick input {
width: auto;
margin: 0;
align-self: center;
}
.dir-picklist .pick .hint {
font-size: 0.78rem;
color: var(--ink-soft);
}
.section-dark .dir-picklist .pick .hint {
color: rgba(255, 255, 255, 0.6);
}
.dir-picklist-empty {
margin: 0;
font-size: 0.9rem;
color: var(--ink-soft);
}
/* Final CTA */ /* Final CTA */
.cmp-cta { .cmp-cta {
text-align: center; text-align: center;
@@ -1113,9 +1060,10 @@
<h2 class="section-title">What we do, and what we don't</h2> <h2 class="section-title">What we do, and what we don't</h2>
<div class="oc-body"> <div class="oc-body">
<p>You are about to write to someone you have not worked with before. Here is exactly what this page does, and what it does not.</p> <p>You are about to write to someone you have not worked with before. Here is exactly what this page does, and what it does not.</p>
<p><strong>You deal with the freelancer, not us.</strong> Each card carries the contact details they chose to publish, and the brief, the price and the delivery are settled between the two of you; we are not a party, as <a href="#listing-note">the note under the listings</a> says. If you would rather not write first, email us your brief and we pass it to the freelancers you name.</p> <p><strong>You deal with the freelancer, not us.</strong> Each card carries the contact details they chose to publish, and the brief, the price and the delivery are settled between the two of you; we are not a party, as <a href="#listing-note">the note under the listings</a> says. Write to the freelancer whose card fits — the contact is theirs, and their answer comes straight back to you.</p>
<p><strong>Nobody pays us to be listed here.</strong> There is no fee to the freelancer and no fee to you for using this page — we charge neither side for the introduction.</p>
<p><strong>We rank nobody.</strong> Nothing here is graded: no score, no ranking, no "best" order. A listing is not a recommendation from us.</p> <p><strong>We rank nobody.</strong> Nothing here is graded: no score, no ranking, no "best" order. A listing is not a recommendation from us.</p>
<p><strong>We verify nothing.</strong> A card is that person's own words. Where a line is the freelancer's own declaration (that they work alone, that they are an OCA member), we label it (as declared): we pass it on, we do not check it.</p> <p><strong>We verify nothing.</strong> A card is that person's own words. Where a line is the freelancer's own declaration (that they work alone, that they are an OCA member), we label it (as declared): we publish it as written, we do not check it.</p>
<p><strong>An invitation is one click to refuse.</strong> When a freelancer is suggested to us, we invite them once, we say who suggested them, and we give them a single click to opt out.</p> <p><strong>An invitation is one click to refuse.</strong> When a freelancer is suggested to us, we invite them once, we say who suggested them, and we give them a single click to opt out.</p>
</div> </div>
</div> </div>
@@ -1188,56 +1136,11 @@
<p class="dir-empty" id="dirEmpty" hidden>No freelancer matches that search and those filters. Use Clear to see everyone.</p> <p class="dir-empty" id="dirEmpty" hidden>No freelancer matches that search and those filters. Use Clear to see everyone.</p>
<div class="dir-note" id="listing-note"> <div class="dir-note" id="listing-note">
<p>You deal with the freelancer directly. The contract for the work is between you and them, and the terms, the price and the delivery are yours to agree. We are not a party to it — we publish the listings and, if you ask, pass your message along.</p> <p>You deal with the freelancer directly. The contract for the work is between you and them, and the terms, the price and the delivery are yours to agree. We are not a party to it — we publish the listings and nothing more; the conversation is between you and the freelancer.</p>
</div> </div>
</div> </div>
</section> </section>
<!-- Client request / anonymous get-quote. The route it posts to
(odoo_experts_quote) is a STUB the user has yet to build, so the form ships
DARK: its endpoint constant is empty and the form stays hidden behind the
"not open yet" line below — no way to submit, so no false confirmation. When the
workflow exists, set ODOO_EXPERTS_QUOTE_WEBHOOK and the note hides itself.
The freelancer selection is an explicit pick-list over the
directory, built at build time — NEVER a free-text box and NEVER an
implicit "all". Rebuild the pick-list with:
python3 /root/workspace/webmaster/work/build-directory.py -->
<section class="section-light" id="request">
<div class="container">
<h2 class="section-title">Post a request</h2>
<p class="form-notlive" id="requestNotLive">Not open yet. Email <a href="mailto:hello@odoo4projects.com?subject=Post%20a%20request">hello@odoo4projects.com</a> with your brief and the freelancers you want it to reach — we will pass it on by hand and send their answers back to you.</p>
<div class="dir-forms" id="requestFormWrap" hidden>
<form class="form-card" id="requestForm" novalidate>
<div class="form-grid">
<fieldset class="form-field full dir-pickfield">
<legend>Freelancers to ask <span class="hint">(pick at least one — we never send to everyone)</span></legend>
<div class="dir-picklist" id="reqPicklist">
<!-- REQUEST-PICKLIST-START -->
<label class="pick"><input type="checkbox" name="freelancers" value="Oliver - The OC Guy" data-id="2" /> <span>Oliver - The OC Guy</span> <span class="hint">Agent Automation, Manufacturing, E-commerce</span></label>
<label class="pick"><input type="checkbox" name="freelancers" value="oliver" data-id="1" /> <span>oliver</span> <span class="hint">vieoem</span></label>
<!-- REQUEST-PICKLIST-END -->
</div>
</fieldset>
<div class="form-field full">
<label for="rqBrief">Your brief</label>
<textarea id="rqBrief" name="brief" rows="6" required placeholder="What you need built, the Odoo version you run, and anything else a freelancer would need to quote."></textarea>
</div>
<div class="form-field full">
<label for="rqEmail">Your email</label>
<input id="rqEmail" name="email" type="email" autocomplete="email" required />
<span class="hint">Only so we can send their answers back to you. The freelancers never see it.</span>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-brass">Send my request</button>
<span class="form-status" id="requestStatus" role="status"></span>
</div>
</div>
</form>
</div>
<noscript><p class="form-noscript">This form needs JavaScript. Email <a href="mailto:hello@odoo4projects.com">hello@odoo4projects.com</a> with your brief and we will pass it on by hand.</p></noscript>
</div>
</section>
<!-- Freelancer application (form 1) --> <!-- Freelancer application (form 1) -->
<section class="section-dark" id="apply"> <section class="section-dark" id="apply">
<div class="container"> <div class="container">
@@ -1673,7 +1576,7 @@
<div class="container"> <div class="container">
<h2 class="section-title">Start</h2> <h2 class="section-title">Start</h2>
<div class="cmp-lead"> <div class="cmp-lead">
<p>Browse the freelancers and write to one directly from their card — or email us your brief and we will pass it to the freelancers you pick.</p> <p>Browse the freelancers and write to one directly from their card. The contact details are theirs; their answer comes straight back to you.</p>
</div> </div>
<div class="cmp-cta-actions"> <div class="cmp-cta-actions">
<a href="#directory" class="btn btn-brass btn-lg">Browse the freelancers</a> <a href="#directory" class="btn btn-brass btn-lg">Browse the freelancers</a>
@@ -1784,15 +1687,17 @@
============================================================================ */ ============================================================================ */
var ODOO_EXPERTS_WEBHOOK = var ODOO_EXPERTS_WEBHOOK =
"https://n8n.odoo4projects.com/webhook/ODOO4projects/odoo_experts"; "https://n8n.odoo4projects.com/webhook/ODOO4projects/odoo_experts";
// The two relay routes below are STUBS the user has yet to build. While an // Below this line: one stub the user has yet to build. While its endpoint is
// endpoint is empty, the surface that posts to it stays hidden — the visitor can // empty, the surface that posts to it stays hidden — the visitor can never
// never submit, so the page can never confirm an action nothing performs. // submit, so the page can never confirm an action nothing performs. Set the URL
// Set the URL to switch it on; its note then hides itself. Nothing else changes. // to switch it on; its note then hides itself. Nothing else changes.
// //
// Setting this endpoint switches the REQUEST form AND the per-card "contact // ODOO_EXPERTS_QUOTE_WEBHOOK gates ONLY the per-card "contact this freelancer
// anonymously" button on. The success copy below must already be a RECEIPT // anonymously" modal. The client REQUEST form that used to share this constant
// ("we have your request"), never a delivery claim — a visitor sees it the moment // was removed (2026-11-03): the relay is gone, clients contact freelancers
// the endpoint goes live, and the endpoint does not repair the copy. // directly. The modal is out of scope for that removal and stays DARK here.
// Its success copy must already be a RECEIPT ("we have your request"), never a
// delivery claim — a visitor sees it the moment the endpoint goes live.
var ODOO_EXPERTS_QUOTE_WEBHOOK = ""; var ODOO_EXPERTS_QUOTE_WEBHOOK = "";
// //
// Setting this endpoint switches the INVITE form on. Same rule: receipt, not a // Setting this endpoint switches the INVITE form on. Same rule: receipt, not a
@@ -2131,8 +2036,9 @@
THE ORDER IS THE ENDPOINT'S. The server randomises on purpose; we render what it THE ORDER IS THE ENDPOINT'S. The server randomises on purpose; we render what it
sent. No sort here — not by name, and never by the client count. sent. No sort here — not by name, and never by the client count.
NOT BUILT — the confirmation gate. The endpoint exposes nothing to tell a THE BACKEND ONLY EXPOSES CONFIRMED PROFILES. Email confirmation happens
confirmed record from an unconfirmed one, so nothing is guessed: every record upstream, so every record the endpoint returns is already confirmed — there is
no status or email_verified field to read, and none is invented. Every record
with a Name renders, exactly as build-directory.py does it. */ with a Name renders, exactly as build-directory.py does it. */
var LIVE_FILTERS = [ var LIVE_FILTERS = [
["Country", "Country", "country", "value"], ["Country", "Country", "country", "value"],
@@ -2589,49 +2495,6 @@
}); });
} }
/* ---- Form 3: anonymous client request. SHIPS DARK ---- */
function initRequestForm() {
var form = document.getElementById("requestForm");
if (!form) return;
var status = document.getElementById("requestStatus");
form.addEventListener("submit", function (event) {
event.preventDefault();
if (!form.reportValidity()) return;
var picks = Array.prototype.slice.call(
form.querySelectorAll('input[name="freelancers"]:checked')
);
if (!picks.length) {
setStatus(status, "Pick at least one freelancer to send your request to.", "err");
return;
}
var payload = {
brief: form.querySelector('[name="brief"]').value.trim(),
email: form.querySelector('[name="email"]').value.trim(),
freelancers: picks.map(function (box) {
var id = Number(box.getAttribute("data-id"));
return { id: isNaN(id) ? null : id, name: box.value };
})
};
var submit = form.querySelector('button[type="submit"]');
submit.disabled = true;
setStatus(status, "Sending…", null);
postTo(ODOO_EXPERTS_QUOTE_WEBHOOK, payload)
.then(function (res) {
if (!res.ok) throw new Error("HTTP " + res.status);
setStatus(status,
"Thank you — we have your request. We will come back to you by email at the address you gave.",
"ok");
form.reset();
})
.catch(function () {
setStatus(status,
"That did not send. Please try again, or email us at hello@odoo4projects.com and we will pass it on.",
"err");
})
.then(function () { submit.disabled = false; });
});
}
/* ---- Form 4: invite a friend. SHIPS DARK ---- */ /* ---- Form 4: invite a friend. SHIPS DARK ---- */
function initInviteForm() { function initInviteForm() {
var form = document.getElementById("inviteForm"); var form = document.getElementById("inviteForm");
@@ -2658,7 +2521,6 @@
}); });
} }
wireRelay("requestFormWrap", "requestNotLive", ODOO_EXPERTS_QUOTE_WEBHOOK);
wireRelay("inviteFormWrap", "inviteNotLive", ODOO_EXPERTS_INVITE_WEBHOOK); wireRelay("inviteFormWrap", "inviteNotLive", ODOO_EXPERTS_INVITE_WEBHOOK);
initFocusUI(document.getElementById("applyForm"), "apFocusWarn", "apFocusTotal"); initFocusUI(document.getElementById("applyForm"), "apFocusWarn", "apFocusTotal");
initFocusCounter(document.getElementById("applyForm"), "apFocus", "apFocusCount"); initFocusCounter(document.getElementById("applyForm"), "apFocus", "apFocusCount");
@@ -2667,7 +2529,6 @@
initFilters(); initFilters();
initLiveList(); initLiveList();
initApplyForm(); initApplyForm();
initRequestForm();
initInviteForm(); initInviteForm();
</script> </script>
</body> </body>