- Flow 11: note CTA token in forward email + matching notification tasks - Flow 12 (new): supplier lead_respond endpoint + one-click CTA token flow - Flow 13 (was 12): add Marketplace admin dashboard row, update Leads row with search/filter/HTMX inline actions, note HTMX partials Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
11 KiB
User Flows
All user-facing flows through the padelnomics app. Use this as the reference when writing E2E tests or auditing coverage.
1. Visitor → Planner
Entry: /<lang>/ → click "Planner" in nav
| Step | URL | Notes |
|---|---|---|
| 1 | GET /<lang>/planner/ |
Wizard loads with default state (indoor, 6 courts, rent). s = default state, d = calc results. |
| 2 | Adjust any slider | POST /<lang>/planner/calculate (HTMX, hx-trigger="input changed delay:200ms") → returns #tab-content partial |
| 3 | Switch result tab | POST /<lang>/planner/calculate with activeTab=<tab> → HTMX swaps #tab-content |
| 4 | View charts | Charts embedded as <script type="application/json" id="chartX-data"> in response. initCharts() in planner.js renders them. |
| 5 | Wizard preview | #wizPreview updated OOB (hx-swap-oob="true") with CAPEX / monthly CF / IRR |
Auth required: No (logged-in users get their default scenario pre-loaded)
HTMX partials: calculate_response.html, wizard_preview.html
Key state: s (validated via validate_state()), d (calc output via calc())
2. Visitor → Quote Request (from Planner)
Entry: Planner → quote sidebar (>1400px wide) or inline CTA (results tabs, <1400px)
| Step | URL | Notes |
|---|---|---|
| 1 | GET /<lang>/leads/quote |
Planner passes state via query params — step 1 pre-populated |
| 2–9 | POST /<lang>/leads/quote/step/<n> |
HTMX: each step swaps #q-step content; progress bar OOB-swapped into #q-progress |
| 9 (submit) | POST /<lang>/leads/quote |
Standard HTML form POST (not HTMX) |
| After submit | Verification email sent if guest; or lead created directly if logged in | |
| Verify | GET /<lang>/leads/verify?token=...&lead=... |
Token validated → lead status updated → success page |
Auth required: No (guests get email verification; logged-in users skip verification)
Key validation: Step 1: facility_type required. Step 2: country required. Step 5: timeline required. Step 7: stakeholder_type required. Step 9: name, email, phone, consent required.
Email sent: send_quote_verification (worker task) — verify URL must include /<lang>/
3. Visitor → Quote Request (Direct)
Same as Flow 2 but arrives at /<lang>/leads/quote directly (no planner state). Step 1 shows the full editable form (no pre-fill).
4. Visitor → Directory
Entry: /<lang>/directory/ via nav
| Step | URL | Notes |
|---|---|---|
| 1 | GET /<lang>/directory/ |
Lists all suppliers, filter UI visible |
| 2 | Search/filter | GET /<lang>/directory/results?q=...&country=...&category=... HTMX swaps #dir-results |
| 3 | Click supplier card | GET /<lang>/directory/<slug> — supplier detail page |
| 4 | Send enquiry | POST /<lang>/directory/<slug>/enquiry HTMX swaps #enquiry-result |
| 4b | External link | GET /<lang>/directory/<slug>/website → 302 redirect (click-tracking) |
| 4c | Get quote | GET /<lang>/directory/<slug>/quote → redirect to quote wizard |
Auth required: No
Category/country labels: Must be translated per g.lang via get_directory_labels(lang)
5. Visitor → Signup
Entry: Any CTA "Create Account" / "Sign up" → /auth/signup
| Step | URL | Notes |
|---|---|---|
| 1 | GET /auth/signup |
Signup form (or waitlist form if WAITLIST_MODE=true) |
| 2 | Submit email | POST /auth/signup → sends magic link via send_welcome + send_magic_link tasks |
| 3 | Click email link | GET /auth/verify?token=... → session created, redirect to next param or /dashboard/ |
| 4 | Dashboard | GET /dashboard/ |
Auth required: No
Language detection: Auth routes have no <lang> prefix — lang detected from cookie or Accept-Language header via @bp.before_request
6. Returning User → Login
Entry: /auth/login
| Step | URL | Notes |
|---|---|---|
| 1 | GET /auth/login |
Login form |
| 2 | Submit email | POST /auth/login → enqueues send_magic_link |
| 3 | Confirmation | GET /auth/magic-link-sent |
| 4 | Click email link | GET /auth/verify?token=... → session set, redirect |
| 5 | Dashboard or prior page | Session user_id set |
Dev shortcut: GET /auth/dev-login?email=test@example.com (DEBUG mode only) — instant login, used in tests
7. User → Save/Load Scenario
Entry: Planner while logged in
| Step | URL | Notes |
|---|---|---|
| 1 | Open scenarios panel | GET /<lang>/planner/scenarios HTMX partial — lists saved scenarios |
| 2 | Save current state | POST /<lang>/planner/scenarios/save (JSON body: {name, state}) → creates/updates scenario |
| 3 | Load scenario | GET /<lang>/planner/scenarios/<id> → returns scenario JSON → JS populates form |
| 4 | Set default | POST /<lang>/planner/scenarios/<id>/default |
| 5 | Delete | DELETE /<lang>/planner/scenarios/<id> |
Auth required: Yes (@login_required)
8. User → Export PDF
Entry: Planner → "Export" tab/button
| Step | URL | Notes |
|---|---|---|
| 1 | GET /<lang>/planner/export |
Export options page (or waitlist if WAITLIST_MODE=true) |
| 2 | Checkout | POST /<lang>/planner/export/checkout → returns Paddle checkout URL (JSON) |
| 3 | Paddle checkout | External Paddle overlay/redirect |
| 4 | Post-checkout | GET /<lang>/planner/export/success |
| 5 | Download PDF | GET /<lang>/planner/export/<id> (checks subscription/purchase) |
Auth required: Yes (@login_required)
Email sent: send_welcome (if new user), PDF ready notification
9. Supplier → Signup
Entry: /<lang>/suppliers/signup or from nav "For Suppliers"
| Step | URL | Notes |
|---|---|---|
| 1 | GET /<lang>/suppliers/signup |
Plan selection (or waitlist if WAITLIST_MODE=true) |
| 2–4 | POST /<lang>/suppliers/signup/step/<n> |
HTMX wizard: step 2 = details, step 3 = credits, step 4 = contact |
| Checkout | POST /<lang>/suppliers/signup/checkout |
Returns Paddle URL |
| Success | GET /<lang>/suppliers/signup/success |
Post-checkout confirmation |
Auth required: No
Plans: basic (free listing), growth (leads), pro (pro listing + leads)
10. Supplier → Dashboard
Entry: Login → redirect to /<lang>/suppliers/dashboard (if supplier role)
| Tab | URL | Notes |
|---|---|---|
| Overview | GET /<lang>/suppliers/dashboard/overview |
Stats: views, leads, credits |
| Lead Feed | GET /<lang>/suppliers/dashboard/leads |
Lead cards (teased for basic, unlockable for growth/pro) |
| Listing | GET /<lang>/suppliers/dashboard/listing |
Edit supplier profile |
| Boosts | GET /<lang>/suppliers/dashboard/boosts |
Purchase credit packs |
Auth required: Yes — @_supplier_required (basic+); lead tabs require @_lead_tier_required (growth/pro)
Dashboard shell: GET /<lang>/suppliers/dashboard — tabs loaded via HTMX
11. Supplier → Unlock Lead
Entry: Lead feed in supplier dashboard
| Step | URL | Notes |
|---|---|---|
| 1 | View teased lead | GET /<lang>/suppliers/dashboard/leads — lead shown with blurred contact info |
| 2 | Unlock | POST /<lang>/suppliers/leads/<id>/unlock — deducts 1 credit, reveals full lead |
| 3 | Receive email | send_lead_forward_email task enqueued — full project brief sent to supplier with one-click CTA link |
| 4 | Entrepreneur notified | send_lead_matched_notification task — notifies entrepreneur a supplier was matched |
Auth required: Yes — @_lead_tier_required
Credit check: Server-side check; if 0 credits → redirect to boosts tab
Matching notification: On quote verification, notify_matching_suppliers task auto-notifies growth/pro suppliers whose service_area matches the lead's country (max 20 per lead); send_weekly_lead_digest sends a Monday 08:00 UTC summary of new matching leads to all paid suppliers
12. Supplier → Update Lead Response Status
Entry: Supplier dashboard leads tab, or one-click CTA link in forward email
| Step | URL | Notes |
|---|---|---|
| 1a | Click "Mark as contacted" in email | GET /suppliers/leads/cta/<cta_token> — one-click; advances status sent → contacted; redirects to /suppliers/dashboard?tab=leads |
| 1b | Update via dashboard | POST /<lang>/suppliers/leads/<token>/respond — HTMX; sets status and optional supplier_note; returns 204 |
Auth required: CTA link is unauthenticated (token is the credential); dashboard endpoint requires @_lead_tier_required
Valid statuses: sent / viewed / contacted / quoted / won / lost / no_response
Idempotency: CTA only advances sent → contacted; subsequent clicks are no-ops
13. Admin Flows
Entry: /admin/ (requires @role_required("admin"))
| Area | URL | What you can do |
|---|---|---|
| Dashboard | GET /admin/ |
Stats overview |
| Marketplace | GET /admin/marketplace |
Lead funnel, credit economy, supplier engagement, live activity stream, inline feature flag toggles |
| Users | GET /admin/users, /admin/users/<id> |
List, view, impersonate |
| Leads | GET /admin/leads, /admin/leads/<id> |
List (search + period filter + summary cards), view detail, HTMX inline status change + forward to supplier |
| Suppliers | GET /admin/suppliers, /admin/suppliers/<id> |
List, view, adjust credits, change tier, create |
| Feedback | GET /admin/feedback |
View all submitted feedback |
| Email Sent Log | GET /admin/emails, /admin/emails/<id> |
List all outgoing emails (filter by type/event/search), detail with API-enriched HTML preview |
| Email Inbox | GET /admin/emails/inbox, /admin/emails/inbox/<id> |
Inbound emails (unread badge), detail with sandboxed HTML, inline reply |
| Email Compose | GET /admin/emails/compose |
Send ad-hoc emails with from-address selection + optional branded wrapping |
| Audiences | GET /admin/emails/audiences, /admin/emails/audiences/<id>/contacts |
Resend audiences, contact list, remove contacts |
| Article Templates | GET /admin/templates |
CRUD + bulk generate articles from template+data |
| Published Scenarios | GET /admin/scenarios |
CRUD public scenario cards (shown on landing) |
| Articles | GET /admin/articles |
CRUD, publish/unpublish, rebuild HTML |
| Task Queue | GET /admin/tasks |
View worker tasks, retry/delete failed |
HTMX partials: lead_status_badge.html (status change), lead_forward_history.html (forward history), marketplace_activity.html (activity stream)
Dev shortcut: /auth/dev-login?email=<admin-email> where email is in config.ADMIN_EMAILS
Route Prefix Reference
| Blueprint | URL Prefix | Lang-prefixed? |
|---|---|---|
public |
/<lang> |
Yes |
planner |
/<lang>/planner |
Yes |
directory |
/<lang>/directory |
Yes |
leads |
/<lang>/leads |
Yes |
suppliers |
/<lang>/suppliers |
Yes |
content |
/<lang> (catch-all, registered last) |
Yes |
auth |
/auth |
No |
dashboard |
/dashboard |
No |
billing |
/billing |
No |
admin |
/admin |
No |
webhooks |
/webhooks |
No |
Language detection for non-prefixed blueprints: Cookie (lang) → Accept-Language header → fallback "en"
Known Test Shortcuts
- Dev login (no magic link):
GET /auth/dev-login?email=...(only whenDEBUG=True) - Admin login:
GET /auth/dev-login?email=<email-in-ADMIN_EMAILS> - Quote verify URL pattern:
GET /<lang>/leads/verify?token=...&lead=... - Auth verify URL pattern:
GET /auth/verify?token=...