Commit Graph

1677 Commits

Author SHA1 Message Date
lemon ffb1677fa2 Move campaign goal note into tooltip 2026-05-27 23:57:35 -07:00
filemon 31d76a8c4b Fix raw i18n key shown for campaign engagement counters
The Trans component's values={{ count: formatNumber(N) }} was spreading
after the numeric count prop, overriding it with a string.  i18next v26
requires a numeric count for plural resolution — a string causes it to
return the raw key path (e.g. 'campaignsDetail.repost') instead of the
resolved plural form.

Rename the interpolation variable from count to formattedCount so it no
longer collides with the count prop that Trans uses for plural selection.
Update all 16 locale files to use {{formattedCount}} in the repost, quote,
and like translation strings.

Regression-of: 86a084f3
2026-05-27 23:57:35 -07:00
lemon c69275e794 Hide empty campaign story section 2026-05-27 23:57:35 -07:00
lemon f79046694a Render campaign story in previews 2026-05-27 23:57:35 -07:00
lemon 1839c2e697 Remove campaign summary field 2026-05-27 23:57:35 -07:00
lemon cc9ff7676a Fallback to profile images for campaign covers 2026-05-27 22:18:27 -07:00
mkfain 6e3bc2d3d1 Seed campaign wallet picker to Agora wallet on first render
The Create Campaign page initialised `walletSource` to `'custom'` and
relied on a post-mount effect to flip it to `'mine'` once
`hdWalletAvailable` became true. With Radix Select's controlled value
that left users staring at the "Choose a wallet" placeholder on the
initial paint and forced them to open the dropdown to see that their
Agora wallet was even an option.

Seed both `walletSource` and `mineAccept` lazily from the
synchronously-available HD-wallet availability so the trigger already
reads as the user's Agora wallet on first render. The existing
availability-change effects still cover the (rare) case where the hook
resolves a tick later or where the wallet disappears mid-session.
2026-05-26 22:16:15 -05:00
mkfain 92d003eac0 Add 'Pay with Agora' button on campaign detail page
Opens the HD wallet's send dialog prefilled with the campaign's on-chain
address — same flow used at /wallet, so donors don't have to learn a
second send UI. Sits above 'Open external wallet' inside the donate
panel; the external button downgrades to the secondary variant when
both are present so only one orange CTA stacks.

When the campaign declares both an on-chain (bc1…) and a silent payment
(sp1…) endpoint, a swap link appears under the privacy disclaimer so
donors can flip to SP without leaving the modal. The toggle hides
itself if the donor manually edits the recipient field, so we never
trash their typed input.

Gated on useHdWalletAccess (nsec logins only) — extension and bunker
logins fall through to the external-wallet QR. The button is also
hidden for SP-only campaigns (no on-chain address to prefill) and for
the campaign owner themselves.
2026-05-26 21:59:36 -05:00
Alex Gleason f329915e88 Merge branch 'style/lemon-wallet' into 'main'
Style/lemon wallet

See merge request soapbox-pub/agora!35
2026-05-27 02:26:00 +00:00
Chad Curtis f173b975b7 Add Kosovo and Western Sahara, surface Tibet in country pickers
Add Kosovo (XK) and Western Sahara (EH) to the country list. Kosovo
has no Unicode emoji flag, so it follows the Tibet pattern with a
bundled SVG asset that CountryFlag swaps in.

Surface Tibet (CN-XZ) as a search-list entry so it can be picked from
country autocompletes and pickers. The on-wire identifier stays
iso3166:CN-XZ; only the picker pretends.

