Commit Graph

4447 Commits

Author SHA1 Message Date
Alex Gleason 4db37f9217 Merge branch 'main' of gitlab.com:soapbox-pub/agora 2026-06-11 17:06:49 -05:00
Alex Gleason 10a1c53e6a fix: don't advance SP scan cursor over unscanned gaps
A forward-jumping scan range (e.g. "Scan recent" on a wallet whose
cursor was far behind) recorded the skipped gap as scanned: runScan set
scanHeight = max(scanHeight, highestContiguousScanned) where contiguity
was measured only from the range's own fromHeight. The auto-scanner then
resumed from the new cursor, so payments received in the gap were
silently never discovered.

The cursor now only advances when the scan range is contiguous with the
effective persisted cursor (relay scanHeight or local checkpoint).
Non-contiguous scans still merge any UTXOs they find — only the cursor
is held back, so a later auto-scan pass walks the gap. Never-scanned
wallets (cursor 0) keep the documented bootstrap jump to a recent
window.
2026-06-11 16:37:52 -05:00
lemon b20e49bf20 Localize campaign list category headers 2026-06-10 22:45:36 -07:00
Alex Gleason c9d77d06a1 Rename "activist" to "recipient" in donation copy
The people receiving donations are not necessarily activists — they are
often simply people in need (e.g. in Venezuela and Gaza). Rename the
user-facing term from "activist" to "recipient" across all copy and the
underlying code.

- Rename ActivistGuidePage -> RecipientGuidePage and the /about/activists
  route -> /about/recipients (legacy URL kept as a redirect).
- Rename guide structure/helpers, the paymentComparison audience literal,
  and the related i18n key namespaces (activistGuide -> recipientGuide,
  about.guides.activist -> .recipient, guides.activist -> guides.recipient,
  activistRows/activistHeader -> recipientRows/recipientHeader,
  ctaActivist -> ctaRecipient).
- Translate the term in all 16 locales, plus fix stale "activists" hero
  taglines left over from 0233a75d in id/hi/sw/tr/zh-Hant.
2026-06-10 15:43:08 -05:00
Alex Gleason ab1a4ba0e8 Show scan progress bar on private wallet tab
The Private wallet tab only showed a spinner plus 'Scanning… block X / Y'
text while the silent-payment scanner ran, which gave no sense of how far
along the scan was. Render a Progress bar (matching the Scan options
dialog) above the block-count text so completeness is visible at a glance.
2026-06-10 15:17:04 -05:00
Alex Gleason 101926e961 fix: restore AudioPlayerProvider so audio/music/podcast pages don't crash
Profile and detail pages that render music tracks, podcast episodes, or
audio kinds call useAudioPlayer(), but the AudioPlayerProvider and its
companion floating-bar / navigation-guard components were deleted as
"orphaned" in 256560cf — they were never wired into App.tsx, so the hook
always threw "useAudioPlayer must be used within AudioPlayerProvider".

Restore the provider, MinimizedAudioBar, and AudioNavigationGuard, re-export
AudioPlayerContext from the context def, wrap AppRouter in the provider, and
render the two router-dependent components inside BrowserRouter.

Regression-of: 256560cf
2026-06-10 14:57:38 -05:00
Alex Gleason 9351d3e243 Speed up silent-payment scanning
Three changes target the scan pipeline's two bottlenecks (HTTP latency
and main-thread ECDH):

- Cache per-block tweak + UTXO data in IndexedDB (blockCache.ts) and
  route fetches through it. Mined blocks below the indexer tip are
  immutable, so a repeat or overlapping scan now costs zero round-trips
  for blocks already seen. Keyed by (indexerUrl, height); degrades
  gracefully when IndexedDB is unavailable.

- Move the secp256k1 ECDH + per-output Pk derivation into a dedicated
  Web Worker (scan.worker.ts + scanWorkerClient.ts). The scan loop no
  longer has to setTimeout(0) every 64 entries to keep the UI
  responsive, and fetching now overlaps with ECDH. The client falls back
  to a main-thread scan if the worker can't be constructed or errors on
  a block. bscan stays on-device — a same-origin Worker is not the
  remote scan-helper pattern BIP-352 contemplates.

