From aa72b07557ebc7c0dedff2a42b71858250c64f37 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Fri, 10 Oct 2025 18:07:06 +0200 Subject: [PATCH] universal publishing feedback --- src/components/MailboxSetting/SaveButton.tsx | 33 ++++- src/components/PostEditor/PostContent.tsx | 137 +++---------------- src/lib/publishing-feedback.tsx | 78 +++++++++++ src/providers/NostrProvider/index.tsx | 14 +- 4 files changed, 134 insertions(+), 128 deletions(-) create mode 100644 src/lib/publishing-feedback.tsx diff --git a/src/components/MailboxSetting/SaveButton.tsx b/src/components/MailboxSetting/SaveButton.tsx index 7de6134..4667b18 100644 --- a/src/components/MailboxSetting/SaveButton.tsx +++ b/src/components/MailboxSetting/SaveButton.tsx @@ -1,10 +1,11 @@ import { Button } from '@/components/ui/button' import { createRelayListDraftEvent } from '@/lib/draft-event' +import { showPublishingFeedback, showSimplePublishSuccess } from '@/lib/publishing-feedback' import { useNostr } from '@/providers/NostrProvider' import { TMailboxRelay } from '@/types' import { CloudUpload, Loader } from 'lucide-react' import { useState } from 'react' -import { toast } from 'sonner' +import { useTranslation } from 'react-i18next' export default function SaveButton({ mailboxRelays, @@ -15,6 +16,7 @@ export default function SaveButton({ hasChange: boolean setHasChange: (hasChange: boolean) => void }) { + const { t } = useTranslation() const { pubkey, publish, updateRelayListEvent } = useNostr() const [pushing, setPushing] = useState(false) @@ -22,12 +24,29 @@ export default function SaveButton({ if (!pubkey) return setPushing(true) - const event = createRelayListDraftEvent(mailboxRelays) - const relayListEvent = await publish(event) - await updateRelayListEvent(relayListEvent) - toast.success('Successfully saved mailbox relays') - setHasChange(false) - setPushing(false) + try { + const event = createRelayListDraftEvent(mailboxRelays) + const result = await publish(event) + await updateRelayListEvent(result) + setHasChange(false) + + // Show publishing feedback + if ((result as any).relayStatuses) { + showPublishingFeedback({ + success: true, + relayStatuses: (result as any).relayStatuses, + successCount: (result as any).relayStatuses.filter((s: any) => s.success).length, + totalCount: (result as any).relayStatuses.length + }, { + message: t('Mailbox relays saved'), + duration: 6000 + }) + } else { + showSimplePublishSuccess(t('Mailbox relays saved')) + } + } finally { + setPushing(false) + } } return ( diff --git a/src/components/PostEditor/PostContent.tsx b/src/components/PostEditor/PostContent.tsx index 35b06ff..286a3be 100644 --- a/src/components/PostEditor/PostContent.tsx +++ b/src/components/PostEditor/PostContent.tsx @@ -19,7 +19,7 @@ import { ImageUp, ListTodo, LoaderCircle, MessageCircle, Settings, Smile, X } fr import { Event, kinds } from 'nostr-tools' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { toast } from 'sonner' +import { showPublishingFeedback, showSimplePublishSuccess } from '@/lib/publishing-feedback' import EmojiPickerDialog from '../EmojiPickerDialog' import Mentions, { extractMentions } from './Mentions' import PollEditor from './PollEditor' @@ -27,7 +27,6 @@ import PostOptions from './PostOptions' import PostRelaySelector from './PostRelaySelector' import PostTextarea, { TPostTextareaHandle } from './PostTextarea' import Uploader from './Uploader' -import RelayStatusDisplay from '@/components/RelayStatusDisplay' export default function PostContent({ defaultContent = '', @@ -65,14 +64,6 @@ export default function PostContent({ relays: [] }) const [minPow, setMinPow] = useState(0) - const [relayStatuses, setRelayStatuses] = useState>([]) - const [showRelayStatus, setShowRelayStatus] = useState(false) - const [lastPublishedEvent, setLastPublishedEvent] = useState(null) const isFirstRender = useRef(true) const canPost = useMemo(() => { const result = ( @@ -249,82 +240,28 @@ export default function PostContent({ }) // console.log('Published event:', newEvent) - // Check if we have relay status information - console.log('Published event:', newEvent) - console.log('Relay statuses:', (newEvent as any).relayStatuses) - + // Show publishing feedback if ((newEvent as any).relayStatuses) { - setRelayStatuses((newEvent as any).relayStatuses) - setLastPublishedEvent(newEvent) - setShowRelayStatus(true) - - // Show success message with relay count - const successCount = (newEvent as any).relayStatuses.filter((s: any) => s.success).length - const totalCount = (newEvent as any).relayStatuses.length - toast.success(t('Post successful - published to {{count}} of {{total}} relays', { - count: successCount, - total: totalCount - }), { duration: 4000 }) - - // Don't close immediately if we have relay status to show - setTimeout(() => { - postEditorCache.clearPostCache({ defaultContent, parentEvent }) - deleteDraftEventCache(draftEvent) - addReplies([newEvent]) - close() - }, 8000) // Give user more time to see the relay status + showPublishingFeedback({ + success: true, + relayStatuses: (newEvent as any).relayStatuses, + successCount: (newEvent as any).relayStatuses.filter((s: any) => s.success).length, + totalCount: (newEvent as any).relayStatuses.length + }, { + message: parentEvent ? t('Reply published') : t('Post published'), + duration: 6000 + }) } else { - toast.success(t('Post successful'), { duration: 2000 }) - postEditorCache.clearPostCache({ defaultContent, parentEvent }) - deleteDraftEventCache(draftEvent) - addReplies([newEvent]) - close() + showSimplePublishSuccess(parentEvent ? t('Reply published') : t('Post published')) } + + postEditorCache.clearPostCache({ defaultContent, parentEvent }) + deleteDraftEventCache(draftEvent) + addReplies([newEvent]) + close() } catch (error) { console.error('Publishing error:', error) - - // Handle different types of errors with user-friendly messages - let errorMessage = t('Failed to post') - - if (error instanceof Error) { - if (error.message.includes('timeout')) { - errorMessage = t('Posting timed out. Your post may have been published to some relays.') - } else if (error.message.includes('auth-required') || error.message.includes('auth required')) { - errorMessage = t('Some relays require authentication. Please try again or use different relays.') - } else if (error.message.includes('blocked')) { - errorMessage = t('You are blocked from posting to some relays.') - } else if (error.message.includes('rate limit')) { - errorMessage = t('Rate limited. Please wait before trying again.') - } else if (error.message.includes('writes disabled')) { - errorMessage = t('Some relays have temporarily disabled writes.') - } else { - errorMessage = `${t('Failed to post')}: ${error.message}` - } - } else if (error instanceof AggregateError) { - // Handle multiple relay failures - const hasAuthErrors = error.errors.some(err => - err instanceof Error && err.message.includes('auth-required') - ) - const hasBlockedErrors = error.errors.some(err => - err instanceof Error && err.message.includes('blocked') - ) - const hasWriteDisabledErrors = error.errors.some(err => - err instanceof Error && err.message.includes('writes disabled') - ) - - if (hasAuthErrors) { - errorMessage = t('Some relays require authentication. Your post may have been published to other relays.') - } else if (hasBlockedErrors) { - errorMessage = t('You are blocked from some relays. Your post may have been published to other relays.') - } else if (hasWriteDisabledErrors) { - errorMessage = t('Some relays have disabled writes. Your post may have been published to other relays.') - } else { - errorMessage = t('Failed to publish to some relays. Your post may have been published to other relays.') - } - } - - toast.error(errorMessage, { duration: 8000 }) - return + // Publishing errors are handled via relay status feedback } finally { setPosting(false) } @@ -578,44 +515,6 @@ export default function PostContent({ {parentEvent ? t('Reply') : t('Post')} - - {showRelayStatus && relayStatuses.length > 0 && ( -
-
-

- 📡 Publishing Results -

-

- Your post has been published. Here's the status for each relay: -

-
- s.success).length} - totalCount={relayStatuses.length} - /> -
-
- This dialog will close automatically in a few seconds -
- -
-
- )} ) } diff --git a/src/lib/publishing-feedback.tsx b/src/lib/publishing-feedback.tsx new file mode 100644 index 0000000..8e6202f --- /dev/null +++ b/src/lib/publishing-feedback.tsx @@ -0,0 +1,78 @@ +import RelayStatusDisplay from '@/components/RelayStatusDisplay' +import { CheckCircle2 } from 'lucide-react' +import { toast } from 'sonner' + +export type RelayStatus = { + url: string + success: boolean + error?: string + authAttempted?: boolean +} + +export type PublishResult = { + success: boolean + relayStatuses: RelayStatus[] + successCount: number + totalCount: number +} + +/** + * Show publishing feedback with relay status details + * @param result Publishing result with relay statuses + * @param options Optional configuration + */ +export function showPublishingFeedback( + result: PublishResult, + options: { + message?: string + duration?: number + } = {} +) { + const { message = 'Published successfully', duration = 6000 } = options + + const { relayStatuses, successCount, totalCount } = result + + if (relayStatuses.length === 0) { + // Fallback for events without relay status tracking + toast.success(message, { duration: 2000 }) + return + } + + // Show toast with custom relay status display + toast.success( +
+
+ +
{message}
+
+
+ Published to {successCount} of {totalCount} relays +
+ +
, + { + duration, + className: 'max-w-md' + } + ) +} + +/** + * Simple success toast without relay details + */ +export function showSimplePublishSuccess(message = 'Published successfully') { + toast.success(message, { duration: 2000 }) +} + +/** + * Show publishing error + */ +export function showPublishingError(error: Error | string) { + const message = error instanceof Error ? error.message : error + toast.error(message, { duration: 4000 }) +} + diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 2cbee74..8d5a0ef 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -14,6 +14,7 @@ import { } from '@/lib/event' import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata' import { formatPubkey, pubkeyToNpub } from '@/lib/pubkey' +import { showPublishingFeedback, showSimplePublishSuccess } from '@/lib/publishing-feedback' import client from '@/services/client.service' import customEmojiService from '@/services/custom-emoji.service' import indexedDb from '@/services/indexed-db.service' @@ -657,10 +658,19 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { // Privacy: Only use user's own relays, never connect to "seen on" relays const relays = await client.determineTargetRelays(targetEvent) - await client.publishEvent(relays, deletionRequest) + const result = await client.publishEvent(relays, deletionRequest) addDeletedEvent(targetEvent) - toast.success(t('Deletion request sent to {{count}} relays', { count: relays.length })) + + // Show publishing feedback + if (result.relayStatuses) { + showPublishingFeedback(result, { + message: t('Deletion request sent'), + duration: 6000 + }) + } else { + showSimplePublishSuccess(t('Deletion request sent')) + } } const signHttpAuth = async (url: string, method: string, content = '') => {