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 [toasts, setToasts] = useState([])
|
||||||
const toastId = useRef(0)
|
const toastId = useRef(0)
|
||||||
|
|
||||||
const dispatchToast = useCallback((toastConfig) => {
|
const dispatchToast = useCallback((toast) => {
|
||||||
toastConfig = {
|
toast = {
|
||||||
...toastConfig,
|
...toast,
|
||||||
id: toastId.current++
|
id: toastId.current++
|
||||||
}
|
}
|
||||||
setToasts(toasts => [...toasts, toastConfig])
|
const { flowId } = toast
|
||||||
return () => removeToast(toastConfig)
|
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 }) => {
|
const removeToast = useCallback(({ id, onCancel, tag }) => {
|
||||||
|
@ -153,3 +166,46 @@ export const ToastProvider = ({ children }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useToast = () => useContext(ToastContext)
|
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 { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||||
import { LNbitsProvider, useLNbits } from './lnbits'
|
import { LNbitsProvider, useLNbits } from './lnbits'
|
||||||
import { NWCProvider, useNWC } from './nwc'
|
import { NWCProvider, useNWC } from './nwc'
|
||||||
import { useToast } from '../toast'
|
import { useToast, withToastFlow } from '../toast'
|
||||||
import { gql, useMutation } from '@apollo/client'
|
import { gql, useMutation } from '@apollo/client'
|
||||||
|
|
||||||
const WebLNContext = createContext({})
|
const WebLNContext = createContext({})
|
||||||
|
@ -81,35 +81,17 @@ function RawWebLNProvider ({ children }) {
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const sendPaymentWithToast = function ({ bolt11, hash, hmac }) {
|
const sendPaymentWithToast = withToastFlow(toaster)(
|
||||||
let canceled = false
|
({ bolt11, hash, hmac }) => {
|
||||||
let removeToast = toaster.warning('payment pending', {
|
return {
|
||||||
autohide: false,
|
flowId: hash,
|
||||||
onCancel: async () => {
|
type: 'payment',
|
||||||
try {
|
onPending: () => provider.sendPayment(bolt11),
|
||||||
// hash and hmac are only passed for JIT invoices
|
// hash and hmac are only passed for JIT invoices
|
||||||
if (hash && hmac) await cancelInvoice({ variables: { hash, hmac } })
|
onCancel: () => hash && hmac ? cancelInvoice({ variables: { hash, hmac } }) : undefined
|
||||||
canceled = true
|
|
||||||
toaster.warning('payment canceled')
|
|
||||||
removeToast = undefined
|
|
||||||
} catch (err) {
|
|
||||||
toaster.danger('failed to cancel payment')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
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) => {
|
const setProvider = useCallback((defaultProvider) => {
|
||||||
// move provider to the start to set it as default
|
// move provider to the start to set it as default
|
||||||
|
|
Loading…
Reference in New Issue