Welcome to the Jose Madrid Salsa developer docs — explore features, APIs, and deployment guides.
Jose Madrid SalsaJMS Docs

Community Polls

Visitor-facing polls at /polls, built and controlled from the admin content section

Community Polls

Polls let staff ask visitors a question — a flavour vote, a fundraising survey, an open-ended "what should we make next" — and collect the answers without anyone needing an account. The public pages live at /polls, reachable from the footer of every page and deliberately absent from the top navigation.

How a visitor experiences it

  • /polls lists every published public poll as a card. Poll images are framed to a fixed 16:10 crop, so any aspect ratio lands tidily; a poll without an image gets its accent gradient instead.
  • /polls/[slug] shows one poll. Each question renders as pill-shaped controls sized for a finger (48px minimum), with real radio/checkbox inputs underneath so keyboards and screen readers behave.
  • Answers are given by tapping a pill, ticking several pills ("select as many as apply"), typing a comment (capped per question, 1500 characters maximum), or choosing a point on a rating scale.
  • Only a first name is required. A last name is offered as explicitly optional. An email field appears only when the poll asks for one, and is optional even then.
  • Above the name fields, every poll shows the participation advisory: answers may be used promotionally or internally and can be seen by people who do not share the respondent's views. A prominent "Keep me anonymous" button records a request to strip identifying details, and a required checkbox confirms the visitor read the notice.
  • Results appear after voting, always, or never — the admin decides per poll. Quoted comments are credited as "First L.", never with a full surname.
  • A respondent who asks to stay anonymous has their votes counted but their written answers kept out of the public results entirely. The form promises to remove names and contact details before their words appear publicly, and an answer can name its author inside the text — so staff quote those deliberately from the admin rather than the page publishing them automatically.

Admin

Content → Polls (/admin/content/polls, permission content:read to view, content:write to change).

TabWhat it controls
PollTitle, URL, subtitle, intro, image (from the Media Library) and alt text, accent colour, status, visibility, results visibility, publish/open/close times, one-answer-per-device, email field, search indexing
QuestionsThe question builder: answer style, prompt, help text, per-question image, required flag, character limit, selection bounds, an optional "something else" box, and the option list with emoji, description and image per option
ResponsesEvery answer received, newest first, with the anonymity request shown next to each name, plus a CSV export

Status and visibility

A poll goes live when its status is Published, or Scheduled with a publication time that has passed — nothing rewrites the row, so the "live" test is evaluated on read, the same way the rest of the CMS treats scheduled content. A poll cannot be published or scheduled until it has at least one question. Meta titles are held to 30–60 characters and descriptions to 160, matching every other page on the site.

  • Public — listed on /polls, in the sitemap, open to anyone including guests who arrive by accident.
  • Invite only — unlisted, excluded from the sitemap, marked noindex, and reachable only through a share link carrying the poll's access code (/polls/slug?key=…). Saving a poll as invite-only mints the code; switching it back to public clears it, which invalidates old links.

Editing a live poll

Questions and options keep their ids when edited, so answers already recorded stay attached. Deleting a question deletes the answers to it — the confirmation on the poll list says so before anything is removed.

Architecture

page.tsx

API

RouteMethodAccess
/api/polls/[slug]/respondPOSTPublic. Rate limited to 5 submissions per IP per 10 minutes through the distributed limiter, with a honeypot field and a repeat-submission check
/api/admin/cms/pollsGET, POSTcontent:read / content:write
/api/admin/cms/polls/[id]GET, PATCH, DELETEcontent:read / content:write
/api/admin/cms/polls/[id]/responsesGETcontent:read. Add ?format=csv to download

Staff with content:read but not content:write can open a poll and its responses, including the CSV export, in a view-only mode with the save control hidden.

Data model

PollPollQuestionPollOption, with PollResponsePollAnswer holding the submissions. Question types are SINGLE_CHOICE, MULTI_CHOICE, SHORT_TEXT, LONG_TEXT and RATING. A response stores anonymousRequested and consentAcknowledged alongside the name, so whoever writes promotional copy can see what the respondent asked for. IP addresses are stored only as an HMAC keyed with a server secret, used for the repeat-submission check and never displayed — a bare digest of an address would be enumerable by anyone holding a database dump.

The repeat-submission check is keyed on the requesting address, so it is a courtesy guard rather than an identity check: a household behind one router shares it, and a determined visitor can change networks. That is a deliberate trade — a stricter check would lock out shared connections.

Search indexing

/polls and every published public poll are registered in app/sitemap.ts. Invite-only and noIndex polls are excluded there and carry robots: { index: false } in their page metadata.

After publishing a new poll, run URL Inspection on its URL in Google Search Console — no agent can do this — and check a few weeks later that the indexed-page count is rising.

How is this guide?

Edit on GitHub

Last updated on

On this page