feat(activities): UI tweaks across feed, detail, hosting, calendar, scan, shell #91

Merged
padreug merged 25 commits from feat/ui-tweaks into dev 2026-06-10 16:35:50 +00:00
Owner

Closes #86 (pending events now visually grey out, not just badge-only).

A pass of UI tweaks across the activities standalone plus a small shell-level change that touches every standalone. Each commit is narrowly scoped so individual pieces can be reverted/picked.

Shell (every standalone)

  • Hamburger sidebar replaces the HubPill (13ece88). The fixed top-right "Back to hub" pill is gone; a hamburger button opens a right-side sheet reusing ProfileSheetContent (identity card, back-to-hub link, theme/lang/currency prefs, profile settings or log-in CTA). The Profile entry is removed from BottomNav and the loggedOutOpensSheet plumbing is dropped — Hub.vue still mounts ProfileSheetTrigger directly so it's unaffected. ProfileSheetContent gains an app-nav slot; AppShell exposes an optional sidebarNav prop.

Activities feed (ActivitiesPage)

  • Reclaim vertical space above the feed (b292cdb). Past events folds into the existing collapsible (renamed "Filters") alongside Categories; the trigger badge counts past-events + categories so hidden toggles stay discoverable. The standalone "Filters active / Clear all" notice is gone — Clear all sits inline next to the trigger. Header tightened, inter-row margins reduced.
  • Stationary Filters column next to scrolling pills (ce7f062). Filters icon + Clear-all sit in a stationary left-aligned column; only the temporal pills scroll horizontally. Clear all tucked tightly under the Filters icon. Badge no longer clipped by an overflow-x container.

Activity cards (ActivityCard / ActivityList)

  • Drop image placeholder when an event has no image (ea1e408). Cards without an image skip the 16:9 placeholder and surface badges inline at the top of the content block.
  • Refine card for pending/rejected + compact (0382f02917d4c40382f02, plus folded follow-ups via the same commit). Pending/rejected events wash out (opacity-50 grayscale on a wrapper div); the status badge is pulled outside the wash-out wrapper as an absolute overlay (-bottom-1 right-2, slight downward spill) so it keeps full color — both pending and rejected use the destructive token. Compact rows in the Hosting view now show a small left-aligned thumbnail (w-20 h-20, self-center, ml-3, rounded-md) when the event carries an image; Card root becomes relative (no overflow-hidden), the hero block picks up rounded-t-lg so corners still match.

Activity detail page (ActivityDetailPage)

  • Restructure layout (4924e70). Bookmark heart moves from the top bar to the right of the title; When/Where become caption-style lines directly under the title (no info cards); description moves directly under the title/info separator; organizer card moves to the bottom; the owned-tickets CTA is promoted to filled primary while Buy-another is demoted to outline so the My-Tickets path jumps out; ticket availability is rendered as an xs muted caption directly under the buy CTA.
  • Host detail view (f01d5aa). When the user owns the LNbits event: top-bar Scan + Edit buttons are dropped; Edit becomes a prominent filled-primary icon button right of the title; the Buy CTA is replaced with a full-width Scan tickets CTA (hoisted outside the ticketInfo gate so it always renders for the host). BookmarkButton + RSVPButton are hidden (favoriting / RSVPing your own event are noise). The badge row leads with "Yours" in the highlighted secondary variant; category and tags drop to outline.

Hosting feed (ActivitiesPage when onlyHosting)

  • Tailor Hosting tab + host detail view for operators (f01d5aa). Date picker strip + calendar shortcut and the entire Filters/temporal-pills row are hidden; search bar stays. Activity list renders compact rows via a new compact prop on ActivityList + ActivityCard.

