Commit graph

1,023 commits

Author SHA1 Message Date
e17b4e05ad Merge pull request 'feat(events): real-time favoriting + live like count + post-purchase refresh' (#111) from feat/events-realtime into dev
Reviewed-on: #111
2026-06-17 08:36:46 +00:00
b6d1626951 feat(events): show a live like count on the favorite heart
Display how many people have favorited (liked) an event next to its
heart, updating in real time. A like == the event appearing in someone's
NIP-51 bookmark list (kind 10003) — the same action the heart performs.

New useEventLikes composable keeps ONE batched subscription over every
mounted heart's event coordinate (filtered by #a). It stays open after
EOSE, so a like published by anyone is pushed live and the count ticks
up for everyone — verified end-to-end against a relay (a like from a
fresh key bumped the shown count 2→3 with no reload). The heart also
pops on a live increment (gated past the initial historical-load
window), and the user's own like/un-like reflects instantly via the
optimistic heart state.

Caveat: an un-like by another user only reflects on next load — a
replaceable list that no longer contains the coord stops matching the #a
filter, so the removal isn't pushed. Counts are correct on fresh load.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:36:27 +00:00
3d37b7393d fix(events): refresh owned tickets after purchase (no reload needed)
After a successful ticket purchase, the feed/calendar "My tickets" filter
and EventCard owned badges didn't update until a full page reload — the
shared useOwnedTickets singleton was never refreshed. Its own docs note a
successful purchase should call refresh(); wire that in at the payment-
confirmed point in useTicketPurchase so every surface reflects the new
ticket immediately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:36:27 +00:00
4f4452057a feat(events): make favoriting instant (optimistic) + pop animation
The heart took ~1s to fill because toggleBookmark awaited the remote
LNbits signer + relay publish before updating state. Flip local state
optimistically so the heart responds on tap, then sign/publish in the
background and roll back (with an error toast) if it fails. Add a brief
scale pop on the heart when a favorite is added for tactile feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:36:27 +00:00
9810b11cc5 Merge pull request 'feat(events): calendar date-picker popup, remove calendar page, My Tickets filtering' (#110) from feat/events-calendar-popup into dev
Reviewed-on: #110
2026-06-17 08:36:13 +00:00
9d98f3fdf2 fix(events): close calendar popup on route leave
Defensive guard so the date-picker popup can never linger across
navigation (reported: it appeared open on the feed after returning from
an event detail page). Force calendarOpen=false in onBeforeRouteLeave on
both the feed and My Tickets, the two popup hosts. Modals shouldn't
survive a route change regardless of how the close/navigation interleave.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
76af245192 feat(events): add calendar date visual to My Tickets
Replaces the My-tickets filter that lived on the removed calendar page.
A calendar button opens the date-picker popup with per-day dots over the
user's own events; picking a day filters the ticket list to it (a
removable date chip overrides the upcoming/past toggle while active).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
e54938ef06 refactor(events): remove the standalone calendar page
The calendar is now a date-picker popup on the feed (and, next, a visual
on My Tickets), so the dedicated /events/calendar page and route are no
longer needed. Delete EventsCalendarPage.vue, drop its route, and update
the stale openCreate comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
5753e34499 feat(events): filter the feed by day via the calendar popup
The calendar button now opens the date-picker popup instead of
navigating to a separate page. Picking a day filters the feed to that
day (button highlights while active) and shows a removable date chip
whose ✕ clears ONLY the date selection — distinct from category
clearing, which keeps its own clear in the filter dropdown. Dots in the
popup come from the full (unfiltered) event set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
b665e3de07 feat(events): re-add specific-day filter (calendar popup picks it)
Bring back selectedDate filtering, now driven by the calendar popup
instead of the removed week-strip: when a day is picked it takes
priority over the temporal pills and bypasses the past/upcoming split.
Add a dedicated clearSelectedDate() so the date selection can be cleared
independently of categories (a preset pill also clears it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
f7642db611 feat(events): add reusable calendar date-picker popup
Add a pickerMode to EventCalendarView (month grid only, emits selectDate
on every day tap, no selected-day events panel) and a new
EventCalendarPopup dialog wrapping it. Picking a day emits the date and
closes. Groundwork for replacing the standalone calendar page with an
on-feed date-picker popup and a My-Tickets event-date visual.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
8c9a8ab945 feat(events): put calendar My-tickets filter next to the Back button
The My-tickets filter chip sat on its own row below the Back link,
wasting vertical space. Move it onto the Back-button row (still
left-aligned to clear the fixed top-right hamburger), reclaiming a row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
3d1b888307 feat(events): compact the calendar month grid
The aspect-square day cells made the month grid ~350px tall on mobile,
pushing the selected-day event list well below the fold. Switch cells to
a fixed h-12 (still a comfortable 48px tap target) and tighten the
section spacing (space-y-4 → space-y-2), so the grid + day's events fit
with less scrolling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
c53bc525ef fix(events): move My Tickets upcoming/past toggle off the hamburger
The toggle sat in the header's top-right corner, where it collided with
the fixed top-right hamburger menu (the Past button rendered behind it).
Move it to its own left-aligned row above the tabs — same approach the
calendar page uses to clear the hamburger.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
1433a2039b feat(events): add upcoming/past toggle to My Tickets
My Tickets listed every ticket with no way to separate events that
already happened. Add an Upcoming/Past segmented toggle (defaults to
upcoming) that filters the grouped tickets by their event's date, with
the tab counts (All/Paid/Pending/Registered) derived from the visible
set so badges match what's shown. Events not yet resolved from relays
stay visible under Upcoming until their date is known.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
084bfc52eb refactor(events): remove dead specific-date filter logic
With the DatePickerStrip gone, selectedDate/selectDate in useEventFilters
were unreachable — nothing set a specific date anymore (the calendar page
only navigates to event detail). Delete the orphaned DatePickerStrip
component and strip the now-dead date-filter branch, state, and actions.
The feed filters purely by temporal pills + past/upcoming + categories.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
cdc8158e1b feat(events): drop the week-day strip, move calendar next to filters
The feed had two redundant day controls — the DatePickerStrip week strip
and the temporal preset pills — on top of the calendar page. Remove the
week strip (the calendar already covers picking a specific date) and move
the calendar shortcut to the end of the temporal-filter row, next to the
pills. Frees a row and keeps coarse windows (Today/This Week/…) inline
with one-tap access to the full calendar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
52e9d11ea9 feat(events): add Back button to the calendar page
The calendar page had no way back to the feed except the browser/back
gesture. Add a top-bar ghost Back link (ArrowLeft + "Back" → events feed)
mirroring EventDetailPage's pattern for navigation consistency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
32a7389d0b feat(events): default calendar selection to today
The month-grid calendar opened with no day selected, so the events panel
below was empty until the user clicked a day. Initialise the selection to
today (currentMonth already starts on the current month) so it opens
showing today's events.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:35:28 +00:00
c9fc3652bb Merge pull request 'feat(ui): cosmetic tweaks — profile pencil, pills, search, ticket count, map icon, avatar trigger, no overlay animations' (#105) from feat/ui-tweaks-2 into dev
Reviewed-on: #105
2026-06-17 08:35:08 +00:00
1249d33aac feat(ui): disable enter/exit animations on overlays globally
Reka-ui overlays (dialog, sheet, popover, dropdown, tooltip, …) animate
their open/close via the data-state open/closed attribute. Zero the
animation duration globally in index.css so overlays appear/dismiss
instantly — no fade/zoom/slide. Verified the dialog still mounts and
unmounts correctly (Presence resolves at 0s). Pulse/spin loaders and CSS
transitions (hovers, the favourite heart pop) are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:25:30 +02:00
8a0f40910a feat(layout): show avatar/login state in the top-right menu trigger
Replace the hamburger icon on the fixed top-right menu button with a
context-aware affordance: the user's avatar when logged in (first
initial when they have no picture), or a login icon when logged out.
Opens the same profile/menu sheet either way; aria-label reflects the
state. overflow-hidden + the avatar filling the round button keeps it
clipped to the chip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:25:30 +02:00
1c004df99c fix(events): even out the Map empty-state icon transparency
The placeholder map icon used text-muted-foreground/30, applying alpha to
the stroke colour so the lucide icon's overlapping fold lines compounded
to a darker shade where they crossed the outline. Use element-level
opacity-30 with the full colour instead, so the whole icon fades
uniformly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:25:30 +02:00
f20b404d09 feat(events): show total ticket capacity alongside remaining
Availability only showed remaining ("N tickets available"), not the total
capacity. Derive total (remaining + sold) on EventTicketInfo and display
"N of M tickets left" on both the event card and the detail page, so
buyers can gauge demand. Unlimited events are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:25:30 +02:00
79e20f1e07 feat(events): include organizer name in event fuzzy search
Search only indexed title/summary/description/location/tags, so typing an
organizer's name found nothing. Organizer display names aren't stored on
the event (they're resolved per-pubkey into the shared ProfileService
cache), so enrich the search corpus with the resolved name read from that
same reactive cache and add it as a Fuse key. Opening the search overlay
warms the cache for any organizers not yet fetched, and the corpus
recomputes as kind-0 metadata arrives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:25:30 +02:00
1f68660783 fix(activities): let temporal filter pills respect palette radius + shadow
Drop the hardcoded rounded-full on the temporal filter pills so they
inherit each palette's --radius (square under neobrut, gently rounded
elsewhere) and the theme's offset drop-shadow, instead of overriding it.
Add pb-1 pr-1 so neobrut's hard 4px shadow isn't clipped by the
overflow-x-auto scroll box.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:25:30 +02:00
2e55a45ed6 feat(profile): move edit affordance into identity card as a pencil
Replace the gear (Settings) button in the profile sheet header with a
pencil button inside the identity card, by the avatar/name row. A pencil
reads as "edit your profile" more intuitively than a header gear, and
co-locating it with the identity it edits is clearer. Opens the same
edit-profile dialog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:25:30 +02:00
d4d088fb50 feat(branding): per-brand default theme + palette via brand.json
Lets a deployer set the in-app color scheme a fresh visitor sees (e.g.
cfaun → darkmatter light) without forking. Two optional brand.json
fields, `theme` (light|dark|system) and `palette` (one of PALETTES),
distinct from `themeColor` which is PWA chrome only.

- vite-branding.ts surfaces them as VITE_BRAND_THEME / VITE_BRAND_PALETTE
  at module load, so the default applies app-wide (hub + all standalones)
  with no per-config wiring.
- theme-provider reads them as the INITIAL value of theme/palette; a
  user's stored choice in localStorage still wins and persists.
- Splits the catppuccin = bare `:root` invariant (now BASE_PALETTE, used
  by applyPalette to drop data-theme) from the configurable default.
  Without this, a non-catppuccin brand default would strip the
  data-theme attribute and silently render catppuccin instead.

Unset → the app's built-ins (dark + catppuccin), unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:12:13 +02:00
8f2c401e00 feat(branding): optional per-app banner replacing logo + name in header
A brand may ship a wide banner (logo + wordmark in one image) that
replaces the brand-kit logo + app-name pair in a standalone's header.
Events is the first consumer.

Banners are optional and resolve at build time, mirroring the existing
@brand-app-logo chain:

- resolveAppBanner(app?) checks per-standalone override first
  (branding/<dep>/icons/<app>/banner.{svg,png}) then the brand's primary
  banner (branding/<dep>/banner.{svg,png}); returns null when absent
  instead of throwing, so brands without a banner keep logo + name.
- brandAppBannerAliasEntry() always registers the @brand-app-banner
  alias (falling back to the logo) so the static import resolves; whether
  it renders is gated by the VITE_APP_BANNER build flag.
- EventsPage renders the banner when the flag is set, else logo + name.

Deployers override per-standalone without touching the component. SVG
banners must have their text outlined to paths (browsers lack designer
fonts) — documented in branding/README.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:11:01 +02:00
138f9905bf fix(wallet): accept uppercase QR-scanned BOLT11 invoices on send
The send path detected and decoded uppercase invoices correctly (SendDialog's
isBolt11 lowercases first) but WalletService.sendPayment gated the bolt11
branch on a case-sensitive startsWith('ln'), so QR-scanned invoices (uppercase
bech32, e.g. LNBC...) fell through to the else and threw "Invalid payment
destination format" despite the UI showing a valid decode.

Detect BOLT11 case-insensitively by its HRP (lnbc/lntb/lntbs/lnbcrt) and send
the lowercase canonical form. The bare 'ln' prefix also incidentally matched
bech32 LNURLs (lnurl1...) and misrouted them to the bolt11 endpoint; matching
the full HRP closes that gap too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:05:09 +02:00
db497d8a06 Merge pull request 'feat(activities): event name on My tickets + organizer on cards' (#102) from feat/event-name-and-organizer into dev
Reviewed-on: #102
2026-06-10 23:10:00 +00:00
42bff96c58 feat(activities): show organizer on event cards + route through ProfileService
Two changes that ship together:

- Compact variant of OrganizerCard (tiny avatar + display name on a
  single line) used on every feed EventCard so viewers see who's
  hosting before they tap into the detail page. Hidden on compact
  feed rows (host's own roster — they already know).

- Refactor useOrganizerProfile.ts to route through the centralized
  ProfileService. Two bugs the local impl was carrying:
  * `displayName` was exposed via a `get` accessor on the returned
    object; destructuring it (`const { displayName } = …`) resolved
    once at the destructure site, so a kind-0 arriving later never
    updated the bound name. Now exposed as `computed<string>`.
  * `relayHub.subscribe()` throws synchronously when the hub isn't
    connected yet. OrganizerCard mounted during cold start swallowed
    the throw silently in onMounted, leaving the user with the
    pubkey-truncated fallback forever. ProfileService.getProfile()
    awaits the relay-hub connection before issuing the filter.

  Bonus: ProfileService's kind-0 cache is shared with chat / market /
  nostr-feed, so a profile fetched by any module is immediately
  visible to all of them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 01:06:03 +02:00
c8930aee3e feat(activities): show event name (linked) on My tickets
Replace the "Event: <8-char-id>…" placeholder with the actual event
title, sourced from the shared events store, and wrap it in a
RouterLink to the event detail page. Card title truncates so long
names don't push the per-event ticket-count badge out of the row.

Subscribe to the events feed on mount so titles resolve as relay
events stream in — the user can land on My tickets directly from a
purchase flow without having to visit /events first to populate the
store. Falls back to the old short-id label until the event arrives
(or if it's been deleted upstream).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 01:06:03 +02:00
4b8fd34bba feat(events): surface brand name in page header + bump logo size
The events page H1 had `{{ t('events.title') }}` which is hardcoded
"Events" in the i18n locale — so cfaun's events standalone showed
"Events" instead of the configured displayName "Oyez!".

Read import.meta.env.VITE_APP_NAME (set by vite.events.config.ts from
brand.name → brandManifestName()) and fall back to the i18n title
when no brand is configured. Now:
- aiolabs default → "Events"
- cfaun → "Oyez!"
- any other deployment → its synthesized brand name

Also bumps the inline logo from h-7/sm:h-8 to h-10/sm:h-12 for more
header presence per cfaun feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 00:47:42 +02:00
5700ac1d1a feat(branding): migrate hardcoded @brand/logo.png to SVG-aware alias
The four shared layout/login components (Login, LoginDemo,
AppSidebar, MobileDrawer) hardcoded `<img src="@brand/logo.png">`,
which means an SVG-only brand kit (like cfaun's) fails the build
with "Could not load @brand/logo.png".

Switch the four to `<img src="@brand-app-logo">` — the alias registered
via brandAppLogoAliasEntry() (already used by events module) resolves
to whichever of logo.{svg,png} exists in BRAND_DIR (SVG preferred),
with per-app overrides under BRAND_DIR/icons/<app>/.

Also register brandAppLogoAliasEntry in the 8 vite configs that
didn't have it (only events did before), converting their alias maps
to array form so the regex-based logo entry doesn't get shadowed by
the bare-string `@brand` and `@` aliases.

Verified:
- AIO default brand (PNG-only): builds, ships logo-<hash>.png — no regression.
- cfaun brand (SVG-only): builds, ships logo-<hash>.svg — previous
  ENOENT on logo.png gone.

Unblocks cfaun deploy with an SVG-only brand kit (no manual
rasterization needed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 00:23:01 +02:00
80b8219494 Merge pull request 'feat(activities): UI tweaks across feed, detail, hosting, calendar, scan, shell' (#91) from feat/ui-tweaks into dev
Reviewed-on: #91
2026-06-10 16:35:49 +00:00
443c8b6a37 feat(activities): brand-kit logo + app name in the events page header
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>
2026-06-10 18:29:39 +02:00
5e3d77efec feat(activities): seat Map right after Home in the bottom nav
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>
2026-06-10 17:56:18 +02:00
60369ce1b1 feat(activities): drop RSVP buttons from EventDetailPage
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>
2026-06-10 17:54:40 +02:00
6d4a9f8c22 fix(activities): keep CreateEvent form actions inside narrow dialogs
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>
2026-06-10 17:44:23 +02:00
c6455b3235 feat(activities): move Past pill to end of the temporal strip
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>
2026-06-10 17:42:11 +02:00
7af0f364cb fix(sidebar): full-width identity values with corner-offset legend badge
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>
2026-06-10 17:41:58 +02:00
00f6a99f92 refactor(sidebar): show identity inline, move edit form into gear popup
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>
2026-06-10 17:26:25 +02:00
2c7597c25f fix(sidebar): clip horizontal overflow on profile sheets
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>
2026-06-10 17:12:01 +02:00
4f7a5dcd88 fix(avatar): pair bg-secondary with text-secondary-foreground
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>
2026-06-10 15:05:24 +00:00
54efff1cc6 fix(activities): relabel "Get invoice" → "Proceed" on PurchaseTicket
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.
2026-06-10 15:05:24 +00:00
cf9fc2db9d fix(activities): drop duplicate empty-state line on ActivityList
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).
2026-06-10 15:05:24 +00:00
b7fd8c99e5 feat(activities): fuzzy search on the Tickets roster
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.
2026-06-10 15:05:24 +00:00
31e48c8f1d feat(activities): manual ticket registration from the roster tab
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.
2026-06-10 15:05:24 +00:00
ba7c1e4cdc revert: move scan counts back above the tabs + fix tab centering
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.
2026-06-10 15:05:24 +00:00