Toast flows (#856)
* Use toast flows "Toast flows" are a group of toasts that should be shown after each other. Before this commit, they were implemented by manually removing previous toasts in the same flow. Now a flowId can be passed and ToastProvider will make sure that there always only exists one toast with the same flowId. This is different to toast tags since tags don't replace toasts with the same tag, they only are shown "above" them. * Create wrapper for toast flows
This commit is contained in:
parent
fe0d960208
commit
5de014cba8
|
@ -13,13 +13,26 @@ export const ToastProvider = ({ children }) => {
|
|||
const [toasts, setToasts] = useState([])
|
||||
const toastId = useRef(0)
|
||||
|
||||
const dispatchToast = useCallback((toastConfig) => {
|
||||
toastConfig = {
|
||||
...toastConfig,
|
||||
const dispatchToast = useCallback((toast) => {
|
||||
toast = {
|
||||
...toast,
|
||||
id: toastId.current++
|
||||
}
|
||||
setToasts(toasts => [...toasts, toastConfig])
|
||||
return () => removeToast(toastConfig)
|
||||
const { flowId } = toast
|
||||
setToasts(toasts => {
|
||||
if (flowId) {
|
||||
// replace previous toast with same flow id
|
||||
const idx = toasts.findIndex(toast => toast.flowId === flowId)
|
||||
if (idx === -1) return [...toasts, toast]
|
||||
return [
|
||||
...toasts.slice(0, idx),
|
||||
toast,
|
||||
...toasts.slice(idx + 1)
|
||||
]
|
||||
}
|
||||
return [...toasts, toast]
|
||||
})
|
||||
return () => removeToast(toast)
|
||||
}, [])
|
||||
|
||||
const removeToast = useCallback(({ id, onCancel, tag }) => {
|
||||
|
@ -153,3 +166,46 @@ export const ToastProvider = ({ children }) => {
|
|||
}
|
||||
|
||||
export const useToast = () => useContext(ToastContext)
|
||||
|
||||
export const withToastFlow = (toaster) => flowFn => {
|
||||
const wrapper = async (...args) => {
|
||||
const {
|
||||
flowId,
|
||||
type: t,
|
||||
onPending,
|
||||
onSuccess,
|
||||
onCancel,
|
||||
onError
|
||||
} = flowFn(...args)
|
||||
let canceled
|
||||
toaster.warning(`${t} pending`, {
|
||||
autohide: false,
|
||||
onCancel: async () => {
|
||||
try {
|
||||
await onCancel?.()
|
||||
canceled = true
|
||||
toaster.warning(`${t} canceled`, { flowId })
|
||||
} catch (err) {
|
||||
toaster.danger(`failed to cancel ${t}`, { flowId })
|
||||
}
|
||||
},
|
||||
flowId
|
||||
})
|
||||
try {
|
||||
const ret = await onPending()
|
||||
if (!canceled) {
|
||||
toaster.success(`${t} successful`, { flowId })
|
||||
await onSuccess?.()
|
||||
}
|
||||
return ret
|
||||
} catch (err) {
|
||||
// ignore errors if canceled since they might be caused by cancellation
|
||||
if (canceled) return
|
||||
const reason = err?.message?.toString().toLowerCase() || 'unknown reason'
|
||||
toaster.danger(`${t} failed: ${reason}`, { flowId })
|
||||
await onError?.()
|
||||
throw err
|
||||
}
|
||||
}
|
||||
return wrapper
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { LNbitsProvider, useLNbits } from './lnbits'
|
||||
import { NWCProvider, useNWC } from './nwc'
|
||||
import { useToast } from '../toast'
|
||||
import { useToast, withToastFlow } from '../toast'
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
|
||||
const WebLNContext = createContext({})
|
||||
|
@ -81,35 +81,17 @@ function RawWebLNProvider ({ children }) {
|
|||
}
|
||||
`)
|
||||
|
||||
const sendPaymentWithToast = function ({ bolt11, hash, hmac }) {
|
||||
let canceled = false
|
||||
let removeToast = toaster.warning('payment pending', {
|
||||
autohide: false,
|
||||
onCancel: async () => {
|
||||
try {
|
||||
// hash and hmac are only passed for JIT invoices
|
||||
if (hash && hmac) await cancelInvoice({ variables: { hash, hmac } })
|
||||
canceled = true
|
||||
toaster.warning('payment canceled')
|
||||
removeToast = undefined
|
||||
} catch (err) {
|
||||
toaster.danger('failed to cancel payment')
|
||||
}
|
||||
const sendPaymentWithToast = withToastFlow(toaster)(
|
||||
({ bolt11, hash, hmac }) => {
|
||||
return {
|
||||
flowId: hash,
|
||||
type: 'payment',
|
||||
onPending: () => provider.sendPayment(bolt11),
|
||||
// hash and hmac are only passed for JIT invoices
|
||||
onCancel: () => hash && hmac ? cancelInvoice({ variables: { hash, hmac } }) : undefined
|
||||
}
|
||||
})
|
||||
return provider.sendPayment(bolt11)
|
||||
.then(({ preimage }) => {
|
||||
removeToast?.()
|
||||
toaster.success('payment successful')
|
||||
return { preimage }
|
||||
}).catch((err) => {
|
||||
if (canceled) return
|
||||
removeToast?.()
|
||||
const reason = err?.message?.toString().toLowerCase() || 'unknown reason'
|
||||
toaster.danger(`payment failed: ${reason}`)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const setProvider = useCallback((defaultProvider) => {
|
||||
// move provider to the start to set it as default
|
||||
|
|
Loading…
Reference in New Issue