Route every remaining raw country.flag span through CountryFlag so
bundled SVGs render in autocomplete dropdowns, organizer selects, the
ComposeBox destination switcher, and the world stats dialog.
2026-05-26 19:53:42 -05:00
lemon 151552868b Polish wallet receive and zap fee UI 2026-05-26 12:48:28 -07:00
lemon 5fa96db1c3 Use brand typography for wallet balances 2026-05-26 12:32:29 -07:00
lemon b9c8904d6f Tighten wallet column spacing and underline scan link 2026-05-26 12:32:29 -07:00
lemon bf3e80d444 Surface SP scan as 'Check for new payments' link under balance 2026-05-26 12:32:29 -07:00
lemon c5c3f5d63c Rework wallet receive actions layout 2026-05-26 12:32:29 -07:00
lemon 130d5d09c6 Streamline wallet receive panel: refresh icon in copy row, caption below 2026-05-26 12:32:29 -07:00
lemon d9357e624f Inline wallet receive panel under balance with logo QR 2026-05-26 12:32:29 -07:00
Alex Gleason 58112adfc6 Remove recent donations preview from campaign donate sidebar 2026-05-25 20:31:57 -05:00
Alex Gleason 609f9f20b0 Loosen spacing around the campaign activity tabs
Two small fit-and-finish tweaks the previous restyle missed:

1. The tab strip's baseline border sat 1px above the rounded panel's top
   edge, so the panel's rounded corners visually overlapped the active
   tab's under-rule. TabsContent now carries an explicit mt-4 (16px) so
   the panel floats cleanly under the tab strip with breathing room
   above its rounded top corners.

2. Tab labels were spaced gap-1 (4px), too tight for labels that read
   as section headers. Bumped to gap-8 (32px) so 'Comments & donations'
   and 'Ledger' read as separate section titles.

CommentsSection's outer mt-4 is overridden with className=mt-0 at the
call site so the TabsContent gap controls the spacing — no more 32px
double-margin.
2026-05-25 20:22:49 -05:00
Alex Gleason e6dd3a04b8 Restyle campaign activity tabs to read as section headers
The shadcn default 'muted pill control' tab strip felt bolted onto the page
between the action bar and the comments panel. The tab labels also doubled
up with the 'Comments & donations' heading rendered inside the panel — two
section titles stacked.

Replaces the pill control with an underline-style tab strip that visually
serves as the section header for the panel below. The active tab label is
rendered at the same size and weight as the page's other h2 section
headings (text-lg font-semibold tracking-tight), inactive tabs are muted
siblings, and a 1px baseline border carries across the panel width while
the active tab paints a thicker primary under-rule that flows into the
content surface.

CommentsSection's title becomes optional and the campaign page omits it
now that the tab label owns the heading. The dead 'commentsAndDonations'
i18n key is removed from all 16 locales (tabComments carries the same
copy).
2026-05-25 20:04:53 -05:00
Alex Gleason d80e1b0a70 Tab Comments & donations and a new Ledger tab on campaign pages
Moves the existing comments + donations panel under a 'Comments & donations'
tab and introduces a sibling 'Ledger' tab that surfaces public on-chain
activity for the campaign's bc1 address — receives, sends, confirmation
status, block height, and the equivalent USD — sourced from the
already-configured Esplora endpoints with mempool.space deep links per row.

The Ledger tab is rendered only when the campaign declares an on-chain
endpoint ('bc1q…' / 'bc1p…'). Silent-payment-only campaigns intentionally
have no scannable address and degrade to the un-tabbed comments surface to
avoid showing a lone disabled tab.

A new fetchAddressTxs helper in src/lib/bitcoin.ts wraps Esplora's
/address/:addr/txs + /chain/:last_seen_txid pagination, and useAddressLedger
exposes it as an infinite query (50 confirmed tx page size, plus mempool on
the first page).
2026-05-25 15:32:27 -05:00
mkfain 85632711c6 Surface pending donations on campaign pages
The campaign detail already verifies each donation receipt on-chain
and knows whether the underlying Bitcoin tx is confirmed, but the
flag was being dropped before it reached the UI. Mirror the wallet's
treatment so visitors can see funds that are inbound but still in
the mempool.

Three changes:

* Extract PendingBadge (orange + spinning RefreshCw) and use it on
  both the wallet headline and the campaign donate column. The
  wallet's inline JSX is replaced with the shared component.
* Widen useCampaignDonations to expose pendingSats (the mempool
  delta from Esplora) and a confirmedByTxid lookup built from the
  already-verified receipts.
* DonateColumn now renders the pending sats under the raised total
  and DonorPreviewList swaps the relative timestamp for the badge
  on any row whose underlying tx is still unconfirmed.
2026-05-25 11:20:42 -05:00
mkfain 8574350778 Show LoginArea on the pledge, group, and event create gates
CreateActionPage, CreateCommunityPage, and CreateEventPage each
showed a dead-end 'Back to pledges' / 'Back to groups' / 'Back to
events' link as their only logged-out CTA. Mirror the campaign-gate
fix and render LoginArea instead so visitors can join or log in
without leaving the page, and drop the three now-unused i18n keys
from every locale.

Regression-of: 2870d7a6
2026-05-25 11:09:57 -05:00
mkfain 2870d7a641 Show LoginArea on the create-campaign logged-out gate
The gate previously offered only a 'Go home' link, leaving visitors
to find the login flow themselves. Render the existing LoginArea CTA
in its place so the 'Join' / 'Log in' modal opens directly from the
gate, and drop the now-unused campaignsCreate.goHome string from all
sixteen locales.
2026-05-25 11:04:56 -05:00
Chad Curtis 2424c96fb9 Match the pledge show-more control to the My Groups styling 2026-05-25 02:28:08 -05:00
Chad Curtis 908585538a Stack the discovery search toolbar below the heading on mobile
Wraps the title/toolbar row with flex-col on mobile so the search input,
filter dropdown, and country picker no longer cram next to the section
title at narrow widths. The toolbar also lets the search input grow to
fill the row on mobile (flex-1) while keeping its compact 16rem width
from the sm breakpoint up.

Applies to /groups, /pledges, and /campaigns/all — every place the
DiscoverySearchToolbar sits on a section heading row.
2026-05-25 02:22:15 -05:00
Chad Curtis a6ba446e42 Strip CommunityGrid's built-in horizontal padding
Every caller wraps the grid in a `max-w-7xl mx-auto px-4 sm:px-6`
page container that already supplies the page-edge gutter — matching
the Campaigns and Pledges discovery pages. CommunityGrid then added a
second `px-4 sm:px-6` of its own, which every caller defensively
overrode with `className="px-0"`. The override only neutralized the
base utility: tailwind-merge happily kept the responsive variant, so
the `sm:px-6` survived. The result was group cards inset 24px further
than campaign and pledge cards at every `sm` breakpoint and up.

Drop the internal padding from CommunityGrid and remove the redundant
`px-0` overrides on the eleven CommunitiesPage callers. The page
container is now the single source of truth for horizontal padding on
all three discovery surfaces.
2026-05-25 02:22:15 -05:00
Chad Curtis 4c043eae55 Hide the country picker on the groups page
A community is its own scope — narrowing groups by country isn't a
useful axis the way it is for campaigns and pledges (which are about
local action). Drop the country prop from CommunitiesPage's
DiscoverySearchToolbar call so the Globe button doesn't render at
all there. Campaigns and Pledges keep theirs.
2026-05-25 02:22:15 -05:00
Chad Curtis b8b7f638ee Bake the country picker into the discovery search toolbar
The pledges page already had a Globe-icon country popover sitting next
to a sort dropdown and the relay-driven search toolbar. The three
side-by-side filter affordances duplicated each other and pulled focus
away from the section heading. Collapse the cluster into the shared
DiscoverySearchToolbar:

- Drop the page-local Recent / Bounty / Deadline sort dropdown on
  pledges. The toolbar's Default / Top / New sort, backed by NIP-50,
  already covers the same ground and applies to every discovery
  surface; pledges fall through to a newest-first chronological view
  inside each lifecycle section.

- Add the country picker as a first-class prop on
  DiscoverySearchToolbar (`country` + `onCountryChange`) so all
  three discovery pages share the same Globe button without a generic
  trailing-slot escape hatch. Brand-orange icon in the neutral state,
  country flag emoji when selected.