Bottom navigation

  • Restructure bottom nav (a48e3ac). Tabs become Home, My tickets, Hosting, Map, Favorites. Feed is relabeled "Home" (en/fr; es already "Inicio"). Hosting is a synthetic tab — no path of its own; it toggles the existing onlyHosting feed filter and lands on /activities, with Home as the inverse (clears the filter on tap). Calendar leaves the bottom nav: a small calendar icon button sits to the right of the week strip in ActivitiesPage. Create activity leaves the bottom nav too; a full-width "+ Create activity" CTA appears at the top of the feed only when the Hosting tab is active. BottomTab gains an optional isActive() predicate so tabs whose active condition doesn't reduce to "current path starts with x" can compute their own state.

Calendar (ActivitiesCalendarPage)

  • My tickets toggle on the calendar view (0823b0f). Small filter chip above the month grid that, when on, limits the calendar to events the signed-in user holds a paid ticket for. Hidden when logged out. State is local to the page on purpose — narrowing the calendar shouldn't also narrow the feed. Left-aligned so it doesn't collide with the fixed top-right hamburger.

Scan tickets page (ScanTicketsPage)

  • Manual ticket registration from the roster tab (e05f276). The "Scanned" tab becomes "Tickets" and lists the full event roster (sold tickets), not just the registered subset. Unregistered rows lead the list with a Register button so the host can manually mark someone present without a QR scan — e.g. lost phone, known in person, or alternate proof of identity. useTicketScanner gains registerManually(ticketId) which reuses PUT /tickets/register/{id} (inheriting the ownership gate + unpaid/already-registered checks), refreshes stats, and mirrors the session-local dedup so a subsequent QR scan on the same ticket reports "Already scanned" instead of round-tripping. Header reads "registered / total · N to go".
  • Fuzzy search on the Tickets roster (9753c4a). Search box matches holder name + ticket id via the shared useFuzzySearch (Fuse.js) composable. Empty query keeps the unregistered-first sort; typing reorders by relevance. Empty-state distinguishes "no tickets sold yet" from "no matches".
  • Tab centering fix (e3f665c). Wrap each TabsTrigger's icon+label in an inline-flex items-center gap-1.5 span — TabsTrigger wraps its slot in an inline <span class="truncate">, so gap-1.5 on the trigger itself never reached the children. (1aeea23 was an interim attempt to relocate the counts strip below the camera; e3f665c reverts that and lands the actual fix.)

Purchase ticket dialog

  • Relabel "Get invoice" → "Proceed" (67a070e). The Lightning rail CTA now reads "Proceed" (or "Proceed buying (N tickets)" for multi-qty) to match the language used on the fiat rails.

Bugfix

  • Share filter refs across useActivities consumers (df7ab30). useActivityFilters allocated a fresh set of refs on every call — so activities-app/App.vue (Hosting tab) and ActivitiesPage each held independent onlyHosting/temporal/etc state. Tapping Hosting toggled the App.vue ref; the page never saw the change. Filter refs hoisted to module scope so every consumer shares one instance.

i18n

  • Adds common.nav.menu, activities.filters.filters, activities.filters.clearAll across en/es/fr + types.ts.
  • Relabels activities.nav.feed to "Home" (en) / "Accueil" (fr); es already "Inicio".

Test plan

  • Activities feed: temporal pills scroll horizontally; Filters icon + Clear-all stay stationary on the left; past-events toggle lives inside the Filters collapsible; trigger badge counts past + categories.
  • Activity card: pending/rejected event renders dimmed + greyscaled, status badge remains in full color at the bottom-right (spilling slightly off the card); compact hosting cards show thumbnails (when image) and single-column rows with gap-4 between them.
  • Activity detail page (regular viewer): bookmark heart sits right of the title; When/Where read as captions; description sits directly under the separator; organizer card at the bottom.
  • Activity detail page (host): no Scan/Edit in the top bar; Edit is a filled-primary icon right of the title; Buy CTA replaced with full-width Scan tickets; BookmarkButton + RSVP hidden; "Yours" leads the badge row in secondary variant.
  • Bottom nav: Home / My tickets / Hosting / Map / Favorites. Tapping Hosting toggles the filter ON and highlights Hosting; tapping Home clears the filter and highlights Home; "+ Create activity" CTA appears at top of feed only when Hosting is active. Calendar icon button sits to the right of the week strip and routes to /activities/calendar.
  • Hamburger sidebar (top-right): every standalone shows it; sheet contains profile (identity / log-in), back-to-hub link, theme + language + currency.
  • Calendar: "My tickets" chip top-left only when authed; when on, calendar only shows events with paid tickets.
  • Scan tickets page: Scanner / Tickets (N) tabs render with icon and label horizontally aligned (no vertical stacking). Tickets tab shows the full roster, unregistered first; Register button works for unregistered rows; fuzzy search filters by name + ticket id; empty states distinguish "no tickets sold" from "no matches".
  • PurchaseTicketDialog: Lightning CTA reads "Proceed" / "Proceed buying (N tickets)".
  • No regressions on the regular feed: signed-in user sees the feed, Past events toggle still hides past events by default, filter refs no longer drift between page and tab.
