MPS-21: public profile page, offer-history identity, shop offers inbox, actions hub rebuild

- Offer history & offer page show the buyer's display name (User.display_name
  = the public `name` handle; `full_name` is private) linked to a profile
  page — never the email. _serialize_offer drops buyer_email; events carry
  actor_name/actor_handle/actor_id, header carries buyer_name/buyer_handle.
- New public profile page: GET /profile/{handle} (views/user.py:user_profile,
  template profile.j2). Shows gravatar (User.gravatar_url(size) — forced
  identicon unless the user opted into Gravatar), member-since, owned/edited
  shops, and a <details> "Show email" that is server-gated: only the user
  themselves, or a shop owner/editor viewing in that shop's context
  (?shop={shop_id}) when the profile user has transacted there (an offer or
  an invoice).
- New operator offers inbox: GET /s/{shop_id}/offers (@shop_editor_required,
  shop_offers.j2) — open offers first, each row links to /o/{id} and the
  buyer's profile. Reachable from /actions/view via a new "Offers" button
  (shown when shop.offer_enabled).
- /actions/view rebuilt: one flat .action-button-grid (Grid auto-fit,
  minmax(15rem,1fr)) inside a properly-padded .action-columns well — fixes
  the off-balance two-column layout and buttons overflowing the well; no
  <br> spacers. Styleguide gains profile-card and action-button-grid
  patterns.
- offer.j2: buyer name shown (linked to profile); "Buyer:"/"Seller:" message
  lines renamed "Buyer note:"/"Seller note:" to disambiguate.

Tests: 11 new functional tests (profile render + email gating, offers inbox,
actions button, styleguide). 989 passed.
This commit is contained in:
russell@unturf.com 2026-05-12 17:47:24 -04:00
parent cb61227b2c
commit e97d18bccd
No known key found for this signature in database
14 changed files with 654 additions and 68 deletions

View file

@ -452,10 +452,34 @@ Form sections:
Routes (registered before `product_slug` / `shop_slug` catch-alls):
- `/a/{auction_id}` + `/a/{id}.json` + `/a/{id}/{bid,buy-now,watch,checkout}`
- `/p/{product_id}/offer` (open) + `/o/{offer_id}` + `/o/{id}/{counter,accept,decline,withdraw,checkout}`
- `/s/{shop_id}/offers` — operator inbox (`@shop_editor_required`), linked from `/actions/view`
Offer/auction POST routes are **capability-driven**: a plain browser
submit gets a flash + `302` redirect; an AJAX submit (`X-Requested-With:
XMLHttpRequest`) gets JSON. `static/js/offer.js` + `auction.js` are the
enhancement layers. `offer.j2` shows a `.offer-state-notice` banner so
the state is clear without a flash.
Identity/privacy: never render a user's email in offer/auction UI. Show
`User.display_name` (= the public `name` handle; **`full_name` is
private**) linked to `/profile/{handle}`. The public profile page
(`views/user.py:user_profile`, route `user_profile``/profile/{name}`)
reveals the email only to the user themselves, or to a shop owner/editor
viewing in that shop's context (`?shop={shop_id}`) when the profile user
has transacted there. `User.gravatar_url(size)` forces an identicon
unless the user opted into Gravatar (`user.gravatar`).
See `docs/auction-house.md` and `docs/make-offer.md` for full state
machines and architecture.
### Actions hub (`/actions/view`)
`actions_view.j2` is one flat `.action-button-grid` (Grid `auto-fit`,
`minmax(15rem, 1fr)`) of `.mps-button` links inside an `.action-columns`
well — no `<br>` spacers, no fixed two-column split. Add new operator
shortcuts as another `<a class="mps-button product-edit-button">` in that
grid; it balances and wraps on its own.
## Feature Kill Switches (MPS-22)
Global feature flags live in `data/development.ini` (and override via env var