Add /sponsors, a landing-style page for companies that want to partner
with the platform. Lays out the three ways to get involved: an
unrestricted gift to the Agora Seed Fund (BTC or USD), a matching pool
for an individual campaign, a featured campaign, or a curated list of
campaigns (e.g. political prisoners or women's sovereignty in Africa),
and promoting Agora donations to a customer base as a philanthropic
initiative.
Modeled on AboutPage's section recipe (dark hero, alternating section
backgrounds, hand-rolled cards) and routed under the wide
FundraiserLayout above the /:nip19 catch-all. Adds a Sponsors link to
the site footer. English copy only for now; other locales fall back to
English until the copy settles.
ProfileCard's namePlaceholder default was a hardcoded 'Your name'
literal used by the profile/org editor onboarding steps. Default it to
the new profile.namePlaceholder key (translated across all locales) and
resolve it via t() in the component body; callers that pass their own
placeholder (e.g. the campaign name step) are unaffected.
Keys added to en.json over time (notably the verifier create-account
onboarding flow, the walletDoubleTweak recovery namespace, and the
campaign-wizard tag step) were never propagated to the other locales,
so they silently fell back to English at runtime — which is why the
verify-campaign onboarding steps still rendered in English under e.g.
Chinese despite the components calling t().
Translate every key present in en.json but missing from each locale
(84-157 keys each, 1553 total) so all sixteen locales are now complete
with zero English fallbacks. Placeholders, technical tokens, and the
Agora name are preserved verbatim; plural categories follow each
language's grammar.
The nsec reveal input in the onboarding 'Save your key' step carried a
hardcoded English aria-label. Route it through
onboarding.secure.secretKeyAriaLabel and translate across all locales.
This was the last untranslated user-facing string in the verifier
create-account flow.
Drop the 'Welcome to Agora' header and the Nostr-flavored subtext from
the welcome step. The Agora bolt mark and wordmark are now the focal
point — laid out inline with the same tight spacing and Bebas display
recipe as the top-nav wordmark, just larger — and the wordmark doubles
as the dialog's accessible title.
Retitle the two buttons to 'Create new account' / 'Log in with
existing' (no Nostr phrasing) and route them through new auth.* keys,
translated across all sixteen locales.
The crop modal used in the verifier 'set up your organization'
onboarding step had hardcoded English controls (Reset, the
reposition/zoom hint, Cancel, Processing, Apply Crop, and the default
title). Route them through a new top-level imageCrop namespace and
add translations for all sixteen locales.
The VerifyTutorial demo card's 'of … goal' label was also hardcoded;
reuse the existing campaignsDetail.ofGoal key.
Swap the top-nav language trigger from the Languages glyph to Globe — the
more universal "change language" affordance.
Stop flipping individual RTL rows: the previous per-item dir="rtl" pushed
Arabic / Persian / Pashto names to the far edge while LTR rows hugged the
left, leaving a ragged, jarring column. Force every row LTR-aligned
(text-left) so the selected dot and the names share one clean left edge,
while each name keeps its own lang attribute so the browser still shapes
Arabic/Persian/Khmer/CJK glyphs correctly.
A visitor who lands in a language they can't read had no discoverable way
to change it: on desktop the only chrome was the search icon + login, and
the picker was buried at /settings/language (itself only reachable through
the mobile hamburger's "Settings" — a word they may not be able to read).
Add a globe/languages icon to the top-nav right cluster, visible to
everyone regardless of login state. It opens a dropdown listing every
supported language by its own native name (each row rendered in its own
lang/dir), so the right entry is recognizable no matter the current UI
language. Selecting one switches in place via changeAppLanguage without
navigating away, so scroll position and in-progress form state survive.
The /settings/language page remains the canonical deep-link.
New key nav.language added to en.json and all 15 other locales (reusing
each locale's existing language.title translation).
Wire the branch-new hardcoded UI strings through i18n:
- ProfileCard's shared image-edit menu ("Upload file", "Paste URL",
"Remove") and the website-slot placeholder now use t(); the component
gains useTranslation.
- The campaign profile step's name placeholder regressed from a
translated string to a literal "Campaign title" when it switched to the
shared identity card; restore it via a new
onboarding.profile.campaignNamePlaceholder key.
New keys added to en.json and translated into all 15 other locales:
profile.imageMenu.{upload,pasteUrl,remove}, profile.websitePlaceholder,
and onboarding.profile.campaignNamePlaceholder.
The plural keys flagged by a first pass (campaignsCount, seeAllCampaigns,
followersTitle, profile.verified.count, etc.) already resolve via
i18next's _one/_other suffixes and needed no change. Pre-existing
untranslated strings that predate this branch (e.g. ImageCropDialog) were
left untouched.
Regression-of: a7a9ed06
These onboarding/campaign surfaces accumulated several copies of the same
logic over the iterative work. Fold them into shared modules:
- Kind-0 publishing: the campaign-creator profile step reimplemented the same
read-modify-write publish that usePublishOrgProfile already had (fetch fresh
-> parse -> merge name/picture/banner/about -> publish with prev), plus its
own parseProfileMetadata. Route it through the shared hook.
- Draft type: the identical ProfileIdentityDraft and OrgProfileDraft
interfaces become one ProfileDraft in src/lib/profileDraft.ts, alongside
parseProfileMetadata/mergeProfileDraft helpers. The hook no longer imports
its draft type from a component module.
- Auto-grow textarea: the campaign story and verifier bio fields shared an
identical borderless textarea with copy-pasted height-resize handlers and
styling. Extract an AutoGrowTextarea component plus an autoGrowTextarea
helper (also reused by ProfileCard's editable fields).
The action now sits in a compact row beside the avatar, so the verbose
'Edit profile' label is unnecessary. Shorten profile.header.editProfile
to just 'Edit' across all locales.
On sm/md the Edit Profile / QR / more (or Follow / Donate) buttons sat in
a full-width row below the bio. Put them on the same row as the avatar,
bottom-right, Twitter/X-style: ProfileIdentityHeader gains a hideActionBar
prop, ActionBar is exported with an align prop, and the mobile layout
renders the avatar and a right-justified ActionBar (align="end") together.
With align="end" the Edit Profile button sizes to its label instead of
stretching full-width.
The profile avatar was size-28 below md and size-32 at md+, so it shrank
on small screens. Pin it to size-32 everywhere and drop the matching
responsive overhang (-mt-20), status-bubble offset, and skeleton
placeholders to the larger variant so the layout stays aligned.
On /verify the VerifyTutorial rendered without the stacked prop, so its
DemoStage stayed capped at max-w-md inside the much wider max-w-2xl
verifier panel, leaving the animated campaign card looking too small for
its parent. Pass stacked so the demo spans the full container width.
Regression-of: 96c7acfc
The recent profile banner/sticky-rail experiments (the 3:1 cover crop,
desktop banner isolation, square corners, and the various sticky/pinned
identity-rail attempts) were reverted off this branch; they added
complexity without landing cleanly.
Re-apply the two wanted tweaks on the clean baseline instead:
- Banner height is now h-48 at every breakpoint rather than shrinking to
h-36 below md, so it no longer gets needlessly shorter on small screens.
- The avatar crop dialog draws a dashed circle inscribed in the existing
(square) crop boundary, tracking the cropper's actual rendered crop size
via onCropSizeChange so it stays flush as the boundary resizes. Wired in
for the avatar (picture) crops in ProfileSettings and the onboarding
ProfileIdentityEditor; banner/cover crops are unaffected.
The centered (non-stacked) demo card was capped at max-w-sm, which looked
undersized inside the wide card on the /verify page. Bump it to max-w-md so
it better fills the parent. The onboarding flow uses the stacked/full-width
path and is unaffected.
Swap the FormSection-wrapped mono textarea on "Tell your story" for the
borderless, auto-growing muted textarea used by the organization bio
step, so the two long-form surfaces look the same.
Fold the standalone "Add a banner" step into the "Name your campaign"
step (title required, banner optional), removing one wizard step.
Adjust the launch-shortcut step index and block advancing while the
banner is still uploading.
Add showBanner and a bioField "none" option to ProfileCard (threaded
through ProfileIdentityEditor), and drop the banner and bio from the
campaign creator's "Put a face to your campaign" step so it asks only
for an avatar and name.
Extract the verifier identity step's ProfileCard + crop/upload/paste/
remove machinery into a shared ProfileIdentityEditor, parameterized by
bioField ('website' for organizations, 'about' for campaigners) and an
aboutPlaceholder. VerifierIdentityStep now wraps it for the org flow.
The campaign creator's "Put a face to your campaign" wizard step now
renders the same banner + avatar + name + bio card instead of the old
plain name/avatar/collapsible-about form, with "A little about you…" as
the bio placeholder. The wizard already supplies the back arrow and
progress bar, and the published kind-0 now carries the banner too.