- Make fetch concurrency tunable via AppConfig.bip352ScanConcurrency
  (clamped to [1, 32], default 8) so a fast self-hosted indexer can run
  hotter than the polite public-host default.
2026-06-10 14:29:02 -05:00
Alex Gleason 0233a75d5c Update hero tagline from 'activists' to 'the world' 2026-06-10 10:43:43 -05:00
Alex Gleason 450989f6ca feat: add moderation menu to campaign detail page hero
Moderators had no way to hide/verify/add-to-list a campaign from its
detail page — the hero toolbar only showed creator (edit/delete) controls.

Follow the same pattern as ActionDetailPage (pledge detail): place a
ModerationMenu in both the mobile and desktop toolbars of CampaignHero.
It returns null for non-moderators so no conditional wrapping is needed.

- Add coord and entityTitle props to CampaignHeroProps; thread them from
  CampaignDetailContent (campaign.aTag, campaign.title)
- Mobile: ModerationMenu sits in the right-side controls group, before
  the creator edit/delete buttons
- Desktop: ModerationMenu appears at the far right of the top overlay bar
  (after creator controls when present), styled with the same
  bg-black/30 backdrop-blur chip as ActionDetailPage
2026-06-10 09:22:46 -05:00
Alex Gleason cf0caa8c85 fix: merge moderation actions into list-member kebab to prevent overlay collision
On /campaigns/lists/:slug, ListMemberCard rendered its own reorder/remove
kebab at top-3 right-3 z-20, directly on top of CampaignCard's ModerationOverlay
(also top-3 right-3 z-10). The ListMemberCard kebab always won stacking order,
blocking moderators from accessing moderation actions on list-page cards.

- Add showModerationMenu prop to CampaignCard (default true); when false,
  ModerationOverlay renders only the Hidden badge (no menu trigger), keeping
  hide-state visibility without adding a second kebab in the same corner.
- ListMemberCard now passes showModerationMenu={false} to CampaignCard and
  embeds ModerationMenuItems directly into its own dropdown (below a separator
  after the reorder/remove items), giving moderators a single combined menu
  with both list-management and moderation actions.
- Dialog state (CampaignListMembershipDialog, VerificationDialog) is hoisted
  to ListMemberCard so the dropdown-unmount lifecycle doesn't tear them down.
2026-06-09 19:58:04 -05:00
Alex Gleason 2242692794 Checkpoint silent-payment scan progress locally every 5s
Scan progress was only persisted to the relay on a match or at end-of-scan,
so refreshing mid-scan re-scanned every block since the last completed scan.
Add a free, synchronous localStorage checkpoint of the advancing scanHeight
(throttled to 5s, plus a final write on finish) scoped per pubkey+indexer.
The auto-scanner now resumes from max(relay scanHeight, local checkpoint),
bounding mid-scan-refresh re-scan to ~5s of blocks without adding any
encrypt/sign/broadcast traffic. Relay publishing stays gated to matches and
end-of-scan for cross-device sync; manual scans still honor the exact
fromHeight the user picks.
2026-06-08 18:15:33 -05:00
Alex Gleason 8473a4990f Scan for silent payments automatically in the background
Lift the silent-payment orchestrator into an HdWalletSpProvider mounted at
the app root so a single shared scan instance keeps running across page
navigation. Add a background auto-scanner that resumes from the last
persisted scanHeight and keeps up with the chain tip without any user
action, bounding the first pass on a never-scanned wallet to ~7 days of
blocks. Surface live scan state on the Private wallet tab and add an
auto-scan toggle to the scan dialog.
2026-06-08 16:11:54 -05:00
Alex Gleason e5277f004e Show verification badge on campaign detail page
Add an inline variant of CampaignVerificationBadge that reads on a light
page surface (bordered chip with a 'Verified' label) and render it next to
the author byline in the campaign detail heading. The overlay variant (dark
pill) is unchanged for cards over banners.

New i18n key campaignVerification.verifiedLabel across all locales.
2026-06-06 19:24:27 -05:00
Alex Gleason 93bcf04ae9 Confirm campaign verification through a dialog
Clicking 'Verify this campaign' now opens a VerificationDialog instead of
publishing immediately. The dialog previews the moderator's own avatar
with the verification checkmark (as it appears on verified campaign cards)
and shows the attestation the moderator is making before confirming.

- New VerificationDialog component (avatar + checkmark preview + attestation
  text + Cancel / Verify actions).
- ModerationMenu owns the dialog state and the verify mutation, hoisted out
  of the dropdown content (which unmounts on select) like the existing
  add-to-list dialog; passes onRequestVerify down to CampaignVerifyItem.
- Removing a verification stays inline (no confirmation).
- New i18n keys dialogTitle + attestation across all locales.
2026-06-06 19:18:39 -05:00
Alex Gleason f95ab1b422 Treat campaign verification as a moderator action
Verification is gated by the existing campaign moderator pack
(useCampaignModerators / CAMPAIGN_MODERATORS), not a separate allowlist.

- Remove the config.labelers field (AppContext interface, Zod schema,
  App.tsx and TestApp defaults) and delete useCampaignLabelers.
- useCampaignVerifications now reads/writes agora.verified labels gated
  on the moderator pack (isModerator), same authors filter as the other
  label streams.
- Move the verify / remove-verification row INSIDE the moderation kebab's
  'Moderator actions' section (leadingExtra slot of ModerationItemsShell),
  no longer a top-level item above the section label.
- Revert the isMod || isLabeler widening in ModerationMenu/Overlay back to
  plain isMod.
- Remove the trailing 'Verified' checkmark text from 'Remove my
  verification'.
- Rename labeler->moderator in agoraVerification, the badge component, and
  all locale strings; drop now-unused notVerified / verifiedState keys.
- Update NIP.md to document verification as a moderator action.
2026-06-06 19:11:48 -05:00
Alex Gleason a4be9d9fbb Put verify row at top of campaign moderation menu
The verify / remove-verification row now renders above the moderator
rows. The moderator section's own 'Moderator actions' label provides the
visual separator, so no extra divider is needed between them.
2026-06-06 18:56:20 -05:00
Alex Gleason 1557e2fff9 Move campaign verify action into moderation kebab menu
The verify / remove-verification controls now live as rows in the
campaign 'Moderator actions' 3-dots menu instead of inside the
verification badge popover. The badge over the card is now display-only
(stacked verifier avatars + a popover listing them, linking to profiles).

Because labelers are a distinct allowlist from the moderator pack, the
campaign moderation menu/overlay now mounts for a user who is a mod OR a
labeler; the verify row itself is gated on labeler membership, while the
hide/add-to-list rows stay moderator-only.
2026-06-06 18:50:00 -05:00
Alex Gleason ba4a7f4e35 Add Me and MK as default campaign labelers 2026-06-06 18:46:02 -05:00
Alex Gleason b8c1bc7409 Add agora.verified campaign verification labels
Trusted labelers (AppConfig.labelers) can vouch for campaigns via NIP-32
kind 1985 labels in the new agora.verified namespace. Verifier avatars
render as a stacked badge on campaign cards; hovering/clicking opens a
popover listing verifiers (linking to their profiles). Logged-in labelers
get verify / remove-verification controls — verify publishes a label,
unverify issues a kind 5 deletion of their own label.

