Files
padelnomics/docs/USER_FLOWS.md
Deeman db14684667 docs: update USER_FLOWS.md for marketplace + lead response flows
- 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>
2026-02-25 09:41:54 +01:00

248 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 |
| 29 | `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`) |
| 24 | `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 when `DEBUG=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=...`