Closes #86 (pending events now visually grey out, not just badge-only). A pass of UI tweaks across the activities standalone plus a small shell-level change that touches every standalone. Each commit is narrowly scoped so individual pieces can be reverted/picked. ## Shell (every standalone) - **Hamburger sidebar replaces the HubPill** (`13ece88`). The fixed top-right "Back to hub" pill is gone; a hamburger button opens a right-side sheet reusing `ProfileSheetContent` (identity card, back-to-hub link, theme/lang/currency prefs, profile settings or log-in CTA). The Profile entry is removed from `BottomNav` and the `loggedOutOpensSheet` plumbing is dropped — `Hub.vue` still mounts `ProfileSheetTrigger` directly so it's unaffected. `ProfileSheetContent` gains an `app-nav` slot; `AppShell` exposes an optional `sidebarNav` prop. ## Activities feed (ActivitiesPage) - **Reclaim vertical space above the feed** (`b292cdb`). Past events folds into the existing collapsible (renamed "Filters") alongside Categories; the trigger badge counts past-events + categories so hidden toggles stay discoverable. The standalone "Filters active / Clear all" notice is gone — Clear all sits inline next to the trigger. Header tightened, inter-row margins reduced. - **Stationary Filters column next to scrolling pills** (`ce7f062`). Filters icon + Clear-all sit in a stationary left-aligned column; only the temporal pills scroll horizontally. Clear all tucked tightly under the Filters icon. Badge no longer clipped by an overflow-x container. ## Activity cards (ActivityCard / ActivityList) - **Drop image placeholder when an event has no image** (`ea1e408`). Cards without an image skip the 16:9 placeholder and surface badges inline at the top of the content block. - **Refine card for pending/rejected + compact** (`0382f02` → `917d4c4` → `0382f02`, plus folded follow-ups via the same commit). Pending/rejected events wash out (`opacity-50 grayscale` on a wrapper div); the status badge is pulled outside the wash-out wrapper as an absolute overlay (`-bottom-1 right-2`, slight downward spill) so it keeps full color — both pending and rejected use the destructive token. Compact rows in the Hosting view now show a small left-aligned thumbnail (`w-20 h-20`, `self-center`, `ml-3`, `rounded-md`) when the event carries an image; Card root becomes `relative` (no overflow-hidden), the hero block picks up `rounded-t-lg` so corners still match. ## Activity detail page (ActivityDetailPage) - **Restructure layout** (`4924e70`). Bookmark heart moves from the top bar to the right of the title; When/Where become caption-style lines directly under the title (no info cards); description moves directly under the title/info separator; organizer card moves to the bottom; the owned-tickets CTA is promoted to filled primary while Buy-another is demoted to outline so the My-Tickets path jumps out; ticket availability is rendered as an `xs` muted caption directly under the buy CTA. - **Host detail view** (`f01d5aa`). When the user owns the LNbits event: top-bar Scan + Edit buttons are dropped; Edit becomes a prominent filled-primary icon button right of the title; the Buy CTA is replaced with a full-width Scan tickets CTA (hoisted outside the `ticketInfo` gate so it always renders for the host). BookmarkButton + RSVPButton are hidden (favoriting / RSVPing your own event are noise). The badge row leads with "Yours" in the highlighted secondary variant; category and tags drop to outline. ## Hosting feed (ActivitiesPage when onlyHosting) - **Tailor Hosting tab + host detail view for operators** (`f01d5aa`). Date picker strip + calendar shortcut and the entire Filters/temporal-pills row are hidden; search bar stays. Activity list renders compact rows via a new `compact` prop on `ActivityList` + `ActivityCard`. ## Bottom navigation - **Restructure bottom nav** (`a48e3ac`). Tabs become Home, My tickets, Hosting, Map, Favorites. Feed is relabeled "Home" (en/fr; es already "Inicio"). Hosting is a synthetic tab — no path of its own; it toggles the existing `onlyHosting` feed filter and lands on `/activities`, with Home as the inverse (clears the filter on tap). Calendar leaves the bottom nav: a small calendar icon button sits to the right of the week strip in ActivitiesPage. Create activity leaves the bottom nav too; a full-width "+ Create activity" CTA appears at the top of the feed only when the Hosting tab is active. `BottomTab` gains an optional `isActive()` predicate so tabs whose active condition doesn't reduce to "current path starts with x" can compute their own state. ## Calendar (ActivitiesCalendarPage) - **My tickets toggle on the calendar view** (`0823b0f`). Small filter chip above the month grid that, when on, limits the calendar to events the signed-in user holds a paid ticket for. Hidden when logged out. State is local to the page on purpose — narrowing the calendar shouldn't also narrow the feed. Left-aligned so it doesn't collide with the fixed top-right hamburger. ## Scan tickets page (ScanTicketsPage) - **Manual ticket registration from the roster tab** (`e05f276`). The "Scanned" tab becomes "Tickets" and lists the full event roster (sold tickets), not just the registered subset. Unregistered rows lead the list with a Register button so the host can manually mark someone present without a QR scan — e.g. lost phone, known in person, or alternate proof of identity. `useTicketScanner` gains `registerManually(ticketId)` which reuses `PUT /tickets/register/{id}` (inheriting the ownership gate + unpaid/already-registered checks), refreshes stats, and mirrors the session-local dedup so a subsequent QR scan on the same ticket reports "Already scanned" instead of round-tripping. Header reads "registered / total · N to go". - **Fuzzy search on the Tickets roster** (`9753c4a`). Search box matches holder name + ticket id via the shared `useFuzzySearch` (Fuse.js) composable. Empty query keeps the unregistered-first sort; typing reorders by relevance. Empty-state distinguishes "no tickets sold yet" from "no matches". - **Tab centering fix** (`e3f665c`). Wrap each `TabsTrigger`'s icon+label in an `inline-flex items-center gap-1.5` span — TabsTrigger wraps its slot in an inline `<span class="truncate">`, so `gap-1.5` on the trigger itself never reached the children. (`1aeea23` was an interim attempt to relocate the counts strip below the camera; `e3f665c` reverts that and lands the actual fix.) ## Purchase ticket dialog - **Relabel "Get invoice" → "Proceed"** (`67a070e`). The Lightning rail CTA now reads "Proceed" (or "Proceed buying (N tickets)" for multi-qty) to match the language used on the fiat rails. ## Bugfix - **Share filter refs across `useActivities` consumers** (`df7ab30`). `useActivityFilters` allocated a fresh set of refs on every call — so `activities-app/App.vue` (Hosting tab) and `ActivitiesPage` each held independent `onlyHosting`/temporal/etc state. Tapping Hosting toggled the App.vue ref; the page never saw the change. Filter refs hoisted to module scope so every consumer shares one instance. ## i18n - Adds `common.nav.menu`, `activities.filters.filters`, `activities.filters.clearAll` across en/es/fr + `types.ts`. - Relabels `activities.nav.feed` to "Home" (en) / "Accueil" (fr); es already "Inicio". ## Test plan - [ ] Activities feed: temporal pills scroll horizontally; Filters icon + Clear-all stay stationary on the left; past-events toggle lives inside the Filters collapsible; trigger badge counts past + categories. - [ ] Activity card: pending/rejected event renders dimmed + greyscaled, status badge remains in full color at the bottom-right (spilling slightly off the card); compact hosting cards show thumbnails (when image) and single-column rows with `gap-4` between them. - [ ] Activity detail page (regular viewer): bookmark heart sits right of the title; When/Where read as captions; description sits directly under the separator; organizer card at the bottom. - [ ] Activity detail page (host): no Scan/Edit in the top bar; Edit is a filled-primary icon right of the title; Buy CTA replaced with full-width Scan tickets; BookmarkButton + RSVP hidden; "Yours" leads the badge row in secondary variant. - [ ] Bottom nav: Home / My tickets / Hosting / Map / Favorites. Tapping Hosting toggles the filter ON and highlights Hosting; tapping Home clears the filter and highlights Home; "+ Create activity" CTA appears at top of feed only when Hosting is active. Calendar icon button sits to the right of the week strip and routes to `/activities/calendar`. - [ ] Hamburger sidebar (top-right): every standalone shows it; sheet contains profile (identity / log-in), back-to-hub link, theme + language + currency. - [ ] Calendar: "My tickets" chip top-left only when authed; when on, calendar only shows events with paid tickets. - [ ] Scan tickets page: Scanner / Tickets (N) tabs render with icon and label horizontally aligned (no vertical stacking). Tickets tab shows the full roster, unregistered first; Register button works for unregistered rows; fuzzy search filters by name + ticket id; empty states distinguish "no tickets sold" from "no matches". - [ ] PurchaseTicketDialog: Lightning CTA reads "Proceed" / "Proceed buying (N tickets)". - [ ] No regressions on the regular feed: signed-in user sees the feed, Past events toggle still hides past events by default, filter refs no longer drift between page and tab.
- Move bookmark heart from top bar to the right of the title.
- Replace the When/Where info cards with caption-style lines directly
  under the title (calendar + map-pin icons + muted text).
