fix: expose more detailed error messages
This commit is contained in:
parent
d37aa61501
commit
71791c9513
27 changed files with 330 additions and 153 deletions
|
|
@ -6,7 +6,6 @@ import { BookmarkIcon, Loader } from 'lucide-react'
|
|||
import { Event } from 'nostr-tools'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function BookmarkButton({ stuff }: { stuff: Event | string }) {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -33,13 +32,8 @@ export default function BookmarkButton({ stuff }: { stuff: Event | string }) {
|
|||
if (isBookmarked || !event) return
|
||||
|
||||
setUpdating(true)
|
||||
try {
|
||||
await addBookmark(event)
|
||||
} catch (error) {
|
||||
toast.error(t('Bookmark failed') + ': ' + (error as Error).message)
|
||||
} finally {
|
||||
setUpdating(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -49,13 +43,8 @@ export default function BookmarkButton({ stuff }: { stuff: Event | string }) {
|
|||
if (!isBookmarked || !event) return
|
||||
|
||||
setUpdating(true)
|
||||
try {
|
||||
await removeBookmark(event)
|
||||
} catch (error) {
|
||||
toast.error(t('Remove bookmark failed') + ': ' + (error as Error).message)
|
||||
} finally {
|
||||
setUpdating(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import { useNostr } from '@/providers/NostrProvider'
|
|||
import { Loader } from 'lucide-react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function FollowButton({ pubkey }: { pubkey: string }) {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -33,13 +32,8 @@ export default function FollowButton({ pubkey }: { pubkey: string }) {
|
|||
if (isFollowing) return
|
||||
|
||||
setUpdating(true)
|
||||
try {
|
||||
await follow(pubkey)
|
||||
} catch (error) {
|
||||
toast.error(t('Follow failed') + ': ' + (error as Error).message)
|
||||
} finally {
|
||||
setUpdating(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -49,13 +43,8 @@ export default function FollowButton({ pubkey }: { pubkey: string }) {
|
|||
if (!isFollowing) return
|
||||
|
||||
setUpdating(true)
|
||||
try {
|
||||
await unfollow(pubkey)
|
||||
} catch (error) {
|
||||
toast.error(t('Unfollow failed') + ': ' + (error as Error).message)
|
||||
} finally {
|
||||
setUpdating(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { createRelayListDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { TMailboxRelay } from '@/types'
|
||||
import { CloudUpload, Loader } from 'lucide-react'
|
||||
|
|
@ -25,11 +26,18 @@ export default function SaveButton({
|
|||
|
||||
setPushing(true)
|
||||
const event = createRelayListDraftEvent(mailboxRelays)
|
||||
try {
|
||||
const relayListEvent = await publish(event)
|
||||
await updateRelayListEvent(relayListEvent)
|
||||
toast.success('Successfully saved mailbox relays')
|
||||
setHasChange(false)
|
||||
setPushing(false)
|
||||
} catch (error) {
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to save mailbox relays')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { CheckIcon, Loader, PlusIcon } from 'lucide-react'
|
|||
import { Event } from 'nostr-tools'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import Image from '../Image'
|
||||
|
||||
export default function EmojiPack({ event, className }: { event: Event; className?: string }) {
|
||||
|
|
@ -27,14 +26,8 @@ export default function EmojiPack({ event, className }: { event: Event; classNam
|
|||
if (isCollected) return
|
||||
|
||||
setUpdating(true)
|
||||
try {
|
||||
await addEmojiPack(event)
|
||||
toast.success(t('Emoji pack added'))
|
||||
} catch (error) {
|
||||
toast.error(t('Add emoji pack failed') + ': ' + (error as Error).message)
|
||||
} finally {
|
||||
setUpdating(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -44,14 +37,8 @@ export default function EmojiPack({ event, className }: { event: Event; classNam
|
|||
if (!isCollected) return
|
||||
|
||||
setUpdating(true)
|
||||
try {
|
||||
await removeEmojiPack(event)
|
||||
toast.success(t('Emoji pack removed'))
|
||||
} catch (error) {
|
||||
toast.error(t('Remove emoji pack failed') + ': ' + (error as Error).message)
|
||||
} finally {
|
||||
setUpdating(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { POLL_TYPE } from '@/constants'
|
|||
import { useTranslatedEvent } from '@/hooks'
|
||||
import { useFetchPollResults } from '@/hooks/useFetchPollResults'
|
||||
import { createPollResponseDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { getPollMetadataFromEvent } from '@/lib/event-metadata'
|
||||
import { cn, isPartiallyInViewport } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
|
|
@ -126,8 +127,10 @@ export default function Poll({ event, className }: { event: Event; className?: s
|
|||
setSelectedOptionIds([])
|
||||
pollResultsService.addPollResponse(event.id, pubkey, selectedOptionIds)
|
||||
} catch (error) {
|
||||
console.error('Failed to vote:', error)
|
||||
toast.error('Failed to vote: ' + (error as Error).message)
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to vote')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
} finally {
|
||||
setIsVoting(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
import { Label } from '@/components/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { createReportDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { Loader } from 'lucide-react'
|
||||
|
|
@ -94,13 +95,9 @@ function ReportContent({ event, closeDialog }: { event: NostrEvent; closeDialog:
|
|||
toast.success(t('Successfully report'))
|
||||
closeDialog()
|
||||
} catch (error) {
|
||||
const errors = error instanceof AggregateError ? error.errors : [error]
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(
|
||||
`${t('Failed to report')}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
{ duration: 10_000 }
|
||||
)
|
||||
console.error(err)
|
||||
toast.error(`${t('Failed to report')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
return
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { formatError } from '@/lib/error'
|
||||
import { getNoteBech32Id, isProtectedEvent } from '@/lib/event'
|
||||
import { toNjump } from '@/lib/link'
|
||||
import { pubkeyToNpub } from '@/lib/pubkey'
|
||||
|
|
@ -117,7 +118,7 @@ export function useMenuActions({
|
|||
error: (err) => {
|
||||
return t('Failed to republish to relay set: {{name}}. Error: {{error}}', {
|
||||
name: set.name,
|
||||
error: err.message
|
||||
error: formatError(err).join('; ')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -147,7 +148,7 @@ export function useMenuActions({
|
|||
error: (err) => {
|
||||
return t('Failed to republish to relay: {{url}}. Error: {{error}}', {
|
||||
url: simplifyUrl(relay),
|
||||
error: err.message
|
||||
error: formatError(err).join('; ')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import PostOptions from './PostOptions'
|
|||
import PostRelaySelector from './PostRelaySelector'
|
||||
import PostTextarea, { TPostTextareaHandle } from './PostTextarea'
|
||||
import Uploader from './Uploader'
|
||||
import { formatError } from '@/lib/error'
|
||||
|
||||
export default function PostContent({
|
||||
defaultContent = '',
|
||||
|
|
@ -160,13 +161,9 @@ export default function PostContent({
|
|||
toast.success(t('Post successful'), { duration: 2000 })
|
||||
close()
|
||||
} catch (error) {
|
||||
const errors = error instanceof AggregateError ? error.errors : [error]
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(
|
||||
`${t('Failed to post')}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
{ duration: 10_000 }
|
||||
)
|
||||
console.error(err)
|
||||
toast.error(`${t('Failed to post')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
return
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { createRelayReviewDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { Loader2, Star } from 'lucide-react'
|
||||
import { NostrEvent } from 'nostr-tools'
|
||||
|
|
@ -32,12 +33,10 @@ export default function ReviewEditor({
|
|||
const evt = await publish(draftEvent)
|
||||
onReviewed(evt)
|
||||
} catch (error) {
|
||||
if (error instanceof AggregateError) {
|
||||
error.errors.forEach((e) => toast.error(`${t('Failed to review')}: ${e.message}`))
|
||||
} else if (error instanceof Error) {
|
||||
toast.error(`${t('Failed to review')}: ${error.message}`)
|
||||
}
|
||||
console.error(error)
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to review')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
return
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { createJoinDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import relayMembershipService from '@/services/relay-membership.service'
|
||||
|
|
@ -57,13 +58,9 @@ export default function JoinDialog({
|
|||
setInviteCode('')
|
||||
setShowJoinDialog(false)
|
||||
} catch (error) {
|
||||
const errors = error instanceof AggregateError ? error.errors : [error]
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(
|
||||
`${t('Failed to send join request')}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
{ duration: 10_000 }
|
||||
)
|
||||
console.error(err)
|
||||
toast.error(`${t('Failed to send join request')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
return
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { createJoinDraftEvent, createLeaveDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { checkNip43Support } from '@/lib/relay'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import relayMembershipService from '@/services/relay-membership.service'
|
||||
|
|
@ -72,7 +73,11 @@ export default function RelayMembershipControl({
|
|||
toast.success(t('Join request sent successfully'))
|
||||
await relayMembershipService.addNewMember(relayInfo.url, joinRequestEvent.pubkey)
|
||||
onMembershipStatusChange?.(true)
|
||||
} catch {
|
||||
} catch (error) {
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to send join request')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
setShowJoinDialog(true)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import Emoji from '../Emoji'
|
|||
import EmojiPicker from '../EmojiPicker'
|
||||
import SuggestedEmojis from '../SuggestedEmojis'
|
||||
import { formatCount } from './utils'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function LikeButton({ stuff }: { stuff: Event | string }) {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -90,7 +92,10 @@ export default function LikeButton({ stuff }: { stuff: Event | string }) {
|
|||
const evt = await publish(reaction, { additionalRelayUrls: seenOn })
|
||||
stuffStatsService.updateStuffStatsByEvents([evt])
|
||||
} catch (error) {
|
||||
console.error('like failed', error)
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to like')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
} finally {
|
||||
setLiking(false)
|
||||
clearTimeout(timer)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
createExternalContentReactionDraftEvent,
|
||||
createReactionDraftEvent
|
||||
} from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { getDefaultRelayUrls } from '@/lib/relay'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
|
|
@ -14,6 +15,7 @@ import { TEmoji } from '@/types'
|
|||
import { Loader } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import Emoji from '../Emoji'
|
||||
|
||||
export default function Likes({ stuff }: { stuff: Event | string }) {
|
||||
|
|
@ -57,7 +59,10 @@ export default function Likes({ stuff }: { stuff: Event | string }) {
|
|||
const evt = await publish(reaction, { additionalRelayUrls: seenOn })
|
||||
stuffStatsService.updateStuffStatsByEvents([evt])
|
||||
} catch (error) {
|
||||
console.error('like failed', error)
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`Failed to like: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
} finally {
|
||||
setLiking(null)
|
||||
clearTimeout(timer)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import { useTranslation } from 'react-i18next'
|
|||
import PostEditor from '../PostEditor'
|
||||
import { formatCount } from './utils'
|
||||
import { SPECIAL_TRUST_SCORE_FILTER_ID } from '@/constants'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function RepostButton({ stuff }: { stuff: Event | string }) {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -87,7 +89,10 @@ export default function RepostButton({ stuff }: { stuff: Event | string }) {
|
|||
const evt = await publish(repost)
|
||||
stuffStatsService.updateStuffStatsByEvents([evt])
|
||||
} catch (error) {
|
||||
console.error('repost failed', error)
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to repost')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
} finally {
|
||||
setReposting(false)
|
||||
clearTimeout(timer)
|
||||
|
|
|
|||
6
src/lib/error.ts
Normal file
6
src/lib/error.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export function formatError(error: unknown): string[] {
|
||||
const errors = error instanceof AggregateError ? error.errors : [error]
|
||||
return errors.map((err) => {
|
||||
return err instanceof Error ? err.message : String(err)
|
||||
})
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { Input } from '@/components/ui/input'
|
|||
import { Separator } from '@/components/ui/separator'
|
||||
import { RECOMMENDED_BLOSSOM_SERVERS } from '@/constants'
|
||||
import { createBlossomServerListDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { getServersFromServerTags } from '@/lib/tag'
|
||||
import { normalizeHttpUrl } from '@/lib/url'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
|
@ -13,6 +14,7 @@ import { AlertCircle, ArrowUpToLine, Loader, X } from 'lucide-react'
|
|||
import { Event } from 'nostr-tools'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function BlossomServerListSetting() {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -48,7 +50,10 @@ export default function BlossomServerListSetting() {
|
|||
setBlossomServerListEvent(newEvent)
|
||||
setUrl('')
|
||||
} catch (error) {
|
||||
console.error('Failed to add Blossom URL:', error)
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to add Blossom URL')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
} finally {
|
||||
setAdding(false)
|
||||
}
|
||||
|
|
@ -72,7 +77,10 @@ export default function BlossomServerListSetting() {
|
|||
await client.updateBlossomServerListEventCache(newEvent)
|
||||
setBlossomServerListEvent(newEvent)
|
||||
} catch (error) {
|
||||
console.error('Failed to remove Blossom URL:', error)
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to remove Blossom URL')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
} finally {
|
||||
setRemovingIndex(-1)
|
||||
}
|
||||
|
|
@ -88,7 +96,10 @@ export default function BlossomServerListSetting() {
|
|||
await client.updateBlossomServerListEventCache(newEvent)
|
||||
setBlossomServerListEvent(newEvent)
|
||||
} catch (error) {
|
||||
console.error('Failed to move Blossom URL to top:', error)
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to move Blossom URL to top')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
} finally {
|
||||
setMovingIndex(-1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { Label } from '@/components/ui/label'
|
|||
import { Textarea } from '@/components/ui/textarea'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { createProfileDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { isEmail } from '@/lib/utils'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
|
|
@ -14,6 +15,7 @@ import { useNostr } from '@/providers/NostrProvider'
|
|||
import { Loader, Upload } from 'lucide-react'
|
||||
import { forwardRef, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -97,10 +99,17 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
|
|||
JSON.stringify(newProfileContent),
|
||||
profileEvent?.tags
|
||||
)
|
||||
try {
|
||||
const newProfileEvent = await publish(profileDraftEvent)
|
||||
await updateProfileEvent(newProfileEvent)
|
||||
setSaving(false)
|
||||
pop()
|
||||
} catch (error) {
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to save profile')}: ${err}`, { duration: 10_000 })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onBannerUploadSuccess = ({ url }: { url: string }) => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button'
|
|||
import { Input } from '@/components/ui/input'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { createProfileDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { isEmail } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useZap } from '@/providers/ZapProvider'
|
||||
|
|
@ -65,8 +66,13 @@ const RizfulPage = forwardRef(({ index }: { index?: number }, ref) => {
|
|||
)
|
||||
const newProfileEvent = await publish(profileDraftEvent)
|
||||
await updateProfileEvent(newProfileEvent)
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : String(e))
|
||||
} catch (error) {
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to update profile with Lightning Address')}: ${err}`, {
|
||||
duration: 10_000
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button'
|
|||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { createProfileDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { isEmail } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { Loader } from 'lucide-react'
|
||||
|
|
@ -45,10 +46,20 @@ export default function LightningAddressInput() {
|
|||
JSON.stringify(profileContent),
|
||||
profileEvent?.tags
|
||||
)
|
||||
try {
|
||||
const newProfileEvent = await publish(profileDraftEvent)
|
||||
await updateProfileEvent(newProfileEvent)
|
||||
} catch (error) {
|
||||
const errors = formatError(error)
|
||||
errors.forEach((err) => {
|
||||
toast.error(`${t('Failed to update profile with Lightning Address')}: ${err}`, {
|
||||
duration: 10_000
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-1">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
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)
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
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
|
||||
)
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
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
|
||||
)
|
||||
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,6 +190,7 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
|||
name: relaySetName,
|
||||
relayUrls: normalizedUrls
|
||||
})
|
||||
try {
|
||||
const newRelaySetEvent = await publish(relaySetDraftEvent)
|
||||
await indexedDb.putReplaceableEvent(newRelaySetEvent)
|
||||
|
||||
|
|
@ -183,6 +200,12 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
|||
])
|
||||
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
|
||||
])
|
||||
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,12 +231,21 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
|||
if (newRelaySetEvents.length === relaySetEvents.length) return
|
||||
|
||||
const draftEvent = createFavoriteRelaysDraftEvent(favoriteRelays, newRelaySetEvents)
|
||||
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)
|
||||
|
||||
try {
|
||||
const newRelaySetEvent = await publish(draftEvent)
|
||||
await indexedDb.putReplaceableEvent(newRelaySetEvent)
|
||||
|
||||
|
|
@ -218,13 +257,26 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
|||
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)
|
||||
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)
|
||||
)
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
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
|
||||
)
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
})
|
||||
}
|
||||
},
|
||||
[
|
||||
|
|
|
|||
|
|
@ -179,12 +179,20 @@ class ClientService extends EventTarget {
|
|||
const successThreshold = uniqueRelayUrls.length / 3
|
||||
const errors: { url: string; error: any }[] = []
|
||||
|
||||
const checkCompletion = () => {
|
||||
const checkCompletion = (url: string, success: boolean, error?: unknown) => {
|
||||
if (error) {
|
||||
errors.push({ url, error })
|
||||
}
|
||||
if (success) {
|
||||
successCount++
|
||||
}
|
||||
finishedCount++
|
||||
|
||||
if (successCount >= successThreshold) {
|
||||
this.emitNewEvent(event, uniqueRelayUrls)
|
||||
resolve()
|
||||
}
|
||||
if (++finishedCount >= uniqueRelayUrls.length) {
|
||||
if (finishedCount >= uniqueRelayUrls.length) {
|
||||
reject(
|
||||
new AggregateError(
|
||||
errors.map(
|
||||
|
|
@ -204,8 +212,7 @@ class ClientService extends EventTarget {
|
|||
return undefined
|
||||
})
|
||||
if (!relay) {
|
||||
errors.push({ url, error: new Error('Cannot connect to relay') })
|
||||
checkCompletion()
|
||||
checkCompletion(url, false, new Error('Cannot connect to relay'))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -216,7 +223,7 @@ class ClientService extends EventTarget {
|
|||
try {
|
||||
await relay.publish(event)
|
||||
that.trackEventSeenOn(event.id, relay)
|
||||
successCount++
|
||||
checkCompletion(url, true)
|
||||
} catch (error) {
|
||||
if (
|
||||
!hasAuthed &&
|
||||
|
|
@ -227,17 +234,20 @@ class ClientService extends EventTarget {
|
|||
try {
|
||||
await relay.auth((authEvt: EventTemplate) => that.signer!.signEvent(authEvt))
|
||||
hasAuthed = true
|
||||
return await publishPromise()
|
||||
await publishPromise().catch(() => {
|
||||
// ignore
|
||||
})
|
||||
return
|
||||
} catch (error) {
|
||||
errors.push({ url, error })
|
||||
checkCompletion(url, false, error)
|
||||
}
|
||||
} else {
|
||||
errors.push({ url, error })
|
||||
checkCompletion(url, false, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return publishPromise().finally(checkCompletion)
|
||||
return publishPromise()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue