Commit Graph

3629 Commits

Author SHA1 Message Date
mkfain f413d29fa1 Remove duplicate close X in mobile hamburger menu
The mobile nav drawer in TopNav has its own X button inside the panel
header, but SheetContent was also rendering the shadcn primitive close
button just outside the panel — two X buttons for the same sheet.

Add an opt-in `hideClose` prop to SheetContent and set it on the
TopNav drawer. Other Sheet consumers (MobileDrawer, etc.) keep the
default built-in close.
2026-05-17 20:30:45 -05:00
mkfain 323c613222 Add archive flow for campaigns
Authors can soft-close a campaign by republishing it with a
`["status", "archived"]` tag. Archived campaigns are hidden from the
main fundraisers feed and the donate button is disabled, but the detail
page still loads by direct link so existing donors can find it and
past donations remain attached. The author sees Archive / Reopen
buttons on the detail page and an Archived badge on cards.

useCampaigns gains an `includeArchived` option (default false) so a
future profile view can opt in. NIP.md documents the new status tag.
2026-05-17 20:20:44 -05:00
mkfain babfbc5b10 Round campaign USD amounts to whole dollars
Cents are visual noise on zap goal progress displays. Add satsToUSDWhole
helper and use it in CampaignCard, CampaignDetailPage, and the goal
preview in CreateCampaignPage. Wallet and send-bitcoin flows continue to
use satsToUSD with cents.
2026-05-17 20:11:14 -05:00
lemon 640a8328cf Polish campaign editing form 2026-05-17 18:03:46 -07:00
lemon e56523b819 Add campaign editing flow 2026-05-17 18:03:46 -07:00
lemon 177caded5c Refine campaign creation form 2026-05-17 18:03:46 -07:00
Chad Curtis d9d99d6b0b Point Discover nav link to /feed 2026-05-17 19:55:26 -05:00
lemon 7dbfc31f04 Render zaps with comment card layout 2026-05-17 17:40:21 -07:00
lemon fb6f157c42 Align zap cards with comments 2026-05-17 17:40:21 -07:00
lemon 3504a24be5 Realign zap card with the regular comment layout
The zap card was an ActivityCard with a chunky amber circle in
the avatar slot and a compact ActorRow up top. That visual
language reads as 'an activity log entry' which clashes with the
NIP-22 comments alongside it on the campaign page. Rebuild it
to mirror NoteCard's normal layout exactly:

- Donor avatar takes the standard size-11 (size-10 in threaded
  mode) slot, with a small amber zap badge anchored bottom-right
  to keep the kind signal.
- Author block uses the same font-bold name + nip05 + timeAgo
  stack as a regular note, with the verb ('donated' / 'sent')
  and amount inlined on the name row so the card reads as one
  sentence.
- For campaign targets the amount is followed by 'to <Campaign
  Title>' where the title is a clickable Link to the naddr —
  same routing the regular CommentContext header would use.
  Resolved via a single useAddrEvent call gated on the receipt's
  a-tag so non-campaign zaps incur no fetch.
- The donor comment renders below the author row as muted italic
  text — same spot a normal note's body sits — instead of being
  tucked under the actor row.
- Action bar uses the shared {actionButtons} JSX with the exact
  same spacing as a comment, so reply / repost / react / zap /
  share / more line up vertically across cards in the thread.

The amber Zap import and ProfileHoverCard / Avatar imports were
already in scope, so no new imports beyond useAddrEvent.
2026-05-17 17:40:21 -07:00
lemon f49c20787e Polish zap cards and clamp the campaign story
Four related changes:

1. Campaign story now clips to three lines (~4.5rem) behind a
   soft fade overlay, with a Read more / Show less toggle.
   When there's no story yet the empty placeholder renders
   unclipped as before.

2. Zap cards in NoteCard gained the same reply/repost/react/
   share action bar as a regular note. Each kind 9735 / 8333
   event is a valid Nostr event in its own right, so NIP-22
   replies target it directly via its event id and reactions
   bind to it the same way.

3. The 'zapped' label is now 'donated' when the receipt's a-tag
   points at a kind 30223 campaign, and 'sent' otherwise. The
   target kind is read from the addressable coordinate; pure
   e-tag Lightning zaps fall back to 'sent' without a fetch.