The read query filters by authors: labelers so verifications from outside
the allowlist are never honored. Kind 1985 label reqs are routed to the
search relays (relay.ditto.pub, relay.dreamith.to) in NostrProvider.
2026-06-06 18:06:36 -05:00
Alex Gleason 15718a575f Add Bitcoin backend config to Advanced settings
Expose the Esplora API endpoints, Blockbook URL, and BIP-352 silent
payment indexer (already wired through AppConfig) in a new Bitcoin
section on the Advanced settings page. Each field validates input,
normalizes trailing slashes, and offers a reset-to-default button;
clearing the indexer URL disables silent-payment scanning.
2026-06-05 18:34:25 -05:00
Alex Gleason 7775c0477f Merge branch 'main' of gitlab.com:soapbox-pub/agora 2026-06-05 17:15:50 -05:00
mkfain 07f77b8a99 Show all featured campaigns on home page instead of capping at 6
The home hero row sliced the Featured Campaigns list to the first 6
members (FEATURED_HERO_CAP). Drop the cap so the row renders every
campaign the curator has added — the existing hero layout (two
column-spanning cards + 4-up tail) already scales to any count. The
skeleton placeholder count stays bounded by FEATURED_SKELETON_CAP for
the loading state only.
2026-06-05 17:04:55 -05:00
mkfain 872e8428d2 Source home hero row from Featured Campaigns list
Switch the home-page hero row from the World Liberty Congress curated
list (d=world-liberty-congress) to the Featured Campaigns list
(d=featured-campaigns), both curator-published kind-30003 NIP-51
bookmark sets.

Replace the WLC org-branded heading (avatar, name, npub profile link
pulled via useAuthor) with a plain 'Featured Campaigns' heading that
links to /campaigns/lists/featured-campaigns. Drop the now-unused
Avatar / useAuthor / genUserName imports and the WLC pubkey/npub
constants.

Rename the WLC_* constants to FEATURED_* and replace the wlcDesc
locale key with featuredTitle + featuredDesc across all 16 locales.
2026-06-05 17:04:55 -05:00
Alex Gleason a8b2fe5ddf Stop /campaigns from hammering Esplora until every backend rate-limits
The campaigns grid renders up to 200 CampaignCards at once with no
virtualization, and each card eagerly ran useCampaignDonations: an
Esplora /address lookup per card (polling every 30s via refetchInterval)
plus a /tx verification call per kind-8333 donation receipt. On first
paint that's hundreds of /address calls and potentially thousands of
/tx calls in one burst, then 200 more /address calls every 30s forever.
Once mempool.space starts returning 429, the failover client retries
each call across every configured endpoint, cascading the rate-limit to
all of them.

Gate the donation lookup behind an IntersectionObserver so only on-screen
cards talk to Esplora, and drop the per-card 30s polling. The detail page
(a single instance, not a grid) opts back into the 30s live refresh via a
new refetchInterval option.

- Add useInView hook (once-only, rootMargin pre-arm, IO-less fallback).
- useCampaignDonations gains enabled + refetchInterval options; default
  no polling, longer staleTime; treat 'not enabled yet' as loading so
  off-screen cards show a skeleton instead of flashing '0 raised'.
- Gate CampaignCard and CampaignInlinePreview on visibility.
- CampaignDetailPage opts into refetchInterval: 30_000.
2026-06-05 16:54:22 -05:00
Alex Gleason a2dd16fc94 Merge branch 'main' of gitlab.com:soapbox-pub/agora 2026-06-05 16:46:39 -05:00
Alex Gleason 0c455c6d6f Block public-wallet sends to your own silent payment address
Sending from the public (BIP-86) wallet to your own silent payment
address co-mingles the resulting silent-payment UTXOs with coins
already exposed on the public ledger, linking the private wallet back
to a known on-chain identity and defeating its purpose.

Unlike the private-wallet reuse guard (which offers a 'send anyway'
acknowledgement), this is disallowed outright: the Send button stays
disabled and a blocking notice explains why.
2026-06-05 16:33:00 -05:00
mkfain f0af799647 Document Public/Private wallet split in About and guides
The previous commit split the wallet into strictly-isolated Public
(BIP-86) and Private (silent-payment) balances that can never be spent
together. Update the user-facing docs to match:

- /about: the Silent Payments card now notes private funds are kept in
  a separate balance, never mixed with public ones.
