Commit Graph

4447 Commits

Author SHA1 Message Date
Alex Gleason 55d873548d remove iframe.diy sandbox, nsite preview, and webxdc runtime
The iframe.diy-based sandbox infrastructure powered two features:
the nsite preview dialog (Run button on NsiteCard and AppHandlerContent)
and the webxdc embed (cartridge launcher + sandboxed iframe runtime
with kind 4932/20932 sync events).

Both are removed wholesale:

- iframe sandbox plumbing: SandboxFrame, src/lib/sandbox/*,
  iframeSubdomain, previewInjectedScript.
- nsite preview: NsitePreviewDialog, NsitePermissionManager,
  NsitePermissionPrompt, useNsiteSignerRpc, nsitePermissions,
  nsiteNostrProvider, NsitePlayerContext.
- webxdc runtime: WebxdcEmbed, Webxdc, GameControls, useWebxdc,
  webxdcMeta, public/cartridge.png, NOSTR_WEBXDC.md,
  @webxdc/types dependency, kindLabels/signerWithNudge entries
  for kinds 4932/20932.
- AppConfig: drop sandboxDomain, showWebxdc, feedIncludeWebxdc.
- extraKinds: drop kind 1063 webxdc entry; sidebar drops 'webxdc'.
- ComposeBox: .xdc uploads now flow through the generic file path
  (no UUID injection, no manifest extraction, .xdc removed from
  the file picker accept list).
- NoteContent / FileMetadataContent: webxdc branches removed; .xdc
  attachments fall through to the generic file card.
- LayoutContext / CenterColumnContext: only consumed by the removed
  fullscreen preview panels — deleted along with its provider in
  AppRouter.

NsiteCard keeps its rich link-preview card but loses the Run button
and preview dialog. AppHandlerContent keeps a kind 35128 `a`-tag
reference but replaces 'Run' with an external 'Open Site' link to
`<pubkeyB36><dTag>.nsite.lol`. The standard HTML iframe `sandbox`
attribute used by SpotifyEmbed/TweetEmbed/RedditEmbed is unrelated
to iframe.diy and stays.
2026-05-23 13:38:28 -05:00
mkfain 4d13c79633 Rename Help to About, redesign as landing-style page, redesign Donor + Activist guides
Squashed re-application of 15 local commits onto the new route-level
layout system (refactor: replace useLayoutOptions store with route-level
layout choice). Drops the obsolete useLayoutOptions({ fullBleed: true })
calls; the About page and the two guide pages instead live under the
wide FundraiserLayout route group in AppRouter.

Routing

- /about, /about/donors, /about/activists are now the canonical paths,
  in the wide layout (no max-width cap so sections can span the
  viewport with their own backgrounds).
- /help, /help/donors, /help/activists become <Navigate> redirects so
  existing bookmarks and links keep working.

About page (new src/pages/AboutPage.tsx)

A landing-style document modeled on https://soapbox.pub/agora,
brought in-app to explain how the platform works. Five sections:

  1. Hero (dark navy + world-map texture + orange halos), Bebas Neue
     italic headline with an inline orange highlighter behind the
     brand name, three trust chips, and Donor / Activist Guide CTAs.
     Tilted Venezuelan sample-campaign card on lg+, hidden on mobile.
     On mobile the H1 fits on one line via text-4xl + a conditional
     <br className="hidden sm:inline" />.
  2. Three steps. No middleman. (cream) 3-up white cards with 4:3
     step images and corner 01 / 02 / 03 numerals.
  3. Two ways to get paid. (white) Bitcoin Public Payments vs.
     Bitcoin Silent Payments compare cards with gradient header
     strips. Public-Payments trade-off carries the above-ground-
     activism warning; Silent-Payments trade-off is five bold-headline
     bullets (few wallets, slow, no push notifications, buggy,
     no public counts). Below: a primary-tinted No-Custody banner
     plus a 3-column comparison grid (Unlike GoFundMe / Unlike
     GiveSendGo / Unlike other 'Bitcoin' platforms).
  4. Frequently asked. (cream) Three integrated FAQ chapters in
     page flow (Getting started / Bitcoin donations / About Nostr),
     each with a Bebas Neue numeral + Inter Bold heading + card-row
     accordion items with a left orange accent on hover/open.
  5. Pick the side you're on. (white) Two large image-led guide
     cards (Donor / Activist) using the soapbox.pub photography.
     Closes with a quiet 'Still stuck? Follow Team Soapbox' line
     linking in-app to the pack via the /:nip19 route.

Typography is Bebas Neue (font-display) italic font-normal with
WebkitTextStroke for the hero H1 and the step numerals only; every
other heading uses Inter Bold (font-sans font-bold tracking-tight).
Bebas Neue is never font-bold (synthetic bold renders as smear at
display sizes).

Em dashes have been removed throughout the page and the
HelpFAQSection component. Box-drawing chars (U+2500) in section
banner comments are not em dashes and stay.

Section backgrounds alternate dark → cream → white → cream → white.
The dark and cream sections keep their literal palette in both light
and dark mode (an editorial choice that gives the page its
landing-page identity rather than being just another themed surface).

Donor + Activist Guides (new block-based design)

Both pages now compose from a typed sequence of GuideBlock variants
defined in helpContent.ts. Each block kind is rendered by a dedicated
component under src/components/guide/:

- GuideTLDR              top-of-page summary card with lede + checklist
- GuideSteps             numbered vertical flow of short steps
- PaymentComparisonTable Public vs. Silent side-by-side. Three-column
                         grid on desktop, two stacked tinted cards on
                         mobile. Row content driven by audience flag.
- CalloutCard            tinted info / warning / danger / success blocks
- OptionGrid             two-column tile grid for privacy and cash-out
                         options
- GuideProse             plain prose escape hatch
- InlinePaymentBadge     small pill that distinguishes the two payment
                         options
- index.ts               barrel

Content is rewritten throughout to reflect current reality: campaigns
can accept Public only, Silent only, or both; the QR code carries
both endpoints when both are accepted; wallets without silent-
payments support fall back to a regular Bitcoin transaction; silent
payments are slow, scan-based, lack push notifications, are
bleeding-edge, and produce no public donation counts.

Activist Guide structure:

  TLDR
  How receiving works
  What everyone can see           (intentionally before the table)
  Public vs. Silent comparison
  A note on silent payments today (calm prose, not an alarm callout)
  Move donations promptly
  Cashing out privately           (silent-payments hop, Lightning
                                   swap, coinjoin, P2P with brokers,
                                   spend it directly)
  Avoid centralized tumblers

Donor Guide structure:

  TLDR
  How a donation flows
  Public vs. Silent comparison
  Public donations are visible on-chain forever callout
  Donating privately option grid
  Consumer apps can't make you anonymous callout
  A note on silent payments today

Other touchpoints

- Sidebar (sidebarItems.tsx): Help label → About, icon LifeBuoy → Info,
  path /help → /about.
- Top nav profile menu (TopNav.tsx): Help → About.
- Site footer (AppRouter.tsx inline): Help → About.
- AccountSwitcher dropdown: Help → About.
- LandingHero FAQ button → /about#faq.
- HelpTip popover footer link → /about#faq.
- GuideHero back link → /about, label 'Back to About', wider
  max-w-5xl on lg+ container so it sits well on the now-full-bleed
  hero. Inner overlay min-height bumped to 320px on lg+.
- CampaignsPage 'How it works' button → /about.

New assets in /public/about/ pulled from soapbox.pub:

- world-map-bg.png (hero + textures)
- venezuela-libertad-presos-politicos.png (hero sample-card image)
- donor-guide-freedom-libertad.jpeg
- activist-guide-unity.png

Step photos in /public/help/ (step-1-account.jpg, step-2-send.jpg,
step-3-spend.jpg) for the Three Steps section.

HelpFAQSection gains:

- variant: 'list' | 'cards' (default 'list')
- tabs: boolean (only meaningful with variant='cards')
- listTone: 'default' | 'reference' (quieter category labels and more
  breathable accordion items for the About page; existing inline
  callers keep the default pill style)

In 'reference' mode each accordion item gets a card-row treatment
(rounded white card, subtle border, hover lifts to primary/40 border,
left orange accent rule driven off data-state=open).

helpContent.ts FAQ content (FAQItem / FAQCategory and templates) is
left untouched. Only the donor/activist guide section was rewritten
into GuideBlock[] arrays.
2026-05-23 13:38:13 -05:00
Alex Gleason 1750215d1b refactor: replace useLayoutOptions store with route-level layout choice
The Ditto-era LayoutStore let pages push layout config (FAB, sidebars,
arc styling, max-width) up to MainLayout via an external store. After
the fundraiser refocus the only knob left is noMaxWidth, and the
`useLayoutEffect + deferred-rAF-reset` dance had a real race condition
where the previous page's options could bleed into the next page
during Suspense transitions — visible as max-w-3xl appearing on pages
that should be wide.

Switch to idiomatic React: two layout variants picked by the router.

- Narrow group (`max-w-3xl`): /feed, /notifications, /search, /profile,
  /t/:tag, /g/:geohash, /settings/*, /wallet*, /bitcoin, /help/*,
  /privacy, /safety, /changelog, /organizers, /remoteloginsuccess
- Wide group (no max-width): /, /campaigns/*, /groups/*, /pledges/*,
  /dashboard, /i/*, /:nip19, catch-all NotFound

`FundraiserLayout` now takes a `narrow` prop and is mounted twice in
the route tree, once per group. Pages no longer reach upward — the
width is a property of the route they live in.

Delete useLayoutOptions and its 13 call sites, useLayoutSnapshot,
LayoutOptions, LayoutStore, and LayoutStoreContext. `LayoutContext.ts`
shrinks to just CenterColumnContext + useCenterColumn (still used by
WebxdcEmbed and NsitePreviewDialog for portal targets).
2026-05-23 13:15:27 -05:00
Alex Gleason 5aba47908d chore: drop unused exports from lib/sidebarItems.tsx
After removing the sidebar / mobile drawer chrome the registry's
remaining consumers are useFeedSettings, ProfileSearchDropdown, and
CONTENT_KIND_ICONS. Drop the helpers no one calls anymore:

- isSidebarDivider, nostrUriToNip19, nsiteUriToSubdomain
- getSidebarItem, sidebarItemIcon, itemLabel, itemPath, isItemActive
- The internal OPTIONAL_SIDEBAR_ITEMS / ALL_SIDEBAR_ITEMS /
  SIDEBAR_ITEM_MAP machinery that only served those helpers

Fold the previously-optional 'dashboard' entry into the main
SIDEBAR_ITEMS list so SIDEBAR_ITEM_IDS still recognizes it.
2026-05-23 13:05:25 -05:00
Alex Gleason 16b597073d chore: prune dead useLayoutOptions arguments and vestigial contexts
The inlined FundraiserLayout only reads noMaxWidth and wrapperClassName
from the layout snapshot, so all other LayoutOptions fields (showFAB,
fabKind, fabHref, onFabClick, fabIcon, fabMenu, hasSubHeader,
rightSidebar, scrollContainer, noOverscroll, noArcs, hideTopBar,
hideBottomNav, fullBleed) are dead.

- Strip those fields from every useLayoutOptions call site (~25 pages)
- Remove the now-empty calls entirely where the result is { }
- Slim LayoutOptions to just noMaxWidth + wrapperClassName
- Remove the fullBleed preset expansion in useLayoutOptions
- Drop DrawerContext, useOpenDrawer, NavHiddenContext, useNavHidden
  (no useful consumers without mobile bottom nav)
- Inline navHidden=false at the two SubHeaderBar / ProfileTabs sites
  and drop the dead conditional classes
- Keep CenterColumnContext + useCenterColumn (still used by
  WebxdcEmbed and NsitePreviewDialog for portal targets)
- Drop LiveStreamPage's chatSidebar (it relied on rightSidebar which
  no longer exists)
- Drop ProfileSettings's useLayoutOptions sidebar injection
  (ProfileRightSidebar is still rendered inline in the page)
2026-05-23 13:02:35 -05:00
Alex Gleason 6ef4dd9812 fix: restore conditional max-width cap in inlined FundraiserLayout
The router-level layout introduced in 73bb2a17 hardcoded max-w-3xl on
every page, ignoring useLayoutOptions({ noMaxWidth: true }) and
wrapperClassName. Split FundraiserLayout into an outer provider and
an inner consumer so the inner component can read the snapshot, and
restore the conditional class composition the original
FundraiserLayout used. CampaignDetailPage and ActionDetailPage (and
any page using the fullBleed preset) render edge-to-edge again.
2026-05-23 12:49:02 -05:00
Alex Gleason 256560cf3b chore: delete components/hooks/lib orphaned by page deletions
Iteratively removed files no longer imported anywhere after the
unreachable-page sweep:

- Layout chrome: MainLayout, LeftSidebar, RightSidebar, WidgetSidebar,
  MobileDrawer, MobileBottomNav, MobileTopBar, FundraiserLayout
  component, FloatingComposeButton, AudioNavigationGuard,
  MinimizedAudioBar, DeepLinkHandler
- Music subsystem: useMusic*, MusicHeroCard, MusicTrackCard,
  MusicPlaylistsTab, MusicTracksTab, MusicArtistsTab, MusicDiscoverTab,
  MusicSortFilterBar, MusicPlaylistCard
- World/discovery: useWorldFeed, WorldMap, WorldDiscoveryPanel/Drawer,
  CountryBrowser, CountryActivityPopover, CountryPulseStrip, TagChips,
  HorizontalScroll, ContentCTACard
- Letter system: useLetterPreferences, useStationery,
  useThemeStationery, LetterCard, LetterEditor, DrawingCanvas,
  StationeryPicker, FramePicker, FramePreviews, SendAnimation,
  ComposeLetterSheet, LetterPreferencesSection, useEnvelopeDimensions,
  svgDrawing
- Bird/Constellation: useBirdSong, BirdSongPlayer, BirdexContent,
  BirdexTile, BirdexChorusButton, BirdDetectionContent,
  ConstellationContent, ConstellationStarMap, parseBirdex, starCatalog
- Books: useBookFeed, useBookSearch, useBookDetails, usePopularBooks,
  useBookSummary, BookFeedItem
- Bluesky: useBluesky*, BlueskyWidget
- Wikipedia: useWikipediaSearch, useWikipediaFeatured, WikipediaWidget
- AI chat: useAIChatSession, useAIChatTools, aiChatTools, tools/*,
  AIChatWidget
- Article editor: ArticleEditor, MilkdownEditor, MilkdownToolbar,
  LinkDialog, usePublishedArticles, articleHelpers
- Badges/community/highlights: useBadgeFeed, useCommunity*,
  useCreateBadge, BadgeRecoveryDialog, CreateBadgeDialog,
  CreateGoalDialog, CommunityCard, HighlightContent
- Profile tabs: useProfileTabs, useResolveTabFilter,
  usePublishProfileTabs, ProfileTabEditModal, ProfileTabsManagerModal,
  profileTabsEvent
- Webxdc/Photo/Stream/Vine: WebxdcUploadDialog, PhotoComposeModal,
  PhotoBottomBar, useStreamKind, vineGlobalMute
- Miscellaneous: AudioPlayerContext, EditProfileForm (unused), feed
  widgets, FabButton, sidebar widgets, ContentSettings, FontPicker,
  EmbeddedPeopleListCard, PeopleListDetailContent, BitcoinContent*,
  exchange rate service, deduplicateEvents, feedDiversity,
  createZapInvoice, MuteListRecoveryDialog, EventRecoveryDialog,
  ZapContent, useCuratedDittoFeed, useScrollDirection, useDrafts,
  useUserZap, useHasUnreadNotifications, useMutedAuthorFilter,
  useFormatMoney, useKeyboardVisible, AgoraLogo, MailboxIcon,
  PlanetIcon, WhiteNoiseIcon, BarsStaggeredIcon, ScopedTheme,
  CommentsSheet, MobileSearchSheet, FollowAllSplitButton,
  FeedEditModal, KindInfoButton, WidgetCard, WidgetPickerDialog,
  SidebarNavItem, SidebarMoreMenu, TrendSparkline, DorkThinking,
  NostrEventSidebarItem, NsiteSidebarItem, ExternalContentSidebarItem,
  GathererCardHeader, MediaCollage, BackgroundPicker,
  BitcoinPrivateDisclaimer, UnknownKindContent, proxyUrl, localDrafts,
  flagPalette, coordinates, checkWasmSupport
- Unused shadcn primitives: calendar, context-menu, input-otp,
  breadcrumb, menubar, toggle, table, resizable, navigation-menu,
  carousel, aspect-ratio, pagination, color-picker, drawer
2026-05-23 12:38:19 -05:00
Alex Gleason b975e55794 chore: delete unreachable page files
Removes 34 page components no longer routed from AppRouter:
content-type feeds (Photos, Videos, Music, Vines, Podcasts,
Books, Treasures, Webxdc, World, Badges, Verified, Archive,
Bluesky, Wikipedia), social/utility pages (Messages, Trends,
UserLists, Bookmarks, AIChat, Follow, Receive), content
creation (CreateEvent, ArticleEditor, LetterCompose), settings
(ContentSettings, LetterPreferences), letter system pages,
relay/domain feeds, and the now-orphaned KindFeedPage base.
2026-05-23 12:31:45 -05:00
Alex Gleason 73bb2a1707 chore: strip unreachable routes from AppRouter
Inlines a slimmed FundraiserLayout into AppRouter and removes routes
not reachable from the visible nav (TopNav, AccountSwitcher,
SettingsPage list, footer, or in-feed hashtag links):

- /messages, /trends, /lists, /feed/:domain
- All dedicated kind feed pages: /photos /videos /vines /music
  /podcasts /polls /treasures /colors /packs /webxdc /articles
  /highlights /decks /emojis /development /books /badges /world
  /verified /archive /bluesky /wikipedia
- /events, /events/new
- /bookmarks, /ai-chat, /agent
- /follow/:npub, /receive, /letters/*, /r/*
- /settings/feed (ContentSettings), /settings/letters
- /actions, /actions/new, /communities, /streams, /event-dashboard
  aliases and the corresponding settings list redirects

Also removes AudioPlayerProvider, MinimizedAudioBar,
AudioNavigationGuard, DeepLinkHandler, FloatingComposeButton,
and the eager Messages import from the shell.

Removes the Home Feed entry from SettingsPage.
2026-05-23 12:30:40 -05:00
Alex Gleason 26d9338370 Merge branch 'main' of gitlab.com:soapbox-pub/agora 2026-05-23 12:04:06 -05:00
mkfain a858053358 Credit the World Liberty Congress in 'Who made Agora' 2026-05-23 11:24:40 -05:00
mkfain 7bf805c9c3 Split FAQ into About Agora / Bitcoin / About Nostr
Move the protocol-level Nostr questions (what is Nostr, why is the
sign-in so long, what happens if I lose my key, password-manager use)
out of 'About Agora' and into a new 'About Nostr' category positioned
after the Bitcoin Donations section. Newcomers see what Agora is and
how Bitcoin works first, and only dig into Nostr's identity model
once they care.

Also reorder the silent-payments FAQ to sit above the Lightning one,
since silent payments are part of the answer to 'how do payments
work' while Lightning is the explanation of an absence.
2026-05-23 11:22:40 -05:00
mkfain b08e6713e5 Add 'How it works' button to home hero, fix settles copy
Donations settle to the activist's wallet, not to abstract
'beneficiaries' — the wallet framing matches the rest of the app.

Adds a secondary outline button next to 'Start a campaign' that links
to /help so newcomers have an obvious next step before committing to
creating a campaign.
2026-05-23 11:13:37 -05:00
mkfain d9fca8b0ec Stop using 'on-chain' as shorthand for 'public' in help content
Silent payments still settle on-chain; the meaningful distinction is
public vs. private (a silent-payment transaction's output can't be
linked back to the recipient's reusable code). Reword help and guide
copy so 'on-chain' only appears where it correctly contrasts with
Lightning or describes chain mechanics that apply to both kinds of
payment.
2026-05-23 11:06:43 -05:00
mkfain 90fcd83994 Document silent-payments support in help content
Activists now pick public, private, or both when creating a campaign;
both generates a combined BIP-21 QR so SP-aware wallets pay privately
and others fall back to on-chain. Update the FAQ, Donor Guide, and
Activist Guide accordingly:

- Recommend Ditto Wallet and Dana for donors who want privacy.
- Lead the activist cashout path with moving funds into a silent-
  payments wallet first, then spending onward with the trail broken.
- Flip the 'why no silent payments' FAQ into 'yes, we support them'.
- Note that silent-payment donations are excluded from public donor
  lists and totals by design.
2026-05-23 10:51:56 -05:00
lemon 11a51509ae Improve save key warning contrast 2026-05-22 23:37:32 -07:00
lemon c1023ad4db Restore profile feed repost and badge rendering 2026-05-22 23:37:32 -07:00
lemon 6bcddda1cb Use main header on follow pages 2026-05-22 23:37:32 -07:00
lemon ba35eb0733 Use canonical Agora share URLs 2026-05-22 23:37:32 -07:00
lemon 5582c178db Add calendar event country selection 2026-05-22 23:37:32 -07:00
lemon fad84655ef Simplify calendar event details 2026-05-22 23:37:32 -07:00
lemon 8335b8082c Polish event and group detail layouts 2026-05-22 23:37:32 -07:00
lemon 67a31a918a Add group country and topic fields 2026-05-22 23:37:32 -07:00
lemon 5bd4adb902 Redesign group action panel 2026-05-22 23:37:32 -07:00
lemon 1ad335c122 Fix mobile profile and composer focus 2026-05-22 23:37:32 -07:00
lemon e1c7f63a51 Polish group member preview 2026-05-22 23:37:32 -07:00
lemon aea5b1f119 Emphasize group member preview 2026-05-22 23:37:32 -07:00
lemon df7200d046 Refine group detail intro layout 2026-05-22 23:37:32 -07:00
lemon 44227900d7 Fix calendar event details clipping 2026-05-22 23:37:32 -07:00
lemon 14d46f0c6c Harmonize group and event detail pages 2026-05-22 23:37:32 -07:00
lemon 72888d7f77 Tune campaign and pledge hero actions 2026-05-22 23:37:32 -07:00
lemon 6ed3ac0026 Match pledge submissions styling to campaigns 2026-05-22 23:37:32 -07:00
lemon 13deea9895 Match pledge detail layout to campaigns 2026-05-22 23:37:32 -07:00
lemon 519c25eb94 Match group detail layout to campaigns 2026-05-22 23:37:32 -07:00
lemon dbc539e202 Rename organizations to groups 2026-05-22 23:37:32 -07:00
Alex Gleason addeea92a7 HD wallet SP scan: fetch blocks in parallel (5-8x speedup)
The BlindBit Oracle exposes only per-block endpoints, so a 144-block
'Scan recent' was up to ~288 sequential HTTP round trips against the
public mainnet indexer (~700ms each = ~200s wall clock before any ECDH
math). Profiling against the configured indexer showed latency, not
ECDH or bandwidth, was the dominant cost — and that the server happily
handled 10 concurrent requests with ~6x speedup.

Replace the sequential for-loop in scanRange with a sliding-window
pipeline: keep up to SCAN_FETCH_CONCURRENCY (8) fetchBlockEntries
calls in flight, but process completed blocks strictly in height order
so optimisticRef, matchesFound, scan-progress, and the contiguous
scanHeight advancement stay single-writer and monotonic.

Cancel and error semantics are preserved:
  - cancelScan still aborts every in-flight fetch via the controller.
  - First in-order fetch failure aborts the rest of the scan, same as
    before. The finally block drains still-pending fetches so their
    rejections don't surface as unhandled promise rejections.
2026-05-23 01:00:44 -05:00
Alex Gleason f7c457b4d4 Campaigns: drop "Private" badges and "totals not public" copy
- Card grid: remove the corner "Private" badge on silent-payment cards.
- Detail hero: remove the "Private campaign" badge above the title.
- Detail donate aside: drop the "Private campaign — totals not public"
  line; silent-payment campaigns now show only the goal target (if any)
  in place of the raised/progress block.

The donate panel already makes it clear which wallet endpoints are
available, so the redundant privacy chrome was just noise.
2026-05-23 00:56:40 -05:00
Alex Gleason e8821deb14 Campaign donate panel: drop wallet disclaimers, combine copy rows
For dual-wallet campaigns the QR already encodes a single BIP-21 URI
(`bitcoin:<addr>?sp=<sp>`). Mirror that in the copyable row instead of
showing one row per endpoint — modern wallets parse the URI in their
recipient field and BIP-352-aware ones pick up the `sp=` parameter
automatically. Single-endpoint campaigns still show the raw value with
a label-appropriate toast.

Also remove the on-chain traceability disclaimer and the silent-payment
unlinkability notice — both are noise on the donation panel.
2026-05-23 00:54:01 -05:00
Alex Gleason bdb3868245 Merge branch 'main' of gitlab.com:soapbox-pub/agora 2026-05-23 00:11:49 -05:00
Alex Gleason c42c3ce116 Campaign new: replace chips/disclosure with two dropdowns
The chip + 'Add another address' UX was too clever. Replace it with:

- A 'wallet source' dropdown ('My wallet' / 'Custom'), defaulting to
  'My wallet' for nsec users and matching the pre-dual-wallet form.
- An 'accept' dropdown beneath it (only shown when source is 'My
  wallet') that picks which donation types to accept: all (default),
  public only, or private only. The 'all' and 'private' options are
  disabled when the active login can't derive a silent-payment code.

When source is 'Custom' we still surface separate bc1 and sp1 inputs
so the user can publish a dual-endpoint campaign with addresses they
hold elsewhere. At least one of them must parse.

Without nsec access the dropdowns are skipped entirely and the two
custom inputs are shown directly \u2014 same as before.

Edit mode still starts in 'Custom' with the existing values pre-filled
so a no-op edit doesn't surprise the user by re-deriving HD
endpoints (or burning a receive index).
2026-05-23 00:11:29 -05:00
Alex Gleason dcab5c95b7 Campaign new: drop wallet disclaimers from the form
The on-chain traceability and silent-payment privacy notices stay
where they matter — on the campaign detail page's donate panel, where
donors actually see them. The form page no longer needs them
duplicated under the picker.
2026-05-23 00:03:42 -05:00
Alex Gleason 708ebd9bef Campaigns: support both on-chain and silent-payment wallets per campaign
A campaign may now declare up to two `w` tags — at most one mainnet
on-chain address (bc1q…/bc1p…) and at most one silent-payment code
(sp1…) — and the QR/payment panel combines them into a single BIP-21
URI (`bitcoin:<bc1>?sp=<sp1>`) when both are present. BIP-352-aware
wallets pick the SP parameter automatically; legacy wallets fall back
to the on-chain address.

The campaign form is reorganized around the dual-endpoint model. Users
with nsec access see two avatar chips — "My wallet" and "My private
wallet" — both selected by default and an "Add another address"
disclosure that reveals separate bc1 and sp1 inputs. A typed value
wins over the corresponding chip's HD-derived value, so a cold-storage
address can be substituted without giving up the SP code. Users
without nsec access (extension / bunker logins) see the two custom
inputs unconditionally. At least one of the four sources must resolve.

The on-chain receive-index cursor is still advanced only at publish
time, and now only when "My wallet" is selected AND no custom
on-chain value was provided — so the cursor never burns on a no-op
edit or on a publish where the user overrode the chip with their own
address.

`ParsedCampaign.wallet` is replaced by `ParsedCampaign.wallets`, a
`{ onchain?, sp? }` struct. Consumers (`useCampaignDonations`,
`useDonateCampaign`, `useProfileCampaignStats`, `useOnchainZaps`,
`CampaignCard`, `CampaignDetailPage`, profile rails) keep their
existing on-chain semantics by reading `wallets.onchain`. The
"Private campaign" badge and hidden-aggregates UI now trigger on
SP-only campaigns (no on-chain endpoint), matching the spec.
2026-05-23 00:00:39 -05:00
filemon e4deebf320 MySquarePage: polish wallet fallback copy and trim brittle cost comment
- Change non-nsec wallet fallback from 'View wallet' to 'Wallet details'
- Replace exact Blockbook/Esplora call counts in module JSDoc with
  behavior-focused wording that won't drift when hook internals change
2026-05-22 21:44:10 -03:00
mkfain 5a04b071f1 Mobile drawer: show Help in main menu when logged out
Logged-in users reach Help through the account menu (AccountSwitcher
dropdown), but logged-out users have no equivalent affordance — the
default sidebar order doesn't include Help, so it was buried in the
"More…" menu. Force it into the main list when there's no user, and
suppress the duplicate in the hidden-items menu.
2026-05-22 19:43:25 -05:00
filemon 5ca616b304 Fix MySquarePage after merging main
- Replace removed useBitcoinWallet with useHdWallet + useBtcPrice
- Show graceful 'View wallet' fallback for non-nsec logins
- Drop includeArchived and recipientPubkeys (removed from useCampaigns)
- Remove beneficiary campaign query and 'Started for you' shelf
  (campaigns are now self-authored; author = beneficiary)
- Simplify hero card from 4 stat tiles to 3
- Update stale JSDoc referencing Esplora and old query costs
2026-05-22 21:37:05 -03:00
filemon 3392e0a91e Merge branch 'main' into feat/my-square 2026-05-22 21:23:01 -03:00
Alex Gleason 7cdeead7b2 Campaign progress: source raised total from on-chain address balance
Per-campaign 'raised' was the sum of verified kind 8333 donation receipts:
each receipt's tx was re-fetched and its outputs paying the campaign's `w`
address were summed. That counted only donations whose donor published a
receipt — direct on-chain payments were ignored — and required N `/tx/`
lookups per campaign view.

Source `totalSats` from a single `/address/{w}` lookup against the
configured Esplora endpoint (default: mempool.space) and use
`chain_stats.funded_txo_sum` (lifetime received). Any payment to the
address now counts, and the progress bar does not regress when the
beneficiary spends.

Kind 8333 receipts are still fetched and verified to power the donor list,
donor count, and per-tx breakdown — they just no longer drive the headline
number.

Silent-payment campaigns are unchanged (no observable balance).
`useProfileCampaignStats` and the `SortedByTopGrid` on the profile
campaigns tab switch to the same address-balance source.
2026-05-22 18:55:07 -05:00
Alex Gleason 93108bc00e AuthDialog: mark nsec login form with data-nsec-allowed
The nsec paste guard (useNsecPasteGuard) bails out when the paste
target has id="nsec" or sits inside [data-nsec-allowed], but the
login form had neither marker, so pasting an nsec into the login
field triggered the "Secret key detected" toast and was blocked.
2026-05-22 16:45:38 -05:00
Alex Gleason 35b84c76dc Campaign detail: drop in-app on-chain Donate button
Removes the 'Donate' button that opened the PSBT-signing
DonateDialog above the wallet QR/address panel. Agora no longer
runs the in-app on-chain donate flow — donors pay from an
external wallet via the QR code, the same path silent-payment
campaigns already used.

The DonateDialog component and useDonateCampaign hook stay in
the tree for now; they're still wired into the profile rail's
campaign-donate dropdown.
2026-05-22 16:35:14 -05:00
Alex Gleason 6671908e2e Use white text on saturated brand-primary buttons
The contrastForeground() helper relied on isDarkTheme(), which only
treats a color as dark when its luminance < 0.2. The default orange
primary (24 100% 50%) has luminance ~0.31, so it was classified as a
light background and got dark text — black letters on orange buttons
throughout the site. Same problem hit most saturated mid-lightness
brand colors (red, blue, purple, green).

Raise the threshold to 0.55 so saturated mid-lightness colors get
white text while genuinely light pastels (pink theme, sunset, light
mode background) still get dark text.
2026-05-22 16:30:45 -05:00