- Extend useNip50Search with an optional `iTags` array, forwarded as
  a standard `#i` filter alongside the `search` field. A non-empty
  iTags array also activates the hook so picking a country with no
  typed query produces a country-scoped grid, narrowing kind 34550 /
  36639 by NIP-73 external identifier.

- Extend useAllCampaigns with a `countryCode` parameter that adds
  the same `#i` filter to the relay query. AllCampaignsPage tracks
  the country on the URL alongside the existing `sort` and `q`
  params so country-scoped views are linkable.

- Brand-orange the ListFilter icon on the toolbar to match the
  pledges-page aesthetic the user wanted to spread to the other
  discovery pages.
2026-05-25 02:22:15 -05:00
Chad Curtis a98384ab32 Remove the search toolbar from the home page
The DiscoverySearchToolbar (search input + sort + show-hidden) was
duplicating affordances the dedicated /campaigns/all page already
provides. The home page is now a curated landing: hero, featured row,
community grid, and moderator/creator sections.
2026-05-25 02:20:16 -05:00
Chad Curtis b2f8311d5e Use a consistent section heading on /campaigns/all and /groups
The /pledges page introduces large bold section headers above its grid
("text-2xl sm:text-3xl font-bold tracking-tight" + muted tagline +
right-side controls). /campaigns/all had no section heading at all, and
/groups was using the smaller SectionHeader pattern ("text-base
font-semibold") for its 'My groups' and 'Featured groups' shelves —
even though its own search-mode branch already used the large-bold
pattern. Three pages, three different section-header treatments.

Aligns both pages with the /pledges style:

- /campaigns/all: add a heading above the grid that switches between
  Search / Top / New labels based on toolbar state, with a constant
  tagline ("Browse every cause on the network").
- /groups: replace the SectionHeader usages on the My groups / Featured
  groups shelves with the same large-bold heading + tagline pattern;
  drop the now-unused SectionHeader import. Inner shelves
  (CommunityGrid, EmptyShelf, show-more) get their internal
  px-4 sm:px-6 stripped since the outer container now provides it.
- Moderator review sections use the same px-0 grid + collapsed trigger
  padding inside the new wrapper.

/ (Index → Feed) keeps tabs as its visual navigation, so it doesn't
need a heading — that exception is the rule that proves the pattern.
2026-05-25 02:20:16 -05:00
Chad Curtis b4696338aa Merge the pledge card's share/delete kebab and moderator kebab into one
ActionCard rendered two kebabs side-by-side: ActionShareMenu (Copy link +
owner Delete) and the shared ModerationOverlay's mod-only kebab. The two
buttons were also styled differently — the share menu had no banner
backdrop, so on photo-heavy cards it floated against the image while
the moderator kebab next to it sat in a translucent pill.

Extract ModerationMenuItems from ModerationMenu — the dropdown rows
themselves (label + items) without the trigger/content wrapper. Standalone
ModerationMenu and ModerationOverlay still work the same; cards that need
to embed moderator actions inside an existing dropdown now compose them
directly:

  <DropdownMenuContent>
    <DropdownMenuItem onClick={copy}>Copy link</DropdownMenuItem>
    {isOwner && <DropdownMenuItem onClick={del}>Delete</DropdownMenuItem>}
    {isMod && <DropdownMenuSeparator />}
    <ModerationMenuItems coord={…} surface="pledge" axes={…} entityTitle={…} />
  </DropdownMenuContent>

ModerationOverlay grows a showMenu prop (default true) so a card can still
get the mod-gated 'Hidden' badge in its banner corner without the
redundant second kebab when the moderation items have been embedded in
another dropdown.

Then on ActionCard:
  - ActionShareMenu absorbs ModerationMenuItems under a separator, so the
    card carries exactly one kebab.
  - Its trigger now uses the same 'h-8 w-8 bg-background/80 backdrop-blur
    text-muted-foreground hover:text-foreground' classes as the campaign
    and group card kebabs so the pill is consistent across surfaces.
  - The mod-only 'Hidden' badge still renders, via ModerationOverlay with
    showMenu={false}.
  - Adds a generic pledges.card.actionsAriaLabel ('Pledge actions')
    aria-label since the kebab is no longer just for sharing — translated
    into all 15 non-English locales.
2026-05-25 02:20:16 -05:00
Chad Curtis 008f6604a8 Unify moderation menus and review queues across campaigns, pledges, and groups
Campaigns, pledges, and groups each had their own moderation kebab,
'Hidden' badge, and collapsible review sections. The three implementations
had drifted: the campaign card inlined the badge + menu while the pledge
and group cards used per-surface overlay wrappers, the pledge cards
imported the kebab without its 'Hidden' badge (so hidden pledges showed
the kebab but no visible status), pledges had no page-level Pending /
Hidden moderator queues at all, and each page reimplemented the
collapsible review section as a local component.

Consolidate everything into src/components/moderation/:

  HiddenBadge — the shared destructive chip with EyeOff + 'Hidden'.
  ModerationMenu — the shared kebab with an `axes` config prop, so
    campaigns can request ['approval', 'hide', 'featured'] and pledges /
    groups can request ['hide', 'featured']. Per-surface inner components
    each mount exactly one moderation hook so a pledge card never
    subscribes to the campaign label query (and vice versa).
  ModerationOverlay — the absolutely-positioned wrapper that bundles
    HiddenBadge + ModerationMenu in a card corner. Same gating contract:
    returns null for non-moderators.
  ModeratorCollapsibleSection — the shared 'Pending / Hidden' collapsible
    used by CampaignsPage, CommunitiesPage, and now ActionsPage.

Migrate the three card surfaces and three index pages onto these shared
pieces, fixing two concrete bugs along the way:

  ActionCard now renders a 'Hidden' badge on hidden pledges. Previously
    it imported PledgeModerationMenu directly (kebab only) instead of
    PledgeModerationOverlay (badge + kebab), so moderators saw the
    kebab but had no inline cue that a card was hidden.
  ActionsPage now has page-level Pending and Hidden moderator review
    queues at the bottom, mirroring the queues already present on
    CampaignsPage and CommunitiesPage. Pledges share the two-axis model
    with groups, so 'Pending' here means 'no curation decision yet' —
    pledges not yet featured or hidden.

useActions grows an optional `enabled` flag so the page can skip the
moderator-only review-queue query entirely for non-moderators rather
than firing it with limit:0.

Locale work: add the new moderation namespace and the pledges.list
needsReview / hidden / *Desc / *Empty keys to en.json and all 15
non-English locales (ar, es, fa, fr, hi, id, km, ps, pt, ru, sn, sw,
tr, zh, zh-Hant).
2026-05-25 02:18:08 -05:00
Chad Curtis 52f6ff9f9f Add a shared on-page NIP-50 search toolbar to every discovery page
Build a single search affordance shared by Campaigns home, All Campaigns,
Communities, and Pledges so the four discovery surfaces stop having
four different toolbars (or none at all).

New shared pieces
-----------------

DebouncedSearchInput renders a controlled shadcn Input with the lucide
Search icon on the left and a clear (X) button on the right that
appears once the user has typed something. Rounded-square corners
(rounded-lg, matching the global SearchPage). Stateless — the caller
pairs it with useDebounce and feeds the debounced value into a query
hook so the same component works for URL-synced searches, in-memory
searches, or anywhere else.

DiscoverySearchToolbar wraps DebouncedSearchInput with a compact filter
button on the right that opens a popover containing:
  - Top / New sort pills wired to NIP-50 'sort:top' (Top) and no sort
    token (New).
  - An optional Show-hidden switch — only rendered when the caller
    passes a showHidden prop, so surfaces without a hidden axis
    (Pledges) just hide the row.

The toolbar matches the visual idiom of the global SearchPage filter
button (40-ish-px rounded-lg square, primary-tinted border when any
modifier is active). Inline against the page background — no card
framing — so the hero anchors visual hierarchy. Fully controlled:
parent owns search / sort / show-hidden state, the toolbar is purely
presentational.

useNip50Search<T> issues a kind-scoped NIP-50 search against
nostr.group(DITTO_RELAYS). Routing through the Ditto group rather
than the default pool keeps results predictable: most non-NIP-50
relays either ignore the 'search' field (returning everything matching
the other filters) or return nothing — both modes drown the result
set in noise. Hook is generic over an event parser so each surface
parses its own kind (parseCampaign / parseCommunityEvent / parseAction).

Sort modes
----------

  - new (default): no sort token in the search payload; relay returns
    chronological results.
  - top: appends ' sort:top' to the search payload; relay scores by
    engagement. Also works with an empty keyword (sends just
    'sort:top') so 'Top' alone is a valid 'most-engaged' feed.

Empty keyword + New sort is the hook's only 'inactive' state, in
which case the page falls through to its existing curated layout
(featured rows, my-groups shelf, active-pledges sections, etc.).

Per-page wiring
---------------

Campaigns home (/campaigns) — toolbar with show-hidden; results
filtered against useCampaignModeration.hiddenCoords. Empty + New
falls through to the existing Featured / Community / moderator
sections.

Communities (/communities) — toolbar with show-hidden; results
filtered against useOrganizationModeration.hiddenCoords. The hook
is lifted to the page level so search results can drop hidden
groups (or include them via the switch). The ModeratorReviewSections
subtree still calls its own copy — query results are cached so the
second call is free.

Pledges (/pledges) — toolbar without show-hidden. Pledges lack a
moderator-driven hidden axis today, so the switch wouldn't have
anything to toggle. Adding it is tracked as a follow-up.

All Campaigns (/campaigns/all) — gets a new HeroBanner +
HeroAtmosphere hero matching the Pledges / Communities shape
(kicker, two-line heading, body, glass stat pill, glass CTA, warm
hope palette), so the route looks like a real discovery surface
instead of a bare grid under a plain H1. The page also migrates
from its bespoke inline toolbar to the shared one, with a small
adapter mapping the toolbar's 'top'/'new' vocabulary to the legacy
'top'/'none' URL param so existing share links keep working.

i18n
----

New common.* keys (sortAriaLabel, sortTop, sortNew, showHidden,
filtersAriaLabel) plus campaigns.all.* hero keys (heroKicker,
heroHeading, heroHeadingLine2, heroBody, campaignsCount_one,
campaignsCount_other). Translated across all sixteen locales.
2026-05-25 02:13:18 -05:00
Chad Curtis 85f3e5fd79 Widen the Communities body to match Campaigns and Pledges
The Communities page rendered its content column at max-w-5xl while
the Campaigns and Pledges bodies use max-w-7xl, making Communities
look squeezed by comparison on wide displays. Bump Communities to
max-w-7xl so the three discovery pages share a content width.
2026-05-25 02:11:04 -05:00
Chad Curtis 42edaa312e Wrap detail-page comments in a shared CommentsSection panel
The campaign detail page presented comments inside a muted, primary-
tinted card with rounded corners and retinted per-note dividers, while
the community and pledge detail pages rendered the composer and
threaded reply list bare on the page background. The result was that
the same conceptual area read as three different surfaces.

Extract the campaign-detail treatment into CommentsSection: a thin
wrapper that owns the heading + optional count chip and the muted
panel. Each detail page passes in its own composer, reply list,
skeletons, and empty state, so per-page behavior (pin headers,
campaigner badges, pledge-specific placeholder) stays where it
belongs.

Apply CommentsSection to Campaigns, Communities, and Pledges. Also
swap the campaign-detail hero's bespoke avatar/Link block for
AuthorByline variant='hero', removing the now-dead Avatar /
useProfileUrl / useAuthor / genUserName imports and the
creatorName / creatorPicture / creatorProfileUrl plumbing through
CampaignHeroProps.
2026-05-25 02:11:04 -05:00
Chad Curtis 921d642f86 Switch Pledges to the unified create-pledge icon and AuthorByline
Two changes to ActionsPage that landed together:

- Replace the bare Plus icon on the 'Create pledge' CTA (both the
  hero button and the in-feed Section CTA) with PlusCircle so the
  Pledges page matches the Communities and Campaigns CTAs.

- Replace the bespoke 'by {name}' footer on ActionCard with the new
  shared AuthorByline component, dropping the now-dead useAuthor /
  getDisplayName / Trans imports. The byline carries the same avatar
  + i18n'd 'by Name' label + profile-navigation behavior as every
  other discovery-page card.
2026-05-25 02:11:04 -05:00
Chad Curtis 11e143ce0e Make hero stat pills legible on light-mode dark scrims
The Pledges and Communities heroes layer their stat pills on top of
black scrims and protest photography. The pills used bg-background/55
plus text-muted-foreground / text-primary, which in light mode rendered
as low-contrast gray text on a dark photo — the "4 pledges open right
now" counter was effectively unreadable.

Switch both pills to a translucent dark surface (bg-black/30) with
explicit white text, white/85 secondary text, theme-tinted icons
(amber-200 for Pledges, cyan-200 for Communities), and drop-shadows
matching the hero headline above. The pill now reads the same in light
and dark mode.
2026-05-25 02:05:55 -05:00
lemon 556a0f96de Drop badges, letters, and highlights from notifications 2026-05-24 22:45:38 -07:00
lemon 50de894392 Drop unreachable CommunityContent branch from PostDetailPage 2026-05-24 22:32:40 -07:00
lemon 771338014f Consolidate pledge cards into shared component 2026-05-24 22:29:29 -07:00
lemon 9708ec136c Rename search accounts tab to users 2026-05-24 22:03:03 -07:00
lemon ce6163662a Move note card overflow actions to header 2026-05-24 21:58:47 -07:00
Chad Curtis ceb1995b6a Lock campaign card body height and lift meta chips onto the banner
Cards in a multi-column grid now have a deterministic, predictable
silhouette. The body region is a fixed stack — title (1 line, truncates)
+ summary (1 line, truncates with a non-breaking-space placeholder when
absent) + progress (invisible-bar placeholder absorbs no-goal and
silent-payment cases) + creator footer — so no card has dead space or
ragged bottoms regardless of which optional fields are populated.

Country, deadline, hidden badge, and moderation menu all moved onto the
banner image as glass chips overlaid on a bottom gradient (chips
appear only when there's data to show, so a chip-free banner stays
clean). The donor-count chip became an inline aside on the creator
line. The variable meta row is gone entirely from the body.

CampaignPrivateNotice was rebuilt to mirror CampaignProgress's
vertical footprint (invisible bar + one text row) so silent-payment
cards line up with public-progress cards beside them.

Progress bars on cards and the detail-page DonateColumn use
`bg-foreground/15` for the track instead of the primitive's default
`bg-secondary`, which in dark mode shares its color with the card
surface and made the empty portion of the bar invisible. The new
foreground-tinted track reads cleanly on the card in both themes.
2026-05-24 20:48:01 -05:00
Chad Curtis 34c9ab7492 Frame composer, sidebar, and feed/comments as panels with brand-edged surfaces
- Wrap composer + comments / composer + feed in a rounded muted panel
  with continuous border-primary rails.
- Composer and campaign sidebar use a near-white surface with a
  brand-orange border; dark mode swaps to a near-black warm surface.
- Per-article borders and backgrounds retinted via scoped selectors
  so NoteCard stays untouched globally.
- Add hideCommentContext, authorBadge to NoteCard and
  renderAuthorBadge, leafCardClassName to ThreadedReplyList; move the
  Campaigner badge into the author row.
- Light-mode tokens: --card to 0 0% 98%, --muted to 30 12% 94% so
  cards and muted regions read as real surface steps.
- Drop the unused campaignsDetail.commentCount locale key everywhere.
2026-05-24 20:29:57 -05:00
mkfain b851f42e6c language settings: keep script-tagged locales checked
The selected-language indicator collapsed every BCP-47 tag down to its
base subtag before comparing against the supported list, so picking
'zh-Hant' set i18n.language to 'zh-Hant' (and the UI strings switched
to Traditional Chinese as expected) but the checkmark moved to the
'zh' (Simplified) row because the comparison only saw 'zh' on both
sides.

Match in three passes instead:

1. Exact case-insensitive match against SUPPORTED_LANGUAGES — keeps
   'zh-Hant' checked when active.
2. Alias map for zh-TW / zh-HK (registered as resource aliases in
   i18n.ts) so device locales from Taiwan and Hong Kong land on the
   zh-Hant row in the switcher.
3. Base-subtag fallback for en-US -> en, pt-BR -> pt, etc., preserved
   from the original behavior.
2026-05-24 19:05:47 -05:00
Alex Gleason bc3271ec65 wallet: move 'Scan for payments' above 'Back up wallet' in the overflow menu 2026-05-24 18:41:20 -05:00
Alex Gleason a83df0f56f wallet: combine SP + on-chain into a single BIP-21 QR in the receive dialog
Mirrors CampaignWalletDonatePanel's payload format
(`bitcoin:<bc1>?sp=<sp1>`) so BIP-352-aware wallets pick the
`sp=` parameter and legacy wallets fall back to the on-chain
address. The dialog now shows one QR and one copyable row with the
full URI, the tabs and per-tab intro copy are gone, and the
"Scan for payments" action lives in the wallet's overflow menu
instead of inside the receive dialog.
2026-05-24 18:19:15 -05:00
Alex Gleason 39f288d819 wallet: turn 'Back up wallet' into an in-page dialog
The Back up wallet menu item on /wallet now opens the existing
WalletBackupMnemonicDialog inline instead of navigating to a dedicated
page. The dialog already existed in src/components/WalletBackupMnemonic.tsx
but wasn't wired up anywhere — this finally uses it.

- WalletPage: import WalletBackupMnemonicDialog, hold an open/closed state,
  flip the menu item from <Link to="/wallet/backup"> to a regular
  DropdownMenuItem that calls setBackupOpen(true) onSelect. Render the
  dialog alongside the existing Send / Receive / SP-scan dialogs. The
  dialog's internal gating still hides for extension/bunker signers, but
  the menu only renders in the 'available' branch of WalletPage so that
  fallback isn't user-visible in practice.
- AppRouter: drop the lazy import and route for WalletBackupPage, replace
  /wallet/backup with a redirect to /wallet so old bookmarks land somewhere
  sensible. Point /wallet/settings/backup at /wallet for the same reason.
- Delete WalletBackupPage.tsx \u2014 no longer reachable.
- Locales: remove the now-unused walletBackupPage block (six keys:
  seoTitle, seoDescription, title, subtitle, loggedOut, unsupported) from
  en + 14 other locales. The dialog reuses walletBackup.* and
  walletSettings.backup.label, which are unchanged.
2026-05-24 17:47:39 -05:00
Alex Gleason 96cf2bcbe0 wallet: collapse settings hub into an overflow menu on /wallet
The cog in the top-right of /wallet now opens a 3-dots (MoreVertical)
dropdown with two items — Back up wallet and Legacy wallet recovery —
each linking straight to the existing /wallet/backup and /wallet/legacy
pages. /wallet/settings (the intermediate Apple-style settings hub) is
gone; the route now redirects to /wallet for any old bookmarks.

- WalletPage: swap the Settings <Link> for a DropdownMenu with two
  Link-backed items. Reuse the existing walletSettings.{backup,legacy}.label
  strings as menu labels.
- AppRouter: drop the lazy import + route for BitcoinWalletSettingsPage,
  replace /wallet/settings with a redirect to /wallet, keep the existing
  /wallet/settings/{backup,legacy} redirects.
- WalletBackupPage / LegacyWalletRecoveryPage: backTo now points at
  /wallet directly instead of the removed hub.
- Delete BitcoinWalletSettingsPage.tsx — no longer reachable.
- Locales: rename wallet.openSettings -> wallet.openMenu and prune the
  walletSettings hub strings (seoTitle, seoDescription, title, subtitle,
  per-row descriptions) across en + 14 other locales, keeping only the
  two label values that the menu items now read.
2026-05-24 17:41:42 -05:00