add double opt-in email verification for quote requests

Guest quote submissions now require email verification before the lead
goes live. The verification click also creates a user account and logs
them in. Logged-in users submitting with their own email skip verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-17 17:02:32 +01:00
parent e0563d62ff
commit 6a10f82b5d
8 changed files with 620 additions and 52 deletions

View File

@@ -6,6 +6,65 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
### Added
- **Double opt-in email verification for quote requests** — guest quote
submissions now require email verification before the lead goes live;
verification click also creates a user account and logs them in
automatically (GDPR-friendly consent trail)
- `GET /leads/verify` route — validates token, activates lead
(`pending_verification``new`, sets `verified_at`), logs user in, sends
admin notification and welcome email
- `send_quote_verification` worker task — branded verification email with
project details and "Verify & Activate Quote" CTA button (DEBUG mode
prints link to console)
- `quote_verify_sent.html` template — "Check your email" page shown after
guest quote submission
- Migration 0006 — adds `verified_at TEXT` column to `lead_requests`
- 9 new tests in `TestQuoteVerification` class covering the full
verification flow, expired tokens, duplicate verification, and user
creation
### Changed
- **Inline CTA full copy** — mobile/narrow-screen inline quote CTA now matches
sidebar: "Next Step" label, full title, description, 4 checkmark benefits,
"Get Supplier Quotes" button, and "Takes ~2 minutes" hint
- **Signup bar simplified** — removed `×` close button from guest signup bar;
now a non-dismissable nudge (still only shown on results tabs via JS)
- **Investment tab narrower** — CAPEX tab content constrained to 800px max-width
so 3-column card grid, table, and chart don't stretch across full 1100px on
wide screens
### Changed
- **Quote form → standalone 9-step HTMX wizard** — extracted "Get Quotes" from
planner Step 5 into a standalone multi-step wizard at `/leads/quote` using
server-rendered HTMX partials; each step validates server-side and swaps via
`hx-post`/`hx-get` with OOB dot progress updates; accumulated state passed
forward as hidden JSON field (no JS state management)
- **Planner reduced to 4 steps** — removed embedded quote form (Step 5) from
planner wizard; Step 4 "Get Quotes →" now navigates to `/leads/quote` with
pre-filled params (venue, courts, glass, lighting, country, budget)
- **Planner sidebar CTA** — "Get Supplier Quotes" button now links to standalone
quote wizard instead of scrolling to embedded Step 5; sidebar now visible on
all tabs including assumptions (was results-only)
- **Sticky wizard nav** — planner preview bar (CAPEX/CF/IRR) and back/next
buttons now stick to the bottom of the viewport so users don't have to scroll
to navigate between steps
- **Mobile quote CTA** — inline "Get Quotes" card shown below main content on
screens narrower than 1400px (where the fixed sidebar is hidden)
- **Step 4 → "Show Results"** — final planner wizard step now says "Show Results"
instead of "Get Quotes" since quote flow is a separate standalone wizard
- **Removed "2-5 suppliers" cap language** — replaced specific supplier count
promises with "matched suppliers" across landing page, supplier FAQ, planner
sidebar, and quote form privacy box
### Removed
- Inline quote form from planner (Step 5 HTML, `#wizSuccess`, hidden inputs)
- `populateWizAutoFill()`, `submitQuote()`, `COUNTRY_NAMES` from planner.js
- `__PADELNOMICS_QUOTE_URL__` JS variable from planner template
- Step 5 scoped CSS (~155 lines): `#wizQuoteForm`, `.wiz-autofill-summary`,
`.wiz-input`, `.wiz-privacy-box`, `.consent-group`, `.wiz-success`,
`.wiz-signup-nudge`, `.wiz-checkbox-label`
### Added
- **Supplier tier system** — Migration 0005 adds `tier` (free/growth/pro),
`logo_url`, `is_verified`, `highlight`, `sticky_until`, `sticky_country`