Skip to content

Add Build the Vote volunteer pages - #110

Merged
xrendan merged 6 commits into
mainfrom
feat/build-the-vote
Oct 16, 2026
Merged

xrendan merged 6 commits into
mainfrom
feat/build-the-vote

Conversation

@xrendan

@xrendan xrendan commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Summary

The Election Day volunteer drive we're running with CJPAC, at the address CJPAC suggested:

  • /toronto/volunteer: the landing page with the sign-up form
  • /toronto/volunteer/step-2: follow-up questions (noindex)
  • /toronto/volunteer/thanks: save the date, invite friends, plan to vote (noindex)

Rebuilt from CJPAC's preview (buildthevote2026.netlify.app) using the site's election-page layout, Button, the election-day countdown and theme tokens. The copy is CJPAC's, with the changes listed below.

How it works

  • Formstack embed (FormstackForm.tsx): the forms stay in CJPAC's Formstack account. Their embed script document.write()s the form, so we capture its output and insert it ourselves; CJPAC's code runs unchanged. formstack.css restyles the form to match our inputs and buttons. All the Formstack field IDs are in build-the-vote.ts.
  • Consent: following Lucy's request, the "Send me email updates" checkbox and CJPAC's consent text are hidden and replaced with: "By submitting this form, you consent to being contacted by a CJPAC staff member or volunteer and to receive Build Canada emails." The hidden checkbox is ticked automatically so the lead data records that consent.
  • Email off the URL: step 1 redirects to step 2 with the volunteer's email in the query string. src/proxy.ts moves it into a short-lived httpOnly cookie scoped to step 2 and 303s to the clean URL before any HTML is served, so GA, PostHog and the pixels never see it. Other parameters like utm_* are kept.
  • Partner tag: the form's "UTM Source" field is filled with buildcanada and hidden. The visitor's own utm_source is left alone, so our analytics stay accurate.
  • Tracking: PostHog volunteer_signed_up fires on reaching step 2, and volunteer_invite_shared fires on sharing. Invite links carry referral UTM tags.
  • The landing page is added to the sitemap and has an OG image.
  • The Laurier quote on the closing bands (the new page and get-involved) now reads in full: "Whatever our errors are otherwise, we shall not err for want of boldness."

Copy changes from CJPAC's preview

  • Removed CJPAC's sentence "CJPAC will use your information to contact you about volunteering on Election Day." Open question: probably restore it before launch.
  • Step 2 and thanks don't show the program line ("Build the Vote is a Build Canada – Toronto volunteer program…") because they use the site footer.
  • Added a closing headline, "Two hours. A close race. Your call.", taken from the preview's mobile sticky bar, which isn't included.
  • Thanks page: added "They may call from a number you don't recognize." from the confirmation-email draft.
  • Small rewordings: the hero eyebrow, the polls-hours lines and the page titles.

Needed from CJPAC before launch

  • Step 1 submit redirect → https://www.buildcanada.com/toronto/volunteer/step-2/?email={email}. The parameter can be email or field199478152.
  • Step 2 submit redirect → https://www.buildcanada.com/toronto/volunteer/thanks/

Test plan

  • eslint and tsc pass
  • All three routes return 200, with and without a trailing slash
  • /step-2?email=…&utm_source=x 303s to /step-2?utm_source=x and sets the scoped httpOnly cookie
  • In a headless browser: the form renders once (including under the StrictMode double mount); the UTM field reads buildcanada; the opt-in is ticked; the hidden fields have display: none; step 2's hidden email field is filled from the cookie
  • No horizontal overflow at 390px on any page
  • End-to-end test submission with CJPAC once their redirects are set; ask them to delete the entry

Also in this PR

  • Moved the gtag-init <Script> in src/app/layout.tsx inside <head>. It was loose in <html>, so React warned about script order and hydration broke. The script still runs before the page becomes interactive on every page, so GA and Ads tracking should work as before.
  • Step 1 identifies each valid sign-up in PostHog, and in Customer.io through a new /api/volunteer route that calls the Track API (/api/v2/entity, identified by email). The profile gets first_name, last_name, city, postal_code, toronto_2026_build_the_vote_volunteer: true and toronto_2026_build_the_vote_signed_up_at (a Unix timestamp). Nothing goes to York Factory, and the phone number stays with CJPAC only. Before merge, set CUSTOMERIO_SITE_ID and CUSTOMERIO_API_KEY in the deployment env (the workspace is in the US region). Without them, the Customer.io identify is skipped. Tested locally end to end: Formstack accepted the submission, and Customer.io accepted the identify.
  • Formstack forms post into a hidden iframe. When the iframe loads (Formstack has the submission), we send the visitor to our own step 2, and from there to our thanks page. This doesn't depend on the redirects in CJPAC's Formstack, which currently send people to CJPAC's hosted step 2. Tested end to end locally: step 1 → our step 2 (email filled, clean URL) → thanks.

🤖 Generated with Claude Code

Election Day volunteer sign-up with CJPAC at /toronto/volunteer, with
/step-2 and /thanks beneath it, rebuilt from CJPAC's preview in the
site's own layout and components.

The sign-up forms stay in CJPAC's Formstack account. FormstackForm
embeds them, fills the partner "UTM Source" field, and hides the email
opt-in and CJPAC's consent copy in favour of the consent line agreed
with CJPAC, which adds Build Canada email.

Step 1 redirects to step 2 with the volunteer's email in the query
string. The proxy moves it into a page-scoped, short-lived httpOnly
cookie and redirects to the clean URL before any HTML is served, so no
analytics tag or pixel records it.

Also uses the full Laurier quote on the closing bands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

React can't place a beforeInteractive <script> loose in <html>; it warned
about script order and broke hydration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

Step 1 now also posts the volunteer's name, email and postal code to
/api/subscribe (placement build_the_vote) on a valid submit, which York
Factory identifies to Customer.io, and identifies them in PostHog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

Replaces the copy into York Factory's subscriber list: step 1 now posts
to /api/volunteer, which identifies the volunteer to Customer.io's CDP
API (keyed by email, tagged build_the_vote_volunteer), alongside the
PostHog identify. Needs CUSTOMERIO_API_KEY in the deployment env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

Matches the demo's tighter vertical rhythm on phones (smaller step
numbers sat right above their titles, less padding between cards and
sections) and raises the smallest reading text to 17px.

Customer.io now goes through the Track API's v2 entity endpoint
(site ID + API key, identified by email) with the attributes
toronto_2026_build_the_vote_volunteer and
toronto_2026_build_the_vote_signed_up_at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

…he site

Content now sits in a ~1080px centred column on wide screens, like the
original design, with full-width backgrounds and rules.

Formstack posts into a hidden iframe, and once it has the submission we
send the volunteer to our own step 2 and thanks pages, so the flow no
longer depends on the redirects set in CJPAC's Formstack (which currently
point at CJPAC's hosted step 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@xrendan
xrendan merged commit da753d5 into main Oct 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant