feat(supporter): render compact supporter chip in TopBar#144
Merged
Conversation
Surfaces the supporter pill on every signed-in page (not just
/u/<handle> self view) by piggybacking on the existing layout-level
Promise.allSettled scaffold. `getSupporterStatus` joins the existing
location / shared / catalog fan-out so the chip costs one extra
fetch per shell render — request-level cached by React anyway.
Web changes:
- layout.tsx adds getSupporterStatus to the Promise.allSettled
block, fail-soft to null on error so a /v1/me/supporter hiccup
doesn't blank the chrome (same posture as the other shell
fetches).
- TopBar.tsx accepts an optional `supporter` prop, renders
<SupporterChip size="sm" /> next to the @handle pill. Compact
palette size mirrors the existing topbar density.
E2E fixture (required per `Playwright fixture default rule`):
- api-mock.ts gets a default `/v1/me/supporter` fixture pointing
at state=none so existing scenarios that don't care about
supporter logic don't 599 on the new layout fetch.
- New `supporterStatus(tier, plate)` helper for tests that DO
want to exercise the chip.
Self-profile chip from PR #143 continues to work; the topbar chip
just makes the recognition visible on /dashboard, /journey, /sharing
etc. without forcing the user to navigate to their profile page.
Follow-up: public/friend profile chip (#145), discover/profiles
chip (#146).
6 tasks
ntatschner
pushed a commit
that referenced
this pull request
Jun 1, 2026
Extends the supporter recognition pill (PR #143 self-only, PR #144 topbar) to anyone visiting a public profile or accepted-share view. Continuation of the chip rollout: #143 → #144 → **#145 (this PR)** → #146 (discover bulk). Server changes: - `SupporterStore` gains `get_by_handle_public(handle)` — single-query JOIN of `users` + `supporter_status` + LATERAL most- recent completed `revolut_orders`. Returns `Ok(None)` for unknown handles, missing rows, or `state=none`; returns `Ok(Some(_))` only for `active`/`lapsed`. Case-insensitive handle match mirrors the rest of the project. - `PublicSummaryResponse` gains `supporter: Option<PublicSupporterInfo>`. `PublicSupporterInfo` is a deliberate projection of the store shape — only `state` + `current_tier_key` + `name_plate` are surfaced. `grace_until`, payment timestamps, and `cancelled_at` are explicitly NOT exposed to strangers (fingerprinting + churn leak avoidance). - `render_summary` + `render_summary_scoped` now take a `&dyn SupporterStore` and populate `supporter` on every public summary response. Fail-soft on lookup error (warn log; chip just doesn't render). Three handler call sites updated: `public_summary`, `friend_summary`, `preview_summary`. Each takes `Extension(Arc<dyn SupporterStore>)`; same dyn-cast pattern as `share_metadata_dyn`. - `main.rs` wires `supporter_store_dyn` + adds the Extension layer alongside `share_metadata_dyn`. - `openapi.rs` registers `PublicSupporterInfo` so the TS client regen picks it up. Web changes: - `/u/[handle]/page.tsx` extends the supporter wiring from PR #143. Self path keeps fetching `/v1/me/supporter` for full DTO. Public + shared paths now read `data.supporter` from the extended PublicSummaryResponse. Both feed the same `<SupporterChip>` via a `ChipStatus` type alias (the three-field overlap between `SupporterStatusDto` and `PublicSupporterInfo`). Tests: - 4 new SupporterStore tests on the memstore: unknown handle, unbound handle (row exists but no handle binding — simulates Postgres JOIN miss), bound handle returns active row with case-insensitive lookup, state=none filtered out. - `MemorySupporterStore` gains `bind_handle(handle, user_id)` so tests can simulate the users-table side of the JOIN. - All existing supporter + sharing tests still pass (35 sharing, 8 supporter, 666 filtered). Follow-up: PR #146 — discover/profiles chip (needs bulk `get_many_public(user_ids)` to avoid N+1).
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Surfaces the supporter pill on every signed-in page (not just
/u/<handle>self view from PR #143) by piggybacking on the existing layout-levelPromise.allSettledscaffold.getSupporterStatusjoins the existing location / shared-with-me / catalog fan-out so the chip costs one extra fetch per shell render — request-level cached by React anyway.Continuation of the chip rollout: PR #143 (profile self) → #144 (topbar, this PR) → #145 (public/friend profile) → #146 (discover/profiles).
Web changes
layout.tsxaddsgetSupporterStatusto thePromise.allSettledblock, fail-soft tonullon error so a/v1/me/supporterhiccup doesn't blank the chrome (same posture as the other shell fetches).TopBar.tsxaccepts an optionalsupporterprop, renders<SupporterChip size="sm" />next to the@handlepill. Compact palette size mirrors the existing topbar density.E2E fixture
Required per the
Playwright fixture default rule(CLAUDE.md): any new authenticated layout-level fetch needs a default fixture inscenarioFor()'s base map, otherwise every existing scenario 599s.api-mock.tsgets a default/v1/me/supporterfixture pointing atstate=noneso existing scenarios that don't care about supporter logic don't 599 on the new layout fetch.supporterStatus(tier, plate)helper for tests that DO want to exercise the chip.Test plan
pnpm --filter web run test:run— 40 tests pass (no new tests; SupporterChip already covered in PR feat(supporter): render tier-styled chip on self profile view #143)pnpm --filter web run typecheckcleanpnpm --filter web run lint— no new warningsFollow-ups