fix: expose more detailed error messages

This commit is contained in:
codytseng 2026-01-17 11:50:24 +08:00
parent d37aa61501
commit 71791c9513
27 changed files with 330 additions and 153 deletions

View file

@ -1,8 +1,10 @@
import { buildATag, buildETag, createBookmarkDraftEvent } from '@/lib/draft-event'
import { formatError } from '@/lib/error'
import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event'
import client from '@/services/client.service'
import { Event } from 'nostr-tools'
import { createContext, useContext } from 'react'
import { toast } from 'sonner'
import { useNostr } from './NostrProvider'
type TBookmarksContext = {
@ -45,8 +47,15 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) {
[...currentTags, isReplaceable ? buildATag(event) : buildETag(event.id, event.pubkey)],
bookmarkListEvent?.content
)
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
await updateBookmarkListEvent(newBookmarkEvent)
try {
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
await updateBookmarkListEvent(newBookmarkEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to add bookmark: ${err}`, { duration: 10_000 })
})
}
}
const removeBookmark = async (event: Event) => {
@ -64,8 +73,15 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) {
if (newTags.length === bookmarkListEvent.tags.length) return
const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags, bookmarkListEvent.content)
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
await updateBookmarkListEvent(newBookmarkEvent)
try {
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
await updateBookmarkListEvent(newBookmarkEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to remove bookmark: ${err}`, { duration: 10_000 })
})
}
}
return (

View file

@ -1,8 +1,10 @@
import { buildATag, createUserEmojiListDraftEvent } from '@/lib/draft-event'
import { formatError } from '@/lib/error'
import { getReplaceableCoordinateFromEvent } from '@/lib/event'
import client from '@/services/client.service'
import { Event, kinds } from 'nostr-tools'
import { createContext, useContext, useMemo } from 'react'
import { toast } from 'sonner'
import { useNostr } from './NostrProvider'
type TEmojiPackContext = {
@ -54,8 +56,15 @@ export function EmojiPackProvider({ children }: { children: React.ReactNode }) {
[...currentTags, buildATag(event)],
userEmojiListEvent?.content
)
const newUserEmojiListEvent = await publish(newUserEmojiListDraftEvent)
await updateUserEmojiListEvent(newUserEmojiListEvent)
try {
const newUserEmojiListEvent = await publish(newUserEmojiListDraftEvent)
await updateUserEmojiListEvent(newUserEmojiListEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to add emoji pack: ${err}`, { duration: 10_000 })
})
}
}
const removeEmojiPack = async (event: Event) => {
@ -72,8 +81,15 @@ export function EmojiPackProvider({ children }: { children: React.ReactNode }) {
newTags,
userEmojiListEvent.content
)
const newUserEmojiListEvent = await publish(newUserEmojiListDraftEvent)
await updateUserEmojiListEvent(newUserEmojiListEvent)
try {
const newUserEmojiListEvent = await publish(newUserEmojiListDraftEvent)
await updateUserEmojiListEvent(newUserEmojiListEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to remove emoji pack: ${err}`, { duration: 10_000 })
})
}
}
return (

View file

@ -1,4 +1,5 @@
import { createFavoriteRelaysDraftEvent, createRelaySetDraftEvent } from '@/lib/draft-event'
import { formatError } from '@/lib/error'
import { getReplaceableEventIdentifier } from '@/lib/event'
import { getRelaySetFromEvent } from '@/lib/event-metadata'
import { randomString } from '@/lib/random'
@ -10,6 +11,7 @@ import storage from '@/services/local-storage.service'
import { TRelaySet } from '@/types'
import { Event, kinds } from 'nostr-tools'
import { createContext, useContext, useEffect, useState } from 'react'
import { toast } from 'sonner'
import { useNostr } from './NostrProvider'
type TFavoriteRelaysContext = {
@ -146,8 +148,15 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
[...favoriteRelays, ...normalizedUrls],
relaySetEvents
)
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
try {
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to add favorite relays: ${err}`, { duration: 10_000 })
})
}
}
const deleteFavoriteRelays = async (relayUrls: string[]) => {
@ -160,8 +169,15 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
favoriteRelays.filter((url) => !normalizedUrls.includes(url)),
relaySetEvents
)
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
try {
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to delete favorite relays: ${err}`, { duration: 10_000 })
})
}
}
const createRelaySet = async (relaySetName: string, relayUrls: string[] = []) => {
@ -174,15 +190,22 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
name: relaySetName,
relayUrls: normalizedUrls
})
const newRelaySetEvent = await publish(relaySetDraftEvent)
await indexedDb.putReplaceableEvent(newRelaySetEvent)
try {
const newRelaySetEvent = await publish(relaySetDraftEvent)
await indexedDb.putReplaceableEvent(newRelaySetEvent)
const favoriteRelaysDraftEvent = createFavoriteRelaysDraftEvent(favoriteRelays, [
...relaySetEvents,
newRelaySetEvent
])
const newFavoriteRelaysEvent = await publish(favoriteRelaysDraftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
const favoriteRelaysDraftEvent = createFavoriteRelaysDraftEvent(favoriteRelays, [
...relaySetEvents,
newRelaySetEvent
])
const newFavoriteRelaysEvent = await publish(favoriteRelaysDraftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to create relay set: ${err}`, { duration: 10_000 })
})
}
}
const addRelaySets = async (newRelaySetEvents: Event[]) => {
@ -190,8 +213,15 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
...relaySetEvents,
...newRelaySetEvents
])
const newFavoriteRelaysEvent = await publish(favoriteRelaysDraftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
try {
const newFavoriteRelaysEvent = await publish(favoriteRelaysDraftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to add relay sets: ${err}`, { duration: 10_000 })
})
}
}
const deleteRelaySet = async (id: string) => {
@ -201,30 +231,52 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
if (newRelaySetEvents.length === relaySetEvents.length) return
const draftEvent = createFavoriteRelaysDraftEvent(favoriteRelays, newRelaySetEvents)
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
try {
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to delete relay set: ${err}`, { duration: 10_000 })
})
}
}
const updateRelaySet = async (newSet: TRelaySet) => {
const draftEvent = createRelaySetDraftEvent(newSet)
const newRelaySetEvent = await publish(draftEvent)
await indexedDb.putReplaceableEvent(newRelaySetEvent)
setRelaySetEvents((prev) => {
return prev.map((event) => {
if (getReplaceableEventIdentifier(event) === newSet.id) {
return newRelaySetEvent
}
return event
try {
const newRelaySetEvent = await publish(draftEvent)
await indexedDb.putReplaceableEvent(newRelaySetEvent)
setRelaySetEvents((prev) => {
return prev.map((event) => {
if (getReplaceableEventIdentifier(event) === newSet.id) {
return newRelaySetEvent
}
return event
})
})
})
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to update relay set: ${err}`, { duration: 10_000 })
})
}
}
const reorderFavoriteRelays = async (reorderedRelays: string[]) => {
setFavoriteRelays(reorderedRelays)
const draftEvent = createFavoriteRelaysDraftEvent(reorderedRelays, relaySetEvents)
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
try {
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to reorder favorite relays: ${err}`, { duration: 10_000 })
})
}
}
const reorderRelaySets = async (reorderedSets: TRelaySet[]) => {
@ -233,8 +285,15 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
favoriteRelays,
reorderedSets.map((set) => set.aTag)
)
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
try {
const newFavoriteRelaysEvent = await publish(draftEvent)
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to reorder relay sets: ${err}`, { duration: 10_000 })
})
}
}
return (

View file

@ -4,6 +4,8 @@ import client from '@/services/client.service'
import { createContext, useContext, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useNostr } from './NostrProvider'
import { formatError } from '@/lib/error'
import { toast } from 'sonner'
type TFollowListContext = {
followingSet: Set<string>
@ -44,8 +46,15 @@ export function FollowListProvider({ children }: { children: React.ReactNode })
(followListEvent?.tags ?? []).concat([['p', pubkey]]),
followListEvent?.content
)
const newFollowListEvent = await publish(newFollowListDraftEvent)
await updateFollowListEvent(newFollowListEvent)
try {
const newFollowListEvent = await publish(newFollowListDraftEvent)
await updateFollowListEvent(newFollowListEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to follow: ${err}`, { duration: 10_000 })
})
}
}
const unfollow = async (pubkey: string) => {
@ -58,8 +67,15 @@ export function FollowListProvider({ children }: { children: React.ReactNode })
followListEvent.tags.filter(([tagName, tagValue]) => tagName !== 'p' || tagValue !== pubkey),
followListEvent.content
)
const newFollowListEvent = await publish(newFollowListDraftEvent)
await updateFollowListEvent(newFollowListEvent)
try {
const newFollowListEvent = await publish(newFollowListDraftEvent)
await updateFollowListEvent(newFollowListEvent)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to unfollow: ${err}`, { duration: 10_000 })
})
}
}
return (

View file

@ -1,4 +1,5 @@
import { createMuteListDraftEvent } from '@/lib/draft-event'
import { formatError } from '@/lib/error'
import { getPubkeysFromPTags } from '@/lib/tag'
import client from '@/services/client.service'
import indexedDb from '@/services/indexed-db.service'
@ -115,7 +116,6 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
}
const newMuteListDraftEvent = createMuteListDraftEvent(tags, content)
const event = await publish(newMuteListDraftEvent)
toast.success(t('Successfully updated mute list'))
return event
}
@ -147,7 +147,10 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
const privateTags = await getPrivateTags(newMuteListEvent)
await updateMuteListEvent(newMuteListEvent, privateTags)
} catch (error) {
toast.error(t('Failed to mute user publicly') + ': ' + (error as Error).message)
const errors = formatError(error)
errors.forEach((err) => {
toast.error(t('Failed to mute user publicly') + ': ' + err, { duration: 10_000 })
})
} finally {
setChanging(false)
}
@ -170,7 +173,10 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
const newMuteListEvent = await publishNewMuteListEvent(muteListEvent?.tags ?? [], cipherText)
await updateMuteListEvent(newMuteListEvent, newPrivateTags)
} catch (error) {
toast.error(t('Failed to mute user privately') + ': ' + (error as Error).message)
const errors = formatError(error)
errors.forEach((err) => {
toast.error(t('Failed to mute user privately') + ': ' + err, { duration: 10_000 })
})
} finally {
setChanging(false)
}
@ -196,6 +202,11 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
cipherText
)
await updateMuteListEvent(newMuteListEvent, newPrivateTags)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(t('Failed to unmute user') + ': ' + err, { duration: 10_000 })
})
} finally {
setChanging(false)
}
@ -223,6 +234,11 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
cipherText
)
await updateMuteListEvent(newMuteListEvent, newPrivateTags)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(t('Failed to switch to public mute') + ': ' + err, { duration: 10_000 })
})
} finally {
setChanging(false)
}
@ -248,6 +264,11 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
const cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newPrivateTags))
const newMuteListEvent = await publishNewMuteListEvent(newTags, cipherText)
await updateMuteListEvent(newMuteListEvent, newPrivateTags)
} catch (error) {
const errors = formatError(error)
errors.forEach((err) => {
toast.error(t('Failed to switch to private mute') + ': ' + err, { duration: 10_000 })
})
} finally {
setChanging(false)
}

