import storage from '@/services/local-storage.service' import RelayStatusDisplay from '@/components/RelayStatusDisplay' import { CheckCircle2 } from 'lucide-react' import type { ReactNode } from 'react' import { useContext } from 'react' import { FavoriteRelaysContext } from '@/providers/favorite-relays-context' import { toast } from 'sonner' export type PublishSuccessSubtleDetail = { message?: string } export const PUBLISH_SUCCESS_SUBTLE_EVENT = 'jumble:publishSuccessSubtle' function emitPublishSuccessSubtle(message?: string): void { if (typeof window === 'undefined') return window.dispatchEvent( new CustomEvent(PUBLISH_SUCCESS_SUBTLE_EVENT, { detail: { message } }) ) } function publishSuccessToastsEnabled(): boolean { return storage.getShowPublishSuccessToasts() } /** Per-relay toast panels only when success toasts are on and the nested setting is enabled. */ export function detailedPublishToastsEnabled(): boolean { return publishSuccessToastsEnabled() && storage.getShowDetailedPublishToasts() } function resolvePromiseSuccessLabel(success: string | (() => ReactNode)): string | undefined { if (typeof success === 'string') return success try { const v = success() if (typeof v === 'string') return v } catch { /* ignore */ } return undefined } export type RelayStatus = { url: string success: boolean error?: string message?: string authAttempted?: boolean } export type PublishResult = { success: boolean relayStatuses: RelayStatus[] successCount: number totalCount: number } function PublishToastRelayPanel({ message, result }: { message: string result: PublishResult }) { const fav = useContext(FavoriteRelaysContext) const onBlockRelay = fav ? (url: string) => { void fav.addBlockedRelays([url]) } : undefined const { relayStatuses, successCount, totalCount } = result const isSuccess = successCount > 0 return (
{message}
Published to {successCount} of {totalCount} relays
) } /** * 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 } = result if (relayStatuses.length === 0) { // e.g. publishEvent with zero target relays still returns { relayStatuses: [] }; must not use success styling const publishFailed = result.successCount < 1 || result.success === false if (publishFailed) { toast.error(message, { duration: 4000 }) return } if (publishSuccessToastsEnabled()) { toast.success(message, { duration: 2000 }) } else { emitPublishSuccessSubtle(message) } return } const isSuccess = successCount > 0 if (isSuccess && !publishSuccessToastsEnabled()) { emitPublishSuccessSubtle(message) return } const toastFunction = isSuccess ? toast.success : toast.error if (!detailedPublishToastsEnabled()) { toastFunction(message, { duration: isSuccess ? 2000 : duration }) return } toastFunction(, { duration, className: 'max-w-lg w-full', icon: null }) } /** * Simple success toast without relay details */ export function showSimplePublishSuccess(message = 'Published successfully') { if (!publishSuccessToastsEnabled()) return 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 }) } type PublishPromiseToastOptions = { loading: string success: string | (() => ReactNode) error: (err: Error) => string } /** * Like `toast.promise` for publish/republish flows: respects {@link storage.getShowPublishSuccessToasts} * (no green success toast when disabled). Loading and error toasts still appear. */ export function toastPublishPromise(promise: Promise, opts: PublishPromiseToastOptions): void { if (!publishSuccessToastsEnabled()) { const id = toast.loading(opts.loading) promise .then(() => { toast.dismiss(id) const label = resolvePromiseSuccessLabel(opts.success) emitPublishSuccessSubtle(label) }) .catch((err: unknown) => { toast.dismiss(id) const e = err instanceof Error ? err : new Error(String(err)) toast.error(opts.error(e)) }) return } toast.promise(promise, opts) }