4. Zap amounts render in USD when a BTC→USD price is cached,
   with the raw sats string moved to the title tooltip. Falls
   back to sats when the price hasn't loaded. A new useBtcPrice
   hook shares the existing 'btc-price' cache key so all
   call sites (NoteCard, CampaignCard, useBitcoinWallet) dedupe
   to one in-flight request.
2026-05-17 17:40:21 -07:00
lemon 39fed90296 Trim campaign donate card
- Drop the extra divider above the action bar; PostActionBar
  already carries its own border-t/b.
- Remove hex pubkey lines under organizer and beneficiary names;
  show the NIP-05 instead when available, otherwise just the name.
- Drop the Donors section entirely. Donations now appear inline
  in the comments thread, so a separate list is redundant.
- Relocate the organizer attribution from a dedicated card section
  to a small 'by {name}' link next to the title in the hero
  overlay. Same subtle styling, just a less intrusive spot.
2026-05-17 17:40:21 -07:00
lemon 760e11138d Show donation receipts in campaign comments
Three changes work together so campaign pages reflect how
campaigns actually receive support — via on-chain donations,
not Lightning zaps:

1. NoteCard's zap-receipt layout now renders kind 8333 in
   addition to kind 9735. The helpers (getZapAmountSats,
   getZapSenderPubkey) already branched correctly; only the
   isZap gate and the amount/message extraction were 9735-only.

2. PostActionBar gained a hideZap prop. Campaigns set it so the
   action bar shows only reply / repost / react / share — a
   generic Lightning zap is the wrong CTA when the campaign
   has its own donation flow.

3. CampaignDetailPage interleaves kind 8333 donation receipts
   into the comments thread, sorted by created_at alongside
   kind 1111 comments. Each donation produces one receipt per
   beneficiary, so we dedupe by (txid, donor) and rewrite the
   canonical receipt's amount tag to the summed total so the
   card shows the full donation rather than one share.
2026-05-17 17:39:39 -07:00
lemon 534b8f0102 Add comments and reactions to campaign pages
Campaigns (kind 30223) now expose the standard PostActionBar
(reply/repost/react/zap/share/more) plus a NIP-22 threaded
comments list, mirroring how PostDetailPage handles other
addressable kinds. A stats row above the action bar opens the
existing InteractionsModal.
2026-05-17 17:39:39 -07:00
lemon 5cb4c9f950 Align campaign support section 2026-05-17 17:39:18 -07:00
lemon e37552c8ce Refine campaign support card 2026-05-17 17:39:18 -07:00
lemon 3af32e167c Simplify campaign support card 2026-05-17 17:39:18 -07:00
lemon 3927a50633 Combine campaign support sections 2026-05-17 17:39:18 -07:00
lemon 0712034720 Clarify campaign organizer and recipients 2026-05-17 17:39:18 -07:00
lemon c0a23061ee Move beneficiaries above campaign story 2026-05-17 17:38:27 -07:00
lemon 44be9e6e35 Promote campaign donation panel 2026-05-17 17:38:27 -07:00
lemon fa813ed084 Restyle campaign category meta 2026-05-17 17:38:27 -07:00
lemon 2c58a7b0fd Tune campaign hero actions 2026-05-17 17:38:27 -07:00
lemon 532ff57c29 Overlay campaign hero details 2026-05-17 17:38:27 -07:00
lemon f99c1d0b17 Avoid duplicate campaign story image 2026-05-17 17:38:27 -07:00
lemon 703bb6d3ab Use people search for campaign beneficiaries 2026-05-17 17:38:27 -07:00
lemon 162d4eee43 Make campaigns USD-first 2026-05-17 17:38:27 -07:00
Alex Gleason 810cbfba00 Set default featured campaigns 2026-05-17 19:34:51 -05:00
Chad Curtis 3e5af1922d Build campaign creator link with useProfileUrl
The 'organized by' link on the campaign detail page was using a raw
hex pubkey URL (/<hex>). Switch to useProfileUrl so the link prefers
the creator's verified NIP-05 identifier when one is available and
falls back to the npub otherwise — same pattern the rest of the app
uses for profile navigation.
2026-05-17 19:00:25 -05:00
Chad Curtis 3a540ffaa1 Point TopNav mobile drawer Profile link at the user's npub
The mobile drawer's Profile link was hardcoded to /profile, which has
no route and fell through to the catch-all NIP19Page. nip19.decode
threw on 'profile' and the profile page rendered 'Please log in to
view your profile' even when the user was logged in.

