Rebrand Agora to Eranos and strip the non-Grin rails. Add Grin donations:
a GoblinPay client + GrinPayDialog, on-chain payment-proof verification
(receiver-sig + kernel-on-chain + dedupe), and a proof-verified campaign
tally (kind 3414). Shift the brand from orange to gold. 118 tests green.
Remove Profile, Search, About, and Get the app items from the account
switcher dropdown. Show the active profile at the top of the profile
list (linking to your profile instead of switching), and reorder the
remaining items to Wallet, Notifications, My Dashboard, Settings.
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.
useLoggedInAccounts runs its own kind-0 query with a hard 1.5s relay
timeout. When that comes back empty (slow relay, cold pool), every login
gets metadata: {}, the AccountSwitcher avatar drops through to the
AvatarFallback, and genUserName() returns the literal 'Anonymous' — so a
logged-in user sees an 'A' placeholder instead of their picture, even
though the rest of the app (which uses useAuthor) shows the right
profile.
Layer useAuthor on top of the existing currentUser. useAuthor is seeded
from IndexedDB and shared with every other consumer of the user's
kind-0, so the avatar now picks up cached metadata immediately and stops
showing the 'A' fallback on logged-in sessions.
- Rename MySquarePage -> MyDashboardPage (file, component, default export)
- Change route from /my-square to /my-dashboard
- Update user-facing copy: SEO title, logged-out heading, JSDoc
- Add 'My Dashboard' link to AccountSwitcher dropdown
- Add 'My Dashboard' entry to TopNav mobile/profile menu
- Use LayoutDashboard icon to distinguish from the existing Dashboard
- Existing /dashboard route and EventDashboardPage are untouched
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.
Previously getDisplayName() and ~50 inline sites only consulted
metadata.name, ignoring display_name. A handful of other sites used the
opposite priority (display_name || name), so the same user could render
under different names across the UI.
Standardize on `name || display_name || genUserName(pubkey)` in the
helper and at every call site, and widen two local inline metadata types
in RightSidebar and SearchPage that did not declare display_name.
- avatar.tsx: consolidate to single isEmojiShape boolean, fix AvatarFallback using same logic as Avatar
- avatarShape.ts: accept NostrMetadata directly in getAvatarShape (no cast needed)
- Remove 'as Record<string, unknown>' casts from all ~50 call sites
- Replace 'circle' magic string with empty string in form defaults and parseShape
- Use isValidAvatarShape instead of string comparison in save logic
- ProfileCard: extract IIFE overlay style into useMemo, use isEmojiShape throughout
- New 'shape' property on kind 0 profile metadata with 7 predefined shapes:
circle, triangle, inverted-triangle, hexagon, star, inverted-star, hexagram
- Avatar component updated with clip-path support for non-circle shapes
- Visual shape picker added to both EditProfileForm and ProfileSettings
- Shape applied across all 49 avatar render sites in the app
- New UserAvatar wrapper component for future simplified avatar rendering
- Unknown shape values gracefully fall back to circle (forward compatible)
Replace the '?' avatar fallback and 'Anonymous' name placeholder with skeleton
loaders in ComposeBox and LeftSidebar during initial profile load. Expose
isLoading from useCurrentUser and useLoggedInAccounts to support this.
The error occurred because the AccountSwitcher component was returning null while the DropdownMenu was still mounted and open. This caused React to throw a minified error #300 about invalid elements.
The fix ensures the dropdown menu is closed before removing the login, preventing the component from being unmounted while still rendering its dropdown content.
Changes:
- Added controlled state for dropdown menu (isOpen/setIsOpen)
- Created handleLogout function that closes dropdown before removing login
- Used setTimeout to ensure dropdown closes before state update
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>