- Move description above the organizer so it sits right under the
  title/info separator; push the organizer card to the bottom.
- Promote the "you own N tickets" CTA (filled primary "View" button)
  and demote "Buy another ticket" to outline when the user already
  owns tickets, so the My-Tickets path is what jumps out.
- Tighten ticket availability against the buy button: standalone strip
  removed, count rendered as an xs muted caption directly under the
  buy CTA.
The top-right "Back to hub" pill in each standalone is replaced by a
hamburger button that opens a right-side sheet reusing the existing
ProfileSheetContent (identity card, back-to-hub link, theme/lang/
currency prefs, profile settings or log-in CTA). The redundant
Profile entry is removed from BottomNav and its loggedOutOpensSheet
plumbing (BottomNav → AppShell) is dropped — Hub.vue still mounts
ProfileSheetTrigger directly so it's unaffected.

ProfileSheetContent gains an `app-nav` slot so standalones can inject
app-specific nav items above the cross-app section. AppShell exposes
a new optional `sidebarNav` prop that forwards items to the menu;
unset on non-activities standalones, those still get the hamburger
menu showing just the shared profile/preferences content.

Activities passes "My tickets" (routes to /my-tickets) and "Hosting"
(toggles the onlyHosting feed filter and lands on /activities), so
those entries leave the inline filter chip row on ActivitiesPage and
live in the sidebar instead. The "Past events" chip stays inline —
it doesn't require auth and pairs visually with the temporal filters.
Past events no longer gets its own row — it folds into the existing
collapsible (renamed "Filters") alongside Categories, so the feed
gains that row by default. The Filters trigger badge counts past-
events being on plus any selected categories, so users still see at
a glance when hidden toggles are active.

The standalone "Filters active / Clear all" notice is gone too;
Clear all sits inline beside the trigger only when something's
active. Header is tightened (text-xl) and inter-row margins drop
from mb-4 to mb-3 across the date strip + temporal pills.
Filters icon + Clear-all sit in a stationary left-aligned column;
only the All/Today/Tomorrow/etc temporal pills scroll horizontally.
Clear-all is tucked tightly under the Filters icon (h-5, 10px text,
gap-0.5) and shows only when a filter is active. The badge no
longer lives inside the overflow-x scroll container, so the count
chip isn't clipped at the corner anymore.
Cards without an image no longer render the solid-color 16:9
placeholder + calendar glyph. They go straight to the content area
with the badges (category, price, Yours, status, Past) shown
inline in a small row at the top, so the title and details aren't
pushed below a meaningless filler block.

The placeholderBg computed (hash → HSL) is removed; it was only
feeding the deleted no-image branch.
The bottom-nav tabs become Home, My tickets, Hosting, Map, Favorites.
- Feed is relabeled "Home" (en/fr; es was already "Inicio").
- My tickets and Hosting move out of the sidebar menu back into the
  bottom nav. Hosting is a synthetic tab — no path of its own; it
  toggles the existing onlyHosting feed filter and lands on
  /activities, with Home as the inverse (clears the filter on tap).
- Calendar leaves the bottom nav. The week strip now ends with a
  small calendar icon button that routes to /activities/calendar,
  so the entry point sits adjacent to the date UI instead of
  competing for a tab slot.
- Create activity leaves the bottom nav too. A full-width "+ Create
  activity" CTA appears at the top of the feed only when the Hosting
  tab is active, so the Create entry point lives inside the section
  it belongs to.

BottomTab gains an optional `isActive()` predicate so tabs whose
active condition doesn't reduce to "current path starts with x"
(e.g. Hosting) can compute their own state.
useActivityFilters allocated a fresh set of refs on every call, so
when activities-app/App.vue (Hosting bottom-nav tab) and
ActivitiesPage.vue each invoked useActivities(), they got
independent onlyHosting/temporal/etc state. Tapping Hosting toggled
the App.vue ref; the page never saw the change. Hoist the filter
refs to module scope so every consumer shares the same instance.
Hosting feed (ActivitiesPage when onlyHosting):
- Hide the date picker strip + calendar shortcut and the entire
  Filters/temporal-pills row; an operator managing their roster
  doesn't need calendar navigation or temporal narrowing.
- Keep the search bar — finding a specific event in a long roster
  still matters.
- Render compact cards via a new `compact` prop on ActivityList +
  ActivityCard: no hero image, single-line title, no summary, no
  bookmark, no "Yours" badge (every card is the operator's own),
  tighter p-3 padding, single-column flex layout.

Host detail view (ActivityDetailPage when ownedLnbitsEvent):
- Drop the top-bar Scan and Edit buttons. Edit moves into the title
  row as a prominent filled-primary icon button right of the title;
  Scan moves into the tickets section.
- Render a full-width "Scan tickets" CTA in place of Buy ticket, and
  hoist it outside the ticketInfo gate so it appears even on hosted
  events that were published without AIO ticket tags.
- Hide BookmarkButton and RSVPButton for the host (favoriting /
  RSVPing your own event are noise affordances).

Detail-page badge row: "Yours" leads the row in the highlighted
secondary variant; category and tags drop to outline so the
ownership signal stands out.
Adds a small filter chip above the month grid that, when on, limits
the calendar to events the signed-in user holds at least one paid
ticket for (intersecting ownedActivityIds from useOwnedTickets).
Hidden when logged out — nothing to own. Left-aligned so it
doesn't collide with the fixed top-right hamburger menu.

State is local to the page on purpose: narrowing the calendar
shouldn't also narrow the feed when the user navigates back.
- Wash out pending/rejected events with opacity-50 + grayscale on a
  wrapper div so the operator sees at a glance the event isn't live,
  not just the small badge.
- Pull the status badge OUT of the wash-out wrapper and absolute-
  position it on Card root (bottom-2 left-2, z-10) so it stays in
  full color above the dim card. Both pending and rejected use the
  destructive token — the label text differentiates the two states.
  Bottom-left so it doesn't collide with the category chip on full
  cards or the thumbnail on compact ones.
- Compact rows in the Hosting view now show a small left-aligned
  thumbnail (w-20 h-20, self-center, ml-3, rounded-md) when the
  event carries an image — host can still recognize each event at a
  glance without paying the visual weight of a full hero.
- Card root becomes `relative overflow-hidden`; the wrapper div
  owns the conditional flex-row (compact) / flex-col (default)
  layout and the opacity/grayscale toggling.
The Scanned / Sold / Remaining strip moves out of the page header
to below the Tabs block. The camera (or scanned list, depending on
the active tab) stays prominent at the top; the counts read as a
summary footer instead of competing with the title for attention.
The stats-error notice follows the counts strip so the warning
stays adjacent to the values it affects.
Reverts 1aeea23 and folds in the actual fix the relocation was
chasing: the Scanner / Scanned tab labels were rendering with
their icons and text mis-aligned because TabsTrigger wraps its
slot in an inline `<span class="truncate">`. A `gap-1.5` on
TabsTrigger never reached the icon/label children. Wrap each
trigger's content in an `inline-flex items-center gap-1.5` span
so the icon and label share a real flex container.
The "Scanned" tab becomes "Tickets" and now lists the full event
roster (sold tickets), not just the registered subset. Unregistered
rows lead the list with a Register button so the host can manually
mark someone present without a QR scan — e.g. lost phone, known in
person, or alternate proof of identity.

useTicketScanner gains registerManually(ticketId), which calls the
same PUT /tickets/register/{id} the scanner uses (so it inherits
the event-ownership gate and the unpaid/already-registered backend
checks), then refreshes stats. It skips the scanner pause + full-
screen banner since the operator initiated the action from the
list, and mirrors the session-local dedup so a subsequent QR scan
on the same ticket reports "Already scanned" instead of a duplicate
register round-trip.

The header now reads "registered / total · N to go" so the host
sees roster progress at a glance; failures from the manual register
surface as a sonner toast and the row reverts.
Adds a search box above the roster list that fuzzy-matches the
holder name and ticket id via the shared useFuzzySearch (Fuse.js)
composable. Empty query keeps the unregistered-first sort intact;
typing reorders by relevance. The empty-state message now
distinguishes "no tickets sold yet" from "no rows matched the
current query" so a busy roster + a typo doesn't look like
backend trouble.
The empty state rendered "No activities found" twice — once as the
heading and once as the description below, because
`activities.noActivities` and `activities.search.noResults`
translate to the same string in every locale. Drop the description
paragraph (and its mb-1 spacer on the heading).
The lightning rail's CTA in PurchaseTicketDialog now reads
"Proceed" (or "Proceed (N tickets)" for multi-quantity) instead
of "Get invoice". Matches the language used on the fiat rails
("Continue to Stripe checkout" etc.) and reads as a generic
forward action regardless of which payment path the user picks.
padreug force-pushed feat/ui-tweaks from 67a070e9b3 to 08f1743557 2026-06-06 21:18:52 +00:00 Compare
The Avatar primitive paired bg-secondary with text-foreground, which
isn't a Shadcn-designed pair. In themes whose secondary is a bright
color (Starry Night dark: bright yellow) the global foreground
(near-white) lands on top and the initial becomes unreadable.

Switch to text-secondary-foreground so contrast is whatever the theme
author guaranteed for that pair. Pre-emptively protects any future
theme since every theme must keep secondary/secondary-foreground
contrast valid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
padreug force-pushed feat/ui-tweaks from d7105f9570 to 136a4c92fb 2026-06-09 19:22:46 +00:00 Compare
padreug force-pushed feat/ui-tweaks from 136a4c92fb to c88df44545 2026-06-10 13:58:08 +00:00 Compare
padreug force-pushed feat/ui-tweaks from c88df44545 to 34ecbd570a 2026-06-10 14:20:32 +00:00 Compare
padreug force-pushed feat/ui-tweaks from 34ecbd570a to 4f7a5dcd88 2026-06-10 15:05:26 +00:00 Compare
Defensive guard against content (long addresses, code spans, etc.)
forcing a horizontal scroll inside the hamburger / profile sheet.
The root cause for individual cards is addressed separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Read-only identity (avatar, display name, @username, copyable
Lightning address, copyable npub) stays visible in the sheet so the
user can grab their address without opening any form. The full edit
form moves behind a gear-icon dialog. Log out stays in the sheet
footer so signing out doesn't require opening the popup.

Lightning Address and NIP-05 share the same `username@domain` in this
stack — the @username row above the card already signals NIP-05, so
the copyable address row is labeled just "Lightning".

The picture-upload row now stacks the preview above the upload widget
on narrow viewports so the Gallery/Camera buttons stay inside the
sheet/dialog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restyle the Lightning / npub rows so the value gets the entire row
and the field-name label sits as a small badge straddling the top
border (fieldset-legend pattern). Long bech32 / username@domain
strings now have room to render without truncation crowding.

The bolt icon picks up a yellow tint so the Lightning row reads at
a glance alongside the neutral npub row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Past used to live in the Filters collapsible — a dropdown row of its
own for a single boolean toggle. Hoist it onto the temporal strip
right after This month so it's discoverable alongside the time-window
pills without claiming any extra vertical space. Composes orthogonally
with the temporal pills the same way as before.

Drop the now-redundant past-count contribution to the Filters badge —
the pill carries its own pressed state on the strip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two narrow-viewport overflow points:

- Pricing row was a hard grid-cols-3 even at 320–360px viewports, so
  the Currency select was getting pinched. Drop to grid-cols-2 with
  Currency spanning both cols on small screens, lift to grid-cols-3
  at sm+.

- Action row was justify-end with no wrap and no width fallback, so a
  wide localized "Submit Event" label could push Cancel out of the
  dialog. Stack full-width on mobile (flex-col-reverse so Submit is
  under the thumb), back to inline at sm+.

Also belt-and-suspenders: overflow-x-hidden on the dialog content so
any future runaway child can't induce a horizontal scroll inside the
dialog body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Going/Maybe/Not going row was redundant: the bookmark heart count
already signals casual interest and ticket sales answer "who's
actually going". Cuts an affordance whose value-add was unclear and
whose visual weight competed with the buy-ticket CTA right below it.

Removes the RSVPButton component, the useRSVP composable, and the
i18n strings that only fed those buttons. Keeps NIP52_KINDS.RSVP and
the CalendarRSVP type as protocol documentation in case we revisit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Map is a primary discovery surface for events, so it earns the second
slot next to Home instead of sitting at the tail of the auth-gated
tabs (My tickets, Hosting). Unchanged auth gating on the other tabs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the bare "Events" h1 with the brand-kit logo paired with the
standalone's localized name. Deployers get per-standalone logo
control via branding/<dep>/icons/events/logo.{svg,png}; the
component itself stays brand-agnostic.

Brand-kit plumbing:

- `resolveAppLogo(app?)` in vite-branding.ts mirrors the resolution
  chain pwa-assets.config.ts already uses for PWA icons
  (per-standalone svg → png → global svg → png).
- `brandAppLogoAliasEntry(app)` returns a vite alias array entry. A
  regex matches `@brand-app-logo` with or without a `?url` query so
  the file resolves cleanly under either form.
- vite.events.config.ts switches its resolve.alias to the array form
  so the per-standalone regex doesn't clash with the bare `@brand`
  string alias.

Component side: a single `import brandAppLogoUrl from '@brand-app-logo?url'`
gives EventsPage the best-resolved logo without any fallback chain
in the component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
padreug deleted branch feat/ui-tweaks 2026-06-10 16:35:50 +00:00
Sign in to join this conversation.
No description provided.