ontoref-outreach/site/htmx-templates/pages/work-request.j2
2026-07-17 00:51:19 +01:00

126 lines
6.3 KiB
Django/Jinja

{# Work request — "request a project quote". A PUBLIC LEAD-GEN FORM.
IT LIVES HERE NOW, AND THAT MATTERS. The jesusperez.pro copy of this template exists ONLY in
`htmx-templates/` — the ASSEMBLED tree, which `just templates` deletes and rebuilds on every run
(adr-070). It was one command away from vanishing, and that is exactly how the nav submenus were
lost once. In the framework it is a source file, inherited by every site built from the image.
AND ITS SUBMIT WAS DEAD. It posted to `/api/work-request`, a route registered NOWHERE in the
stack — not in rustelo_server, not in the image, not in the binary. Every submission for as long
as the page has been up went to an endpoint that does not exist. Nothing looked wrong, because a
form that RENDERS looks exactly like a form that WORKS.
It now posts to the declarative endpoint (`POST /api/htmx/form/work-request`), whose spec is
`site/config/forms/work-request.ncl`. The spec declares all eleven fields, so all eleven are
delivered — which is the whole point. Routing this through the four-field contact endpoint would
have returned 200, said "sent", and mailed the name and email with the description, the budget
and the timeline silently dropped, because serde ignores unknown fields by default.
The field names below MUST match the spec: a posted field the spec does not declare is refused
with a 400 that names it. That is deliberate — a template and a spec that disagree should fail
on the first request, not in a quarterly review of an empty inbox. #}
<article class="ds-page ds-page-work-request" data-page-id="work-request">
<header class="ds-page-header">
<div class="ds-container">
<h1 class="ds-heading-xl">{{ texts["work-request-title"] | default(texts["work-request-page-title"]) }}</h1>
{% if texts["work-request-subtitle"] %}
<p class="ds-text-lead">{{ texts["work-request-subtitle"] }}</p>
{% endif %}
</div>
</header>
<div class="ds-page-body">
<div class="ds-container">
{% if texts["work-request-description"] %}
<p class="ds-text-lead">{{ texts["work-request-description"] }}</p>
{% endif %}
<form class="ds-form space-y-ds-4"
hx-post="/api/htmx/form/work-request"
hx-target="#work-request-result"
hx-swap="innerHTML">
<input type="hidden" name="lang" value="{{ lang }}" />
{# Honeypot. Hidden from people, irresistible to bots; a value here means the handler
confirms without delivering, so the bot gets no signal. #}
<div style="position:absolute;left:-9999px" aria-hidden="true">
<label for="wr-website">Website</label>
<input id="wr-website" name="website" type="text" tabindex="-1" autocomplete="off" />
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-ds-4">
<div>
<label for="wr-name" class="ds-label">{{ texts["work-request-your-name"] }}</label>
<input id="wr-name" name="name" type="text" required class="ds-input w-full"
placeholder="{{ texts['work-request-name-placeholder'] }}" />
</div>
<div>
<label for="wr-email" class="ds-label">{{ texts["work-request-your-email"] }}</label>
<input id="wr-email" name="email" type="email" required class="ds-input w-full"
placeholder="{{ texts['work-request-email-placeholder'] }}" />
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-ds-4">
<div>
<label for="wr-company" class="ds-label">{{ texts["work-request-company"] }}</label>
<input id="wr-company" name="company" type="text" class="ds-input w-full" />
</div>
<div>
<label for="wr-project-type" class="ds-label">{{ texts["work-request-project-type-label"] }}</label>
<select id="wr-project-type" name="project_type" class="ds-input w-full">
<option value=""></option>
{% for opt in ["web", "infrastructure", "consulting", "other"] %}
<option value="{{ opt }}">{{ texts["work-request-project-type-label-" ~ opt] | default(opt) }}</option>
{% endfor %}
</select>
</div>
</div>
<div>
<label for="wr-description" class="ds-label">{{ texts["work-request-project-description-label"] }}</label>
<textarea id="wr-description" name="project_description" rows="5" required class="ds-input w-full"></textarea>
</div>
<div>
<label for="wr-situation" class="ds-label">{{ texts["work-request-current-situation-label"] }}</label>
<textarea id="wr-situation" name="current_situation" rows="3" class="ds-input w-full"></textarea>
</div>
<div>
<label for="wr-goals" class="ds-label">{{ texts["work-request-goals-label"] }}</label>
<textarea id="wr-goals" name="goals" rows="3" class="ds-input w-full"></textarea>
</div>
<div>
<label for="wr-technical" class="ds-label">{{ texts["work-request-technical-requirements-label"] }}</label>
<textarea id="wr-technical" name="technical_requirements" rows="3" class="ds-input w-full"></textarea>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-ds-4">
<div>
<label for="wr-timeline" class="ds-label">{{ texts["work-request-timeline-label"] }}</label>
<input id="wr-timeline" name="timeline" type="text" class="ds-input w-full" />
</div>
<div>
<label for="wr-budget" class="ds-label">{{ texts["work-request-budget"] }}</label>
<input id="wr-budget" name="budget" type="text" class="ds-input w-full" />
</div>
<div>
<label for="wr-contact-method" class="ds-label">{{ texts["work-request-contact-method-label"] }}</label>
<input id="wr-contact-method" name="contact_method" type="text" class="ds-input w-full" />
</div>
</div>
<button type="submit" class="ds-btn ds-btn-primary">
{{ texts["work-request-submit"] | default(texts["work-request-send"]) | default("Send") }}
</button>
<div id="work-request-result" role="status" aria-live="polite"></div>
</form>
</div>
</div>
</article>