Encode the current user's pubkey as an npub for the link target,
matching how every other profile link in the app is built.

Regression-of: 704cb42e
2026-05-17 19:00:20 -05:00
Chad Curtis 2b9ea24238 Resolve hex pubkey URLs on the profile page
NIP19Page renders <ProfilePage /> for raw 64-char hex identifiers that
relays resolve to a kind-0 author, but ProfilePage only knew how to
decode NIP-19 and NIP-05. The hex param fell through nip19.decode (which
throws), returned undefined, and the page rendered 'Please log in to
view your profile' — even for logged-in users visiting somebody else's
hex URL.

Accept raw hex pubkeys in the pubkey resolver, and replace the
misleading log-in copy with 'User not found' (the no-param case can't
reach this branch from the router anyway).

Regression-of: d58f4bb6
2026-05-17 19:00:14 -05:00
Chad Curtis 77eee4f872 Switch credential domain and Android deep links from ditto.pub to agora.spot
The iOS Associated Domains entitlement, Android intent filters, AASA
file, and assetlinks.json already reference agora.spot. Three call
sites still hard-coded ditto.pub:

- CREDENTIAL_DOMAIN in src/lib/credentialManager.ts, which keys iCloud
  Keychain Shared Web Credentials by domain. Saved nsecs were being
  filed under ditto.pub and so could never be matched against the
  agora.spot AASA file.
- MainActivity.handleNotificationIntent host check, which only routed
  the WebView when the tapped notification's URI host equaled
  ditto.pub.
- NostrPoller.showNotification, which built notification PendingIntents
  pointing at https://ditto.pub/notifications.
2026-05-17 18:42:46 -05:00
Chad Curtis 1b4399df68 Rebrand app identifier and IPA name from Ditto to Agora
Renames the Capacitor app identifier from pub.agora.app to
spot.agora.app and cleans up Ditto-branded artifacts that don't refer
to upstream Ditto-the-project or Ditto-stack services.

App identifier (pub.agora.app -> spot.agora.app):
- capacitor.config.ts appId
- android applicationId, namespace, package_name string, custom_url_scheme
- iOS PRODUCT_BUNDLE_IDENTIFIER (Debug + Release)
- public/.well-known/assetlinks.json package_name
- public/.well-known/apple-app-site-association app id
- Info.plist BGTaskSchedulerPermittedIdentifiers and the matching
  Swift bgTaskIdentifier (previously mismatched: plist said
  pub.agora.app.notification-refresh, Swift said
  pub.ditto.app.notification-refresh, so background refresh would
  silently fail to register)
- src/lib/helpContent.ts Zapstore URLs
- .gitlab-ci.yml --package_name for fastlane supply

Android Java package (pub.ditto.app -> spot.agora.app):
- Move android/app/src/main/java/pub/ditto/app/ ->
  android/app/src/main/java/spot/agora/app/ (4 files: MainActivity,
  DittoNotificationPlugin, NostrPoller, NotificationRelayService)
- Update package declarations to match the new Android namespace
  (was a hard build failure with namespace = spot.agora.app)
- Update proguard -keep rule
- Update NotificationRelayService ACTION_FETCH intent string
  pub.ditto.app.ACTION_FETCH -> spot.agora.app.ACTION_FETCH

Fastlane (pub.ditto.app -> spot.agora.app):
- Appfile, Matchfile, Fastfile provisioning profile specifiers.
  Matchfile still points at Soapbox's certificates git repo; a new
  match repo with certs for spot.agora.app is required before iOS CI
  signing works.

IPA artifact name (Ditto.ipa -> Agora.ipa):
- Fastfile output_name and matching CI artifact paths
- .gitlab-ci.yml: artifacts/Ditto.ipa references and the GitLab
  Generic Packages path from /packages/generic/ditto/ ->
  /packages/generic/agora/ (matches how APK/AAB are already
  published). Existing release artifacts at the old path remain
  reachable; new releases land at the new path.

Release-notes script fallback (Ditto vX.Y.Z -> Agora vX.Y.Z):
- scripts/extract-release-notes.mjs fallback used as the App Store /
  Play Store 'What's New' blurb when a changelog section has no
  summary.