View file

@ -1,5 +1,6 @@
import { MAX_PINNED_NOTES } from '@/constants'
import { buildETag, createPinListDraftEvent } from '@/lib/draft-event'
import { formatError } from '@/lib/error'
import { getPinnedEventHexIdSetFromPinListEvent } from '@/lib/event-metadata'
import client from '@/services/client.service'
import { Event, kinds } from 'nostr-tools'
@ -67,7 +68,7 @@ export function PinListProvider({ children }: { children: React.ReactNode }) {
const { unwrap } = toast.promise(_pin, {
loading: t('Pinning...'),
success: t('Pinned!'),
error: (err) => t('Failed to pin: {{error}}', { error: err.message })
error: (err) => t('Failed to pin: {{error}}', { error: formatError(err).join('; ') })
})
await unwrap()
}
@ -92,7 +93,7 @@ export function PinListProvider({ children }: { children: React.ReactNode }) {
const { unwrap } = toast.promise(_unpin, {
loading: t('Unpinning...'),
success: t('Unpinned!'),
error: (err) => t('Failed to unpin: {{error}}', { error: err.message })
error: (err) => t('Failed to unpin: {{error}}', { error: formatError(err).join('; ') })
})
await unwrap()
}

View file

@ -1,8 +1,10 @@
import { ExtendedKind } from '@/constants'
import { formatError } from '@/lib/error'
import { getPubkeysFromPTags } from '@/lib/tag'
import indexedDb from '@/services/indexed-db.service'
import { Event } from 'nostr-tools'
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { z } from 'zod'
import { useNostr } from './NostrProvider'
@ -105,7 +107,10 @@ export function PinnedUsersProvider({ children }: { children: React.ReactNode })
const newEvent = await publish(draftEvent)
await updatePinnedUsersEvent(newEvent, privateTags)
} catch (error) {
console.error('Failed to pin user:', error)
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to pin user: ${err}`, { duration: 10_000 })
})
}
},
[accountPubkey, isPinned, pinnedUsersEvent, publish, updatePinnedUsersEvent, privateTags]
@ -130,7 +135,10 @@ export function PinnedUsersProvider({ children }: { children: React.ReactNode })
const newEvent = await publish(draftEvent)
await updatePinnedUsersEvent(newEvent, newPrivateTags)
} catch (error) {
console.error('Failed to unpin user:', error)
const errors = formatError(error)
errors.forEach((err) => {
toast.error(`Failed to unpin user: ${err}`, { duration: 10_000 })
})
}
},
[