aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-02 23:48:31 -0700
committerAdam Soltys <[email protected]>2026-08-02 23:48:31 -0700
commit4b191f4eb8ad519e982f6667f2b540cdd77e539a (patch)
tree07c409c93e0e0d99df249ad40bf95da67ed99eb6 /src/features
parent02d60eb824cad0b8c09cb5685a6c2dbe02e16c4f (diff)
downloadcoinosv3-4b191f4eb8ad519e982f6667f2b540cdd77e539a.tar.gz
coinosv3-4b191f4eb8ad519e982f6667f2b540cdd77e539a.zip
First-run onboarding: a five-step wizard and a coach-mark tour
Fresh devices land in a full-screen flow instead of the unlock screen: welcome → "do you have a nostr account?" (sign in brings wallet, name, and profile along; No mints a wallet on the spot) → claim a username (the background npub-default only surfaces a Keep button; a custom claim — or a restored login's recovered name — advances by itself) → pick a punk or upload a photo (nostr.build), published as the kind-0 name+picture → confetti. "Enter coinos" drops into a three-stop tour: the avatar menu, the Spending/Savings toggle, and the tabs with the swipe tip — each highlighted by a measured gold ring over a dimmed page. One time only (localStorage flag), with an "I already have a wallet" escape to the classic screen. New seams: names.namesClaimForm, messages.publishProfile. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js22
-rw-r--r--src/features/names.js2
2 files changed, 24 insertions, 0 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 6ff94a4..46f7399 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -1356,6 +1356,28 @@ export function messagesFeature(ctx) {
// Anyone (ark's history, other features) can open a profile or render a
// small clickable identity chip.
showProfile(pk) { openProfile(pk); return true; },
+ // Publish (merge) kind-0 fields for the current identity — the onboarding
+ // wizard sets name + picture through this.
+ async publishProfile(fields) {
+ const id = await identity();
+ if (!id) throw new Error(t('msgNoIdentity'));
+ const evs = await queryOn([...new Set([...PROFILE_RELAYS, ...DM_RELAYS])], { kinds: [0], authors: [id.pubkey] }, 3000);
+ const newest = evs.sort((a, b) => b.created_at - a.created_at)[0];
+ let base = {};
+ try { base = newest ? JSON.parse(newest.content) : {}; } catch {}
+ const merged = { ...base };
+ for (const [k, v] of Object.entries(fields || {})) {
+ if (v) merged[k] = v;
+ }
+ if (merged.name) merged.display_name = merged.name;
+ const partial = { kind: 0, content: JSON.stringify(merged), tags: [], created_at: Math.floor(Date.now() / 1000) };
+ const evt = id.signer instanceof Uint8Array ? finalizeEvent(partial, id.signer) : await id.signer.signEvent(partial);
+ const ok = await publishOn([...new Set([...PROFILE_RELAYS, ...DM_RELAYS])], evt);
+ if (!ok) throw new Error(t('msgSendFailed'));
+ fullProfiles.set(id.pubkey, merged);
+ profiles.set(id.pubkey, { name: merged.name || null, picture: merged.picture || null, t: Date.now() });
+ return true;
+ },
profileChip(pk) {
return h('span', {
class: 'zap-chip',
diff --git a/src/features/names.js b/src/features/names.js
index 3952214..a30038e 100644
--- a/src/features/names.js
+++ b/src/features/names.js
@@ -310,6 +310,8 @@ export function namesFeature(ctx) {
namesAdoptIdentity(signer, npub) { return adoptIdentity(signer, npub); },
// the claimed payment address, for anyone prefilling a lightning address
namesAddress() { const st = load(); return st.name ? `${st.name}@${st.domain || DOMAIN}` : null; },
+ // the onboarding wizard renders the same claim form on its username step
+ namesClaimForm() { return claimForm(true); },
stop() { clearTimeout(retryTimer); clearTimeout(deadline); deadline = null; },
receiveModes() {
if (!available()) return [];