manifest.webmanifest:
- Update related_applications Play Store entry to spot.agora.app.
- Remove the iTunes related_applications entry that pointed at
  the existing Ditto App Store listing; not applicable to Agora
  until Agora has its own listing.

Capacitor sync incidentals:
- npm run cap:sync picked up @capacitor/barcode-scanner registration
  that had been missed in a prior plugin install
  (android/app/capacitor.build.gradle, capacitor.settings.gradle,
  ios/App/CapApp-SPM/Package.swift).

Intentionally NOT touched:
- ditto.json filename, DittoConfigSchema, DittoConfig, and JSDoc
  references to ditto.json. The config-system shape is shared with
  upstream Ditto by design.
- relay.ditto.pub, blossom.ditto.pub, ditto.pub/api/* and other
  Ditto-stack services Agora actively consumes.
- The DittoNotificationPlugin Android/iOS class name, the
  DittoNotification JS bridge name, ditto_notification_config
  SharedPreferences keys, ic_stat_ditto drawables, and the
  DittoBridgeViewController. Renaming requires a coordinated
  JS-side rename plus a SharedPreferences migration or existing
  users on the Ditto fork lose their notification config on upgrade.
- Ditto references in skill docs, NIP.md kind comments, README, and
  zapstore.yaml attribution \u2014 those correctly describe the upstream
  Ditto project that Agora forked from.

Follow-ups required before CI succeeds end-to-end (out of scope here):
- Stand up a new fastlane match git repo containing certs +
  provisioning profiles for spot.agora.app, or update Matchfile
  git_url to point at it.
- Register spot.agora.app in App Store Connect for team GZLTTH5DLM
  and create a new App Store listing.
- Create a new Google Play Console listing for spot.agora.app
  (package name is immutable per app on Play; the existing
  pub.agora.app listing cannot be reused).
- Re-publish to Zapstore under spot.agora.app so the URLs in
  helpContent.ts resolve.
2026-05-17 18:42:46 -05:00
Alex Gleason aacfb66e2c Restore MobileBottomNav across all screen sizes
The FundraiserLayout overhaul dropped the bottom nav along with the
rest of the Twitter-style chrome. Bring it back and unhide it above
the 900px sidebar breakpoint so the Search / Communities / Feed /
Notifications / World row is available on every viewport.

- Mount <MobileBottomNav /> in FundraiserLayoutInner, outside the
  flex column so its fixed positioning behaves normally.
- Drop the 'sidebar:hidden' class on the nav element.
- Pad the layout root by --bottom-nav-height + safe-area-inset-bottom
  so the SiteFooter still clears the fixed bar.

Regression-of: 704cb42e
2026-05-17 18:24:34 -05:00
Alex Gleason a1be35f1f2 Cap center column width app-wide in FundraiserLayout
The layout outlet had no max-width, so pages without their own `max-w-*`
wrapper (e.g. /help, the home feed) stretched edge-to-edge on widescreen
monitors. Add a default `max-w-3xl` cap on the center column and wire
the existing `noMaxWidth` and `wrapperClassName` LayoutOptions through,
so pages that need wider canvases keep working — CampaignsPage,
CampaignDetailPage, CreateCampaignPage, EventDashboardPage, and WorldPage
already opt out via `noMaxWidth: true` or the `fullBleed` preset.
2026-05-17 18:10:58 -05:00
Alex Gleason fe5a622998 Merge branch 'main' of gitlab.com:soapbox-pub/agora 2026-05-17 17:41:02 -05:00
Alex Gleason 0f1103a607 Reframe homepage hero around activist funding mission 2026-05-17 17:32:09 -05:00
Chad Curtis 8975d762ef Switch default AI model to google/gemma-4-26b 2026-05-17 17:31:13 -05:00
Alex Gleason 704cb42e99 Replace MainLayout with a top-nav-only FundraiserLayout
The previous overhaul left the campaigns content nested inside the
Twitter-style three-column MainLayout (LeftSidebar + 600-px center
column + WidgetSidebar + mobile FAB + mobile bottom nav). It looked
like a Nostr client that happened to render campaign cards instead of
a fundraising site.

This commit takes the chrome down to studs:

- New FundraiserLayout: a sticky GoFundMe-style TopNav with logo,
  Discover / Start a campaign / About links, the existing LoginArea
  on the right (so the avatar dropdown / Log in & Sign up buttons all
  keep working unchanged), and a primary "Start a campaign" pill.
  Mobile collapses to a hamburger drawer with the same items plus
  quick shortcuts to Wallet / Bitcoin / Notifications / Profile /
  Settings for logged-in users.
- One full-width content area below the nav and a slim site footer.
  No LeftSidebar, no WidgetSidebar, no FAB, no MobileTopBar/BottomNav.
- The old layout still provides LayoutStoreContext / DrawerContext /
  CenterColumnContext / NavHiddenContext so every page that calls
  useLayoutOptions(...) keeps mounting cleanly. FAB / sidebar /
  scroll-direction options are simply ignored.

Routing changes:

- / now renders CampaignsPage directly (instead of dispatching
  through a configurable HomePage). /campaigns redirects to /.
- The orphaned HomePage.tsx is removed.

Campaign pages were calibrated for the old 600-px center column.
Re-flowed them to take advantage of the full canvas:

- Hero copy is recentred under max-w-7xl with GoFundMe-style language
  ("Where successful fundraisers start.").
- Campaign grid grows to four columns on xl screens.
- CampaignDetailPage drops its local sticky sub-header (redundant
  under the global TopNav) and the donation rail re-anchors to the
  new nav height.
- CreateCampaignPage drops its sticky sub-header and reads as a
  proper landing form.

The legacy MainLayout / LeftSidebar / WidgetSidebar / MobileTopBar /
MobileBottomNav / MobileDrawer / FloatingComposeButton components
remain on disk but are no longer mounted; they tree-shake out of the
production bundle.
2026-05-17 16:58:58 -05:00
Alex Gleason 1db8b4d5b0 Add fundraising campaigns as the new home surface
Pivot the homepage from a Twitter-style social feed to a GoFundMe-style
fundraising hub. Introduces a new addressable kind 30223 "Campaign" that
carries the marketing-style metadata (title, summary, cover image, story,
category, goal, deadline, location) plus a list of recipient pubkeys with
optional split weights. Documented in NIP.md alongside the kind 8333
onchain-zap spec it builds on.

Donations are sent as a single multi-output Bitcoin transaction (one
output per recipient, derived Taproot addresses) using the existing
buildUnsignedMultiOutputPsbt + useBitcoinSigner infrastructure that
backs community on-chain zaps. After broadcast, the client publishes
one kind 8333 receipt per recipient with the campaign's `a` coordinate
so the donation aggregates into the campaign's totals.

UI surfaces:

- /campaigns is now the default homePage. Hero, two featured slots
  (placeholders in src/lib/featuredCampaigns.ts), then a grid of
  user-submitted campaigns.
- /campaigns/new is a full create form with cover upload, slug
  collision check, recipient builder with per-row weights, and
  preset/custom donation-amount UX.
- naddr1 identifiers for kind 30223 route to CampaignDetailPage via
  NIP19Page (full story rendered through the existing ArticleContent
  markdown component, plus a sticky donate rail with progress).
- DonateDialog presets are tuned for on-chain amounts (10K-1M sats)
  with a dust-aware minimum guard derived from the split math.
- Fundraisers sidebar item with a HandHeart icon.

Kept the existing social-feed pages addressable from the sidebar; the
overhaul is scoped to the home/landing experience rather than removing
the underlying Nostr features.
2026-05-17 16:48:06 -05:00
Alex Gleason b62da321f7 Restore Messages sidebar entry, add WhiteNoise logo to /messages page
- Re-add the Messages item to the left sidebar with its previous
  MessageSquareMore lucide icon. Drop requiresAuth so logged-out users
  also see the entry — the page is a static recommendation.
- Restore 'messages' to the default sidebarOrder in App.tsx.
- Add WhiteNoiseIcon (the logomark from whitenoise.chat, recolored to
  currentColor so it adapts to theme) and use it on the /messages
  install-CTA card in place of the generic Lock glyph.
2026-05-17 14:41:34 -05:00
Alex Gleason c5b929187a Remove Nostr direct messaging feature
The @samthomson/nostr-messaging library opens fresh NRelay1 sockets per
participant per relay outside the shared NPool, fanning out to every
conversation partner's NIP-65 + NIP-17 inbox relays plus all
discoveryRelays in hybrid mode. In practice this drives connection counts
to several hundred relays per session.

Rather than band-aid the fan-out, drop the feature entirely and point
users to White Noise for end-to-end encrypted Nostr chat.

- Replace /messages with a 'Install White Noise' CTA card (route kept)
- Delete MessagingSettingsPage, DMProviderWrapper, messaging-intro.png
- Remove DMProvider wrapper and PROTOCOL_MODE config from App.tsx
- Drop messaging config from AppConfig, AppConfigSchema,
  EncryptedSettingsSchema, EncryptedSettings, and the NostrSync /
  useInitialSync sync paths
- Remove messages sidebar entry, default sidebarOrder slot, and
  SettingsPage messaging card
- Uninstall @samthomson/nostr-messaging and drop its tailwind content
  glob and vitest deps.inline entry
- Update copy in PrivacyPolicy, AdvancedSettings delete-account warning,
  ProfileSettings nsec warning, RequestToVanishDialog deletion checklist,
  MainLayout comment, and NIP.md
- Leave kind 4 rendering (EncryptedMessageContent) intact so DM events
  authored elsewhere still display in feeds and quote embeds
2026-05-17 14:37:49 -05:00
Alex Gleason 119307d13b Auto-reload open tabs on SW activate so returning users see fresh build immediately
Regression-of: 5b8d2d5c

The previous SW eviction commit wiped caches and called clients.claim()
on activate, but that only changes which SW handles future fetches — it
does not re-render a tab that already finished loading the stale bundle.
In practice, returning users had to manually close and reopen the tab
before seeing the new build.

Fix: after clients.claim(), iterate self.clients.matchAll({ type: 'window' })
and call client.navigate(client.url) on each one. Since this SW has no
fetch handler, the navigation falls through to the network and the tab
re-renders against the fresh index.html + hashed bundle.

Caveats:
- Users mid-interaction (typing a post, scrolling) lose their unsaved
  state. Acceptable trade — the alternative is they stay on a broken
  cached bundle indefinitely.
- Fires exactly once per user (only on the install -> activate transition
  for a byte-different /sw.js). No reload loop.

Also corrected the misleading comment on the main.tsx registration: that
registration is forward-looking insurance for future cache busts, not the
mechanism that evicts the old SW. The browser's own SW update check is
what re-fetches /sw.js out-of-band; our in-page JS never runs on a tab
the old precache SW is controlling.
2026-05-17 14:05:20 -05:00
Alex Gleason 5b8d2d5c06 Evict stale precache service worker from old Agora deployment
A previous version of Agora deployed at agora.spot shipped a precaching
service worker that is still controlling returning browsers and serving
them stale HTML/JS — they never see new deploys.

The fix has three parts:

1. public/sw.js — on activate, delete every Cache Storage entry the old
   SW left behind. This SW has no fetch handler, so once it takes over
   nothing re-populates the cache.

2. src/main.tsx — register /sw.js unconditionally on every web page load.
   Previously only usePushNotifications registered it, which meant users
   who never visited NotificationSettings stayed pinned to the old SW
   forever. Native (Capacitor) skips this — there is no stale SW on the
   filesystem origin.

3. .gitlab-ci.yml — the deploy-web rsync was excluding sw.js from the
   first pass and never re-adding it to the second pass, so deploys
   silently never updated sw.js. Now it ships in the second pass
   alongside index.html (after hashed assets land).
2026-05-17 13:48:24 -05:00
lemon cd90cbce0e Retry profile banners as blob URLs 2026-05-17 10:59:31 -07:00
lemon 650ba868b3 Fix profile banner image fallback 2026-05-17 10:34:52 -07:00
lemon 256e22f0bd Default Bitcoin community zaps to 1000 sats 2026-05-17 01:20:08 -07:00
lemon 3926a1c886 Initialize Bitcoin ECC before address derivation 2026-05-17 01:16:56 -07:00
lemon b87b70fa72 Polish Bitcoin dust limit handling 2026-05-17 01:13:53 -07:00
lemon 5f891bbce4 Add community Bitcoin zap action 2026-05-17 01:11:18 -07:00