From 48bfdec578a47c611d5bb0093f26a3907d98cb0f Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 31 May 2026 15:15:19 +0200 Subject: [PATCH] implement onboarding --- package-lock.json | 6 - package.json | 1 - .../AccountManager/GenerateNewAccount.tsx | 85 -------------- src/components/AccountManager/index.tsx | 83 ++++++------- .../PrivateKeyRecoverySetting/index.tsx | 92 +++++++++++++++ src/components/Settings/SettingsMenuBody.tsx | 41 +------ src/i18n/locales/en.ts | 19 ++- src/lib/new-user-template.test.ts | 69 +++++++++++ src/lib/new-user-template.ts | 109 ++++++++++++++++++ .../secondary/CacheSettingsPage/index.tsx | 2 + src/providers/NostrProvider/index.tsx | 81 +++++++++---- vite.config.ts | 4 - 12 files changed, 388 insertions(+), 204 deletions(-) delete mode 100644 src/components/AccountManager/GenerateNewAccount.tsx create mode 100644 src/components/PrivateKeyRecoverySetting/index.tsx create mode 100644 src/lib/new-user-template.test.ts create mode 100644 src/lib/new-user-template.ts diff --git a/package-lock.json b/package-lock.json index f654018a..26a4a5e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,7 +77,6 @@ "lucide-react": "^0.469.0", "marked": "^17.0.5", "nostr-tools": "^2.17.0", - "nstart-modal": "^2.0.0", "path-to-regexp": "^8.3.0", "prosemirror-state": "^1.4.3", "qr-code-styling": "^1.9.2", @@ -12895,11 +12894,6 @@ "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", "license": "MIT" }, - "node_modules/nstart-modal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nstart-modal/-/nstart-modal-2.1.0.tgz", - "integrity": "sha512-PolShYoWK07yJJbINtUn/IoOI5B0lmXRG9zOY9dirKKVjmMWFKjYLlafunNOl94EGcEndzAPWJAFDCt8flYRqg==" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", diff --git a/package.json b/package.json index f17fda16..199045e7 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,6 @@ "lucide-react": "^0.469.0", "marked": "^17.0.5", "nostr-tools": "^2.17.0", - "nstart-modal": "^2.0.0", "path-to-regexp": "^8.3.0", "prosemirror-state": "^1.4.3", "qr-code-styling": "^1.9.2", diff --git a/src/components/AccountManager/GenerateNewAccount.tsx b/src/components/AccountManager/GenerateNewAccount.tsx deleted file mode 100644 index 313a781a..00000000 --- a/src/components/AccountManager/GenerateNewAccount.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { useNostr } from '@/providers/NostrProvider' -import { Check, Copy, RefreshCcw } from 'lucide-react' -import { generateSecretKey } from 'nostr-tools' -import { nsecEncode } from 'nostr-tools/nip19' -import { useState } from 'react' -import { useTranslation } from 'react-i18next' - -export default function GenerateNewAccount({ - back, - onLoginSuccess -}: { - back: () => void - onLoginSuccess: () => void -}) { - const { t } = useTranslation() - const { nsecLogin } = useNostr() - const [nsec, setNsec] = useState(generateNsec()) - const [copied, setCopied] = useState(false) - const [password, setPassword] = useState('') - - const handleLogin = () => { - nsecLogin(nsec, password, true).then(() => onLoginSuccess()) - } - - return ( -
{ - e.preventDefault() - handleLogin() - }} - > -
- {t( - 'This is a private key. Do not share it with anyone. Keep it safe and secure. You will not be able to recover it if you lose it.' - )} -
-
- -
- - - -
-
-
- - setPassword(e.target.value)} - /> -
-
- - -
-
- ) -} - -function generateNsec() { - const sk = generateSecretKey() - return nsecEncode(sk) -} diff --git a/src/components/AccountManager/index.tsx b/src/components/AccountManager/index.tsx index 155d0fd3..97217e92 100644 --- a/src/components/AccountManager/index.tsx +++ b/src/components/AccountManager/index.tsx @@ -1,17 +1,19 @@ import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' import { Separator } from '@/components/ui/separator' import { useNostr } from '@/providers/NostrProvider' -import { useTheme } from '@/providers/ThemeProvider' -import { NstartModal } from 'nstart-modal' +import { generateSecretKey } from 'nostr-tools' +import { nsecEncode } from 'nostr-tools/nip19' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from 'sonner' import AccountList from '../AccountList' -import GenerateNewAccount from './GenerateNewAccount' import NostrConnectLogin from './NostrConnectionLogin' import NpubLogin from './NpubLogin' import PrivateKeyLogin from './PrivateKeyLogin' -type TAccountManagerPage = 'nsec' | 'bunker' | 'generate' | 'npub' | null +type TAccountManagerPage = 'nsec' | 'bunker' | 'npub' | null export default function AccountManager({ close }: { close?: () => void }) { const [page, setPage] = useState(null) @@ -22,8 +24,6 @@ export default function AccountManager({ close }: { close?: () => void }) { setPage(null)} onLoginSuccess={() => close?.()} /> ) : page === 'bunker' ? ( setPage(null)} onLoginSuccess={() => close?.()} /> - ) : page === 'generate' ? ( - setPage(null)} onLoginSuccess={() => close?.()} /> ) : page === 'npub' ? ( setPage(null)} onLoginSuccess={() => close?.()} /> ) : ( @@ -40,9 +40,24 @@ function AccountManagerNav({ setPage: (page: TAccountManagerPage) => void close?: () => void }) { - const { t, i18n } = useTranslation() - const { themeSetting } = useTheme() - const { nip07Login, bunkerLogin, nsecLogin, ncryptsecLogin, accounts } = useNostr() + const { t } = useTranslation() + const { nip07Login, nsecLogin, accounts } = useNostr() + const [password, setPassword] = useState('') + const [signingUp, setSigningUp] = useState(false) + + const handleSignUp = async () => { + setSigningUp(true) + try { + const nsec = nsecEncode(generateSecretKey()) + await nsecLogin(nsec, password.trim() || undefined, true) + setPassword('') + close?.() + } catch (error) { + toast.error(t('Login failed') + ': ' + ((error as Error).message ?? String(error))) + } finally { + setSigningUp(false) + } + } return (
e.stopPropagation()} className="flex flex-col gap-8"> @@ -72,38 +87,24 @@ function AccountManagerNav({
{t("Don't have an account yet?")}
- -
{accounts.length > 0 && ( diff --git a/src/components/PrivateKeyRecoverySetting/index.tsx b/src/components/PrivateKeyRecoverySetting/index.tsx new file mode 100644 index 00000000..b7189606 --- /dev/null +++ b/src/components/PrivateKeyRecoverySetting/index.tsx @@ -0,0 +1,92 @@ +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { pubkeyToNpub } from '@/lib/pubkey' +import { useNostr } from '@/providers/NostrProvider' +import { Check, Copy, Eye, EyeOff, KeyRound } from 'lucide-react' +import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' + +export default function PrivateKeyRecoverySetting() { + const { t } = useTranslation() + const { pubkey, nsec, ncryptsec } = useNostr() + const [showKey, setShowKey] = useState(false) + const [copiedNpub, setCopiedNpub] = useState(false) + const [copiedKey, setCopiedKey] = useState(false) + + const npub = useMemo(() => (pubkey ? pubkeyToNpub(pubkey) : null), [pubkey]) + const recoverableKey = nsec ?? ncryptsec + const keyLabel = nsec ? 'nsec' : 'ncryptsec' + + if (!pubkey || !recoverableKey) { + return null + } + + const copyToClipboard = async (text: string, which: 'npub' | 'key') => { + await navigator.clipboard.writeText(text) + if (which === 'npub') { + setCopiedNpub(true) + setTimeout(() => setCopiedNpub(false), 2000) + } else { + setCopiedKey(true) + setTimeout(() => setCopiedKey(false), 2000) + } + } + + return ( +
+
+ +

{t('Private key recovery')}

+
+

+ {t( + 'Your private key is stored in this browser. Clearing cache does not remove your account, but losing this browser profile does. Back up your key somewhere safe.' + )} +

+ {ncryptsec && !nsec && ( +

+ {t( + 'This account uses an encrypted key (ncryptsec). You need your encryption password to sign in; the blob below is for backup only.' + )} +

+ )} +
+ +
+ + +
+
+
+ +
+ + +
+ {showKey && ( +
+

+ {t('Do not share this with anyone. Anyone with this key can control your account.')} +

+
{recoverableKey}
+
+ )} +
+
+ ) +} diff --git a/src/components/Settings/SettingsMenuBody.tsx b/src/components/Settings/SettingsMenuBody.tsx index 851c1ca9..588b2ee2 100644 --- a/src/components/Settings/SettingsMenuBody.tsx +++ b/src/components/Settings/SettingsMenuBody.tsx @@ -12,12 +12,9 @@ import { cn } from '@/lib/utils' import { useSmartSettingsNavigation } from '@/PageManager' import { useNostr } from '@/providers/NostrProvider' import { - Check, ChevronRight, - Copy, Database, Info, - KeyRound, PencilLine, Rss, Server, @@ -25,7 +22,7 @@ import { Users, Wallet } from 'lucide-react' -import { forwardRef, HTMLProps, useState } from 'react' +import { forwardRef, HTMLProps } from 'react' import { useTranslation } from 'react-i18next' /** @@ -34,10 +31,8 @@ import { useTranslation } from 'react-i18next' */ export default function SettingsMenuBody({ className }: { className?: string }) { const { t } = useTranslation() - const { pubkey, nsec, ncryptsec } = useNostr() + const { pubkey } = useNostr() const { navigateToSettings } = useSmartSettingsNavigation() - const [copiedNsec, setCopiedNsec] = useState(false) - const [copiedNcryptsec, setCopiedNcryptsec] = useState(false) return (
@@ -98,38 +93,6 @@ export default function SettingsMenuBody({ className }: { className?: string }) )} - {!!nsec && ( - { - navigator.clipboard.writeText(nsec) - setCopiedNsec(true) - setTimeout(() => setCopiedNsec(false), 2000) - }} - > -
- -
{t('Copy private key')} (nsec)
-
- {copiedNsec ? : } -
- )} - {!!ncryptsec && ( - { - navigator.clipboard.writeText(ncryptsec) - setCopiedNcryptsec(true) - setTimeout(() => setCopiedNcryptsec(false), 2000) - }} - > -
- -
{t('Copy private key')} (ncryptsec)
-
- {copiedNcryptsec ? : } -
- )}
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 4f019a8d..05cdc098 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -539,9 +539,22 @@ export default { 'read & write relays notice': 'The number of read and write servers should ideally be kept between 2 and 4.', "Don't have an account yet?": "Don't have an account yet?", - 'or simply generate a private key': 'or simply generate a private key', - 'This is a private key. Do not share it with anyone. Keep it safe and secure. You will not be able to recover it if you lose it.': - 'This is a private key. Do not share it with anyone. Keep it safe and secure. You will not be able to recover it if you lose it.', + 'Sign up creates a private key stored in this browser. Back it up anytime under Settings → Cache & offline storage.': + 'Sign up creates a private key stored in this browser. Back it up anytime under Settings → Cache & offline storage.', + 'Signing up…': 'Signing up…', + 'Account created — customize profile and relays in Settings.': + 'Account created — customize profile and relays in Settings.', + 'Private key recovery': 'Private key recovery', + 'Your private key is stored in this browser. Clearing cache does not remove your account, but losing this browser profile does. Back up your key somewhere safe.': + 'Your private key is stored in this browser. Clearing cache does not remove your account, but losing this browser profile does. Back up your key somewhere safe.', + 'This account uses an encrypted key (ncryptsec). You need your encryption password to sign in; the blob below is for backup only.': + 'This account uses an encrypted key (ncryptsec). You need your encryption password to sign in; the blob below is for backup only.', + 'Show key': 'Show key', + 'Hide key': 'Hide key', + 'Do not share this with anyone. Anyone with this key can control your account.': + 'Do not share this with anyone. Anyone with this key can control your account.', + 'Copy npub': 'Copy npub', + npub: 'npub', Edit: 'Edit', Save: 'Save', 'Display Name': 'Display Name', diff --git a/src/lib/new-user-template.test.ts b/src/lib/new-user-template.test.ts new file mode 100644 index 00000000..6ae38832 --- /dev/null +++ b/src/lib/new-user-template.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, it } from 'vitest' +import { kinds } from 'nostr-tools' +import { ExtendedKind, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants' +import { + NEW_USER_INTEREST_TOPICS, + buildNewUserTemplateDrafts, + newUserProfileDisplayName, + newUserProfileName, + newUserProfileSuffix +} from '@/lib/new-user-template' + +const TEST_PUBKEY = 'a'.repeat(63) + 'b' + +describe('newUserProfileSuffix', () => { + it('returns a number between 1000 and 9999', () => { + const suffix = newUserProfileSuffix(TEST_PUBKEY) + expect(suffix).toBeGreaterThanOrEqual(1000) + expect(suffix).toBeLessThanOrEqual(9999) + }) + + it('formats profile names with the suffix', () => { + const suffix = newUserProfileSuffix(TEST_PUBKEY) + expect(newUserProfileName(TEST_PUBKEY)).toBe(`ImwaldUser${suffix}`) + expect(newUserProfileDisplayName(TEST_PUBKEY)).toBe(`Imwald User ${suffix}`) + }) +}) + +describe('buildNewUserTemplateDrafts', () => { + const drafts = buildNewUserTemplateDrafts(TEST_PUBKEY) + + it('builds profile kind 0 with unique names', () => { + expect(drafts.profile.kind).toBe(kinds.Metadata) + const profile = JSON.parse(drafts.profile.content) + expect(profile.name).toBe(newUserProfileName(TEST_PUBKEY)) + expect(profile.display_name).toBe(newUserProfileDisplayName(TEST_PUBKEY)) + expect(profile.about).toContain('Imwald') + }) + + it('builds favorite relays kind 10012', () => { + expect(drafts.favoriteRelays.kind).toBe(ExtendedKind.FAVORITE_RELAYS) + expect(drafts.favoriteRelays.tags.filter((t) => t[0] === 'relay')).toHaveLength(2) + }) + + it('splits mailbox read and write relays', () => { + expect(drafts.relayList.kind).toBe(kinds.RelayList) + const readTags = drafts.relayList.tags.filter((t) => t[0] === 'r' && t[2] === 'read') + const writeTags = drafts.relayList.tags.filter((t) => t[0] === 'r' && t[2] === 'write') + expect(readTags).toHaveLength(FAST_READ_RELAY_URLS.length) + expect(writeTags).toHaveLength(FAST_WRITE_RELAY_URLS.length) + }) + + it('builds HTTP relay list kind 10243 with mercury', () => { + expect(drafts.httpRelayList.kind).toBe(ExtendedKind.HTTP_RELAY_LIST) + expect(drafts.httpRelayList.tags.some((t) => t[1]?.includes('mercury-relay.imwald.eu'))).toBe(true) + }) + + it('builds interest list with expected topics', () => { + expect(drafts.interestList.kind).toBe(10015) + const topics = drafts.interestList.tags.filter((t) => t[0] === 't').map((t) => t[1]) + expect(topics).toEqual([...NEW_USER_INTEREST_TOPICS]) + }) + + it('builds empty follow and mute lists', () => { + expect(drafts.followList.kind).toBe(kinds.Contacts) + expect(drafts.followList.tags).toHaveLength(0) + expect(drafts.muteList.kind).toBe(10000) + expect(drafts.muteList.tags).toHaveLength(0) + }) +}) diff --git a/src/lib/new-user-template.ts b/src/lib/new-user-template.ts new file mode 100644 index 00000000..b13b6af4 --- /dev/null +++ b/src/lib/new-user-template.ts @@ -0,0 +1,109 @@ +import { + DEFAULT_FAVORITE_RELAYS, + FAST_READ_RELAY_URLS, + FAST_WRITE_RELAY_URLS +} from '@/constants' +import { + createFavoriteRelaysDraftEvent, + createFollowListDraftEvent, + createHttpRelayListDraftEvent, + createInterestListDraftEvent, + createMuteListDraftEvent, + createProfileDraftEvent, + createRelayListDraftEvent +} from '@/lib/draft-event' +import { TDraftEvent, TMailboxRelay } from '@/types' + +export const NEW_USER_HTTP_RELAY_URL = 'https://mercury-relay.imwald.eu/' + +export const NEW_USER_INTEREST_TOPICS = [ + 'art', + 'music', + 'news', + 'foodstr', + 'coffeechain', + 'travel', + 'grownostr', + 'plebchain' +] as const + +export const NEW_USER_PROFILE_ABOUT = 'New on Nostr via Imwald. Edit your profile in Settings.' + +/** Stable 4-digit suffix (1000–9999) from pubkey hex. */ +export function newUserProfileSuffix(pubkey: string): number { + const hex = pubkey.trim().toLowerCase() + if (!/^[0-9a-f]{64}$/.test(hex)) { + return 1000 + } + return (parseInt(hex.slice(-4), 16) % 9000) + 1000 +} + +export function newUserProfileName(pubkey: string): string { + return `ImwaldUser${newUserProfileSuffix(pubkey)}` +} + +export function newUserProfileDisplayName(pubkey: string): string { + return `Imwald User ${newUserProfileSuffix(pubkey)}` +} + +export function buildNewUserMailboxRelays(): TMailboxRelay[] { + return [ + ...FAST_READ_RELAY_URLS.map((url) => ({ url, scope: 'read' as const })), + ...FAST_WRITE_RELAY_URLS.map((url) => ({ url, scope: 'write' as const })) + ] +} + +export function buildNewUserProfileDraft(pubkey: string): TDraftEvent { + const content = JSON.stringify({ + name: newUserProfileName(pubkey), + display_name: newUserProfileDisplayName(pubkey), + about: NEW_USER_PROFILE_ABOUT + }) + return createProfileDraftEvent(content) +} + +export function buildNewUserFavoriteRelaysDraft(): TDraftEvent { + return createFavoriteRelaysDraftEvent([...DEFAULT_FAVORITE_RELAYS], []) +} + +export function buildNewUserRelayListDraft(): TDraftEvent { + return createRelayListDraftEvent(buildNewUserMailboxRelays()) +} + +export function buildNewUserHttpRelayListDraft(): TDraftEvent { + return createHttpRelayListDraftEvent([{ url: NEW_USER_HTTP_RELAY_URL, scope: 'both' }]) +} + +export function buildNewUserInterestListDraft(): TDraftEvent { + return createInterestListDraftEvent([...NEW_USER_INTEREST_TOPICS]) +} + +export function buildNewUserFollowListDraft(): TDraftEvent { + return createFollowListDraftEvent([]) +} + +export function buildNewUserMuteListDraft(): TDraftEvent { + return createMuteListDraftEvent([]) +} + +export type TNewUserTemplateDrafts = { + profile: TDraftEvent + favoriteRelays: TDraftEvent + relayList: TDraftEvent + httpRelayList: TDraftEvent + interestList: TDraftEvent + followList: TDraftEvent + muteList: TDraftEvent +} + +export function buildNewUserTemplateDrafts(pubkey: string): TNewUserTemplateDrafts { + return { + profile: buildNewUserProfileDraft(pubkey), + favoriteRelays: buildNewUserFavoriteRelaysDraft(), + relayList: buildNewUserRelayListDraft(), + httpRelayList: buildNewUserHttpRelayListDraft(), + interestList: buildNewUserInterestListDraft(), + followList: buildNewUserFollowListDraft(), + muteList: buildNewUserMuteListDraft() + } +} diff --git a/src/pages/secondary/CacheSettingsPage/index.tsx b/src/pages/secondary/CacheSettingsPage/index.tsx index d219791a..d71d628c 100644 --- a/src/pages/secondary/CacheSettingsPage/index.tsx +++ b/src/pages/secondary/CacheSettingsPage/index.tsx @@ -1,6 +1,7 @@ import CacheEventImportSettings from '@/components/CacheEventImportSettings' import InBrowserCacheSetting from '@/components/InBrowserCacheSetting' import EventArchiveCacheSettings from '@/components/EventArchiveCacheSettings' +import PrivateKeyRecoverySetting from '@/components/PrivateKeyRecoverySetting' import { RefreshButton } from '@/components/RefreshButton' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { usePrimaryNoteView } from '@/contexts/primary-note-view-context' @@ -31,6 +32,7 @@ const CacheSettingsPage = forwardRef( controls={hideTitlebar ? undefined : } >
+ diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index ea1e71c5..cf376bab 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -6,6 +6,7 @@ import { ACCOUNT_SESSION_NETWORK_HYDRATE_MIN_INTERVAL_MS, DEFAULT_FAVORITE_RELAYS, FAST_READ_RELAY_URLS, + FAST_WRITE_RELAY_URLS, AUTHOR_PROFILE_VIEW_REPLACEABLE_KINDS, ExtendedKind, PROFILE_RELAY_URLS, @@ -16,11 +17,9 @@ import { } from '@/constants' import { applyImwaldAttributionTags, - createDeletionRequestDraftEvent, - createFollowListDraftEvent, - createMuteListDraftEvent, - createRelayListDraftEvent + createDeletionRequestDraftEvent } from '@/lib/draft-event' +import { buildNewUserTemplateDrafts } from '@/lib/new-user-template' import { getLatestEvent, minePow } from '@/lib/event' import { shouldDropEventOnIngest } from '@/lib/event-ingest-filter' import { @@ -1506,27 +1505,6 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { options?: { addClientTag?: boolean } ): TDraftEvent => applyImwaldAttributionTags(draftEvent, options) - const setupNewUser = async (signer: ISigner) => { - await Promise.allSettled([ - client.publishEvent( - FAST_READ_RELAY_URLS, - await signer.signEvent(normalizeDraftEventTags(createFollowListDraftEvent([]))) - ), - client.publishEvent( - FAST_READ_RELAY_URLS, - await signer.signEvent(normalizeDraftEventTags(createMuteListDraftEvent([]))) - ), - client.publishEvent( - FAST_READ_RELAY_URLS, - await signer.signEvent( - normalizeDraftEventTags( - createRelayListDraftEvent(FAST_READ_RELAY_URLS.map((url) => ({ url, scope: 'both' }))) - ) - ) - ) - ]) - } - const signEvent = async ( draftEvent: TDraftEvent, normalizeOpts?: { addClientTag?: boolean } @@ -1915,6 +1893,59 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { setFavoriteRelaysEvent(stored) } + const setupNewUser = async (signer: ISigner) => { + const bootstrapRelays = [...new Set([...FAST_WRITE_RELAY_URLS, ...FAST_READ_RELAY_URLS])] + + try { + const pubkey = await signer.getPublicKey() + const drafts = buildNewUserTemplateDrafts(pubkey) + + const signDraft = async (draft: TDraftEvent) => { + const event = await signer.signEvent(normalizeDraftEventTags(draft)) + if (!validateEvent(event)) { + throw new Error('Event validation failed') + } + return event as VerifiedEvent + } + + const profileEvent = await signDraft(drafts.profile) + const favoriteRelaysEvent = await signDraft(drafts.favoriteRelays) + const relayListEvent = await signDraft(drafts.relayList) + const httpRelayListEvent = await signDraft(drafts.httpRelayList) + const interestListEvent = await signDraft(drafts.interestList) + const followListEvent = await signDraft(drafts.followList) + const muteListEvent = await signDraft(drafts.muteList) + + await Promise.all([ + updateProfileEvent(profileEvent), + updateFavoriteRelaysEvent(favoriteRelaysEvent), + updateRelayListEvent(relayListEvent), + updateHttpRelayListEvent(httpRelayListEvent), + updateInterestListEvent(interestListEvent), + updateFollowListEvent(followListEvent), + updateMuteListEvent(muteListEvent, []) + ]) + + await Promise.allSettled( + [ + profileEvent, + favoriteRelaysEvent, + relayListEvent, + httpRelayListEvent, + interestListEvent, + followListEvent, + muteListEvent + ].map((event) => client.publishEvent(bootstrapRelays, event)) + ) + + toast.success( + t('Account created — customize profile and relays in Settings.') + ) + } catch (error) { + logger.error('[setupNewUser] failed', { error }) + } + } + const updateBlockedRelaysEvent = async (blockedRelaysEvent: Event) => { const newBlockedRelaysEvent = await indexedDb.putReplaceableEvent(blockedRelaysEvent) if (newBlockedRelaysEvent.id !== blockedRelaysEvent.id) return diff --git a/vite.config.ts b/vite.config.ts index 20d3078c..5ff69fdf 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -357,10 +357,6 @@ export default defineConfig(({ mode }) => { if (norm.includes('@getalby') || norm.includes('bitcoin-connect')) { return 'vendor-lightning-alby' } - if (norm.includes('nstart-modal')) { - return 'vendor-lightning-nstart' - } - if (norm.includes('embla-carousel')) { return 'vendor-embla' }