- Activist Guide: the receiving section explains the Public/Private tabs
  and why mixing would re-link silent-payment donations; a new callout
  covers cashing out from the Private tab to an sp1… address and the
  'Send anyway' address-reuse guard; the cash-out intro now points at
  the Private tab.
- Wallet FAQ: notes the two strictly-separated tabs.

All copy mirrored across the 15 non-English locales.
2026-06-05 15:47:53 -05:00
Alex Gleason 4cd725daf1 Merge branch 'main' of gitlab.com:soapbox-pub/agora 2026-06-05 15:19:25 -05:00
Alex Gleason e7439611b1 Separate wallet into isolated Public and Private wallets
Silent-payment (private) UTXOs were combined with BIP-86 (public) UTXOs
during coin selection, and silent-payment change was sent to a public
BIP-86 change address. Both linked private funds back to the public
wallet on-chain, defeating the unlinkability silent payments exist to
provide.

Enforce strict UTXO isolation at the PSBT-build layer: a spend now
carries a `walletScope` and the coin selector only ever sees the matching
input kind (public → BIP-86, private → SP). Private-wallet change is
routed to a fresh BIP-352 output back to the wallet's own sp1 address, so
it re-enters the private wallet instead of leaking to a public change
address. The two UTXO sets can no longer be spent together.

Surface the separation as Public / Private tabs on /wallet, each with its
own balance, receive QR (bc1 vs sp1), send flow, and transaction history.
Private sends to a bare on-chain address are gated by an address-reuse
guard: when the recipient chip is selected, a Blockbook getAccountInfo
probe checks for prior on-chain history, and the address is also checked
against the user's own public wallet. Either case requires an explicit
"Send anyway" acknowledgement; the Send button is disabled while the
probe is in flight. Sending a silent payment to an sp1 address has no
such warning.
2026-06-05 15:18:51 -05:00
mkfain 168ca2d067 Drop "Plain English, no walls of text." from activist guide subtitle 2026-06-05 13:57:59 -05:00
Chad Curtis 54c711b3be Point NIP-89 client tag to Agora handler naddr 2026-06-02 09:12:00 -05:00
Chad Curtis 79c6e7e516 Translate replies/comments header and count on post detail
The replies section heading ('Replies'/'Comments') and the count noun
('reply'/'replies'/'comment'/'comments') were hardcoded English literals
adjacent to the placeholder fixed in the previous commit. Move them to a
new postDetail locale section with i18next plural keys, translated across
all locales.
2026-06-02 09:03:20 -05:00
Chad Curtis 9717a6827f Fix untranslated reply composer placeholder on post detail
The placeholder used a hardcoded 'Write a reply...' literal and referenced
a nonexistent compose.activityPlaceholder key (which fell back to the raw
key string). Switch to translated replyModal.placeholder.writeReply /
writeComment keys and add writeReply to all locales.
2026-06-02 08:57:55 -05:00
Chad Curtis f6c7bc366d Add app download link to slide-out menu; tune nudge spacing
- Add a 'Get the app' external link to the TopNav drawer menu, hidden
  inside the native app.
- Move the home-page nudge out of the feed (it now lives on the actual
  home page, CampaignsPage) and give it pt-8 breathing room above.
- Remove redundant pb-16 on main and shrink the footer's top gap on
  mobile (pt-6 sm:pt-12) so spacing above the footer isn't doubled.
2026-06-02 08:54:43 -05:00
Chad Curtis 4c7d059b0b Add Start a campaign button to middle home CTA
Place a 'Start a campaign' button next to the centered 'Browse all'
CTA in the middle of the campaigns home page, stacking on mobile.
2026-06-02 08:51:38 -05:00
Chad Curtis eae5e1c3a7 Always show Explore campaigns button on home page
Remove the !loggedIn gate that hid the 'Explore campaigns' CTA from
logged-in users. The campaigns home page shouldn't gate this button
behind login state.
2026-06-02 08:46:45 -05:00
Chad Curtis e7c488af63 Add Zapstore app download nudge
Prompt mobile-web visitors to install the native Android app from
Zapstore. Shows a card at the bottom of the home feed and a link in
the account switcher menu. Both are hidden inside the native app
(Capacitor.isNativePlatform) and on desktop (sm:hidden for the banner).

Adds nav.getApp and feed.getApp.* strings across all locales.
2026-06-02 08:40:58 -05:00
lemon 4153792e54 Add missing Why Agora translations 2026-06-02 04:29:26 -07:00
lemon c731256efb Preserve Latin display font for balances 2026-06-02 04:18:04 -07:00
lemon b174152566 Fix stale language switching 2026-06-02 04:09:44 -07:00
Chad Curtis d69cfa0862 fix zapstore info v2 2026-06-02 04:32:59 -05:00
Chad Curtis a5cc9c5163 Break walletHeroNote onto two lines 2026-06-02 04:24:57 -05:00
Chad Curtis 5d4d0825c6 Remove "no payout setup" from wallet hero note copy 2026-06-02 04:10:33 -05:00
Chad Curtis d0836328a4 qrcode: stop fixed pixel styles overflowing the container
The qrcode library hard-codes inline width/height pixel styles on the
canvas, overriding the Tailwind sizing classes (h-auto w-full) callers
pass in. On viewports narrower than the QR's intrinsic size this made
the code spill outside its rounded box — visible on the campaign
details donate panel. Remove the inline styles after rendering so the
caller's className controls the responsive size.
2026-06-02 02:02:58 -05:00
Chad Curtis 123f53e7a6 campaigns: carry public/private framing into custom wallet mode
Switching to a custom (manual-entry) wallet used to drop the friendly
accept picker entirely, leaving two unlabeled-purpose address inputs.
Restore the hand-holding: add an intro line restating the field-driven
model (public address, private code, or both) and label each input
with its meaning. The public/on-chain input is marked with a Bitcoin
icon and a 'Public. Anyone can see these donations.' caption; the
silent-payment input with an EyeOff icon and a privacy caption. Both
inputs keep the Wallet leading icon. Updates all 16 locales.
2026-06-02 01:59:44 -05:00
Chad Curtis 977fd000ea campaigns: make the donation-accept picker friendlier
Replace the three terse jargon pills (Accept All / Public Only /
Private Only, captioned with 'on-chain' and 'silent payment') with a
vertical stack of selectable option cards. Each card has a friendly
icon, a plain-language title, and a one-sentence reassurance written
for an anxious first-time creator, with the SP-dependent options
clearly disabled when the login can't support them.

Also softens the wallet hero card: drop the linked-icon trio for a
simple campaign-to-wallet arrow, and rewrite the copy without the
key/posts technical aside or em dashes. Updates all 16 locales.
2026-06-02 01:59:44 -05:00
Chad Curtis 5132141aa2 campaigns: give the wallet step a hero coupling card
Redesign the 'My wallet' branch of the campaign wizard's donation
destination step. Replace the plain identity+balance row with a
primary-tinted hero card modelled on the onboarding 'Save your key'
surface: a linked-icon trio (campaign -> key -> wallet) explains that
donations land in the creator's own Agora wallet unlocked by the same
key that signs their posts, with the avatar+live-balance chip
confirming the exact destination and a ShieldCheck reassurance line
below.
2026-06-02 01:59:44 -05:00
Chad Curtis b6dc57eb85 Merge branch 'feat/send-MAX' into 'main'
Feat/send max

See merge request soapbox-pub/agora!40
2026-06-02 06:36:55 +00:00
Chad Curtis 016a7b4a7d Constrain event detail pages to max-w-3xl
Both PostDetailShell (Nostr event details) and ExternalContentPage
(NIP-73 external content like bitcoin:tx) rendered their <main> with no
max-width under the wide layout, stretching edge-to-edge on large
screens. Add w-full max-w-3xl mx-auto to match the narrow-layout column
width used elsewhere.
2026-06-02 01:30:55 -05:00
mkfain 7ae63883e9 campaigns: surface 'Add to list' in the detail-page three-dots menu 2026-06-02 00:28:08 +02:00