stacker.news/components/toast.js

163 lines
5.5 KiB
JavaScript
Raw Normal View History

import { useRouter } from 'next/router'
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import Button from 'react-bootstrap/Button'
import Toast from 'react-bootstrap/Toast'
import ToastBody from 'react-bootstrap/ToastBody'
import ToastContainer from 'react-bootstrap/ToastContainer'
import styles from './toast.module.css'
const ToastContext = createContext(() => {})
export const TOAST_DEFAULT_DELAY_MS = 5000
const mapHidden = ({ id, tag }) => toast => {
// mark every previous toast with same tag as hidden
if (toast.tag === tag && toast.id !== id) return { ...toast, hidden: true }
return toast
}
export const ToastProvider = ({ children }) => {
const router = useRouter()
const [toasts, setToasts] = useState([])
const toastId = useRef(0)
2024-02-01 13:18:42 +00:00
Frontend payment UX cleanup (#1194) * Replace useInvoiceable with usePayment hook * Show WebLnError in QR code fallback * Fix missing removal of old zap undo code * Fix payment timeout message * Fix unused arg in super() * Also bail if invoice expired * Fix revert on reply error * Use JIT_INVOICE_TIMEOUT_MS constant * Remove unnecessary PaymentContext * Fix me as a dependency in FeeButtonContext * Fix anon sats added before act success * Optimistic updates for zaps * Fix modal not closed after custom zap * Optimistic update for custom zaps * Optimistic update for bounty payments * Consistent error handling for zaps and bounty payments * Optimistic update for poll votes * Use var balance in payment.request * Rename invoiceable to prepaid * Log cancelled invoices * Client notifications We now show notifications that are stored on the client to inform the user about following errors in the prepaid payment flow: - if a payment fails - if an invoice expires before it is paid - if a payment was interrupted (for example via page refresh) - if the action fails after payment * Remove unnecessary passing of act * Use AbortController for zap undos * Fix anon zap update not updating bolt color * Fix zap counted towards anon sats even if logged in * Fix duplicate onComplete call * Fix downzap type error * Fix "missing field 'path' while writing result" error * Pass full item in downzap props The previous commit fixed cache updates for downzaps but then the cache update for custom zaps failed because 'path' wasn't included in the server response. This commit is the proper fix. * Parse lnc rpc error messages * Add hash to InvoiceExpiredError
2024-05-28 17:18:54 +00:00
const removeToast = useCallback(({ id, tag }) => {
2024-02-01 13:18:42 +00:00
setToasts(toasts => toasts.filter(toast => {
2024-02-18 19:33:32 +00:00
if (toast.id === id) {
// remove the toast with the passed id with no exceptions
return false
2024-02-01 13:18:42 +00:00
}
2024-02-18 19:33:32 +00:00
const sameTag = tag && tag === toast.tag
Frontend payment UX cleanup (#1194) * Replace useInvoiceable with usePayment hook * Show WebLnError in QR code fallback * Fix missing removal of old zap undo code * Fix payment timeout message * Fix unused arg in super() * Also bail if invoice expired * Fix revert on reply error * Use JIT_INVOICE_TIMEOUT_MS constant * Remove unnecessary PaymentContext * Fix me as a dependency in FeeButtonContext * Fix anon sats added before act success * Optimistic updates for zaps * Fix modal not closed after custom zap * Optimistic update for custom zaps * Optimistic update for bounty payments * Consistent error handling for zaps and bounty payments * Optimistic update for poll votes * Use var balance in payment.request * Rename invoiceable to prepaid * Log cancelled invoices * Client notifications We now show notifications that are stored on the client to inform the user about following errors in the prepaid payment flow: - if a payment fails - if an invoice expires before it is paid - if a payment was interrupted (for example via page refresh) - if the action fails after payment * Remove unnecessary passing of act * Use AbortController for zap undos * Fix anon zap update not updating bolt color * Fix zap counted towards anon sats even if logged in * Fix duplicate onComplete call * Fix downzap type error * Fix "missing field 'path' while writing result" error * Pass full item in downzap props The previous commit fixed cache updates for downzaps but then the cache update for custom zaps failed because 'path' wasn't included in the server response. This commit is the proper fix. * Parse lnc rpc error messages * Add hash to InvoiceExpiredError
2024-05-28 17:18:54 +00:00
// remove toasts with same tag
return !sameTag
2024-02-01 13:18:42 +00:00
}))
}, [setToasts])
const dispatchToast = useCallback((toast) => {
toast = {
...toast,
createdAt: +new Date(),
id: toastId.current++
}
Frontend payment UX cleanup (#1194) * Replace useInvoiceable with usePayment hook * Show WebLnError in QR code fallback * Fix missing removal of old zap undo code * Fix payment timeout message * Fix unused arg in super() * Also bail if invoice expired * Fix revert on reply error * Use JIT_INVOICE_TIMEOUT_MS constant * Remove unnecessary PaymentContext * Fix me as a dependency in FeeButtonContext * Fix anon sats added before act success * Optimistic updates for zaps * Fix modal not closed after custom zap * Optimistic update for custom zaps * Optimistic update for bounty payments * Consistent error handling for zaps and bounty payments * Optimistic update for poll votes * Use var balance in payment.request * Rename invoiceable to prepaid * Log cancelled invoices * Client notifications We now show notifications that are stored on the client to inform the user about following errors in the prepaid payment flow: - if a payment fails - if an invoice expires before it is paid - if a payment was interrupted (for example via page refresh) - if the action fails after payment * Remove unnecessary passing of act * Use AbortController for zap undos * Fix anon zap update not updating bolt color * Fix zap counted towards anon sats even if logged in * Fix duplicate onComplete call * Fix downzap type error * Fix "missing field 'path' while writing result" error * Pass full item in downzap props The previous commit fixed cache updates for downzaps but then the cache update for custom zaps failed because 'path' wasn't included in the server response. This commit is the proper fix. * Parse lnc rpc error messages * Add hash to InvoiceExpiredError
2024-05-28 17:18:54 +00:00
setToasts(toasts => [...toasts, toast].map(mapHidden(toast)))
return () => removeToast(toast)
}, [setToasts, removeToast])
const toaster = useMemo(() => ({
2024-02-01 13:18:42 +00:00
success: (body, options) => {
const toast = {
body,
variant: 'success',
autohide: true,
delay: TOAST_DEFAULT_DELAY_MS,
2024-02-01 13:50:51 +00:00
tag: options?.tag || body,
2024-02-01 13:18:42 +00:00
...options
}
return dispatchToast(toast)
},
2024-02-01 13:18:42 +00:00
warning: (body, options) => {
const toast = {
body,
variant: 'warning',
autohide: true,
delay: TOAST_DEFAULT_DELAY_MS,
2024-02-01 13:50:51 +00:00
tag: options?.tag || body,
2024-02-01 13:18:42 +00:00
...options
}
return dispatchToast(toast)
},
2024-02-01 13:18:42 +00:00
danger: (body, options) => {
const toast = {
body,
variant: 'danger',
autohide: false,
2024-02-01 13:50:51 +00:00
tag: options?.tag || body,
2024-02-01 13:18:42 +00:00
...options
}
2024-02-01 13:18:42 +00:00
return dispatchToast(toast)
Frontend payment UX cleanup (#1194) * Replace useInvoiceable with usePayment hook * Show WebLnError in QR code fallback * Fix missing removal of old zap undo code * Fix payment timeout message * Fix unused arg in super() * Also bail if invoice expired * Fix revert on reply error * Use JIT_INVOICE_TIMEOUT_MS constant * Remove unnecessary PaymentContext * Fix me as a dependency in FeeButtonContext * Fix anon sats added before act success * Optimistic updates for zaps * Fix modal not closed after custom zap * Optimistic update for custom zaps * Optimistic update for bounty payments * Consistent error handling for zaps and bounty payments * Optimistic update for poll votes * Use var balance in payment.request * Rename invoiceable to prepaid * Log cancelled invoices * Client notifications We now show notifications that are stored on the client to inform the user about following errors in the prepaid payment flow: - if a payment fails - if an invoice expires before it is paid - if a payment was interrupted (for example via page refresh) - if the action fails after payment * Remove unnecessary passing of act * Use AbortController for zap undos * Fix anon zap update not updating bolt color * Fix zap counted towards anon sats even if logged in * Fix duplicate onComplete call * Fix downzap type error * Fix "missing field 'path' while writing result" error * Pass full item in downzap props The previous commit fixed cache updates for downzaps but then the cache update for custom zaps failed because 'path' wasn't included in the server response. This commit is the proper fix. * Parse lnc rpc error messages * Add hash to InvoiceExpiredError
2024-05-28 17:18:54 +00:00
}
}), [dispatchToast, removeToast])
2024-02-01 13:18:42 +00:00
// Only clear toasts with no cancel function on page navigation
// since navigation should not interfere with being able to cancel an action.
useEffect(() => {
Frontend payment UX cleanup (#1194) * Replace useInvoiceable with usePayment hook * Show WebLnError in QR code fallback * Fix missing removal of old zap undo code * Fix payment timeout message * Fix unused arg in super() * Also bail if invoice expired * Fix revert on reply error * Use JIT_INVOICE_TIMEOUT_MS constant * Remove unnecessary PaymentContext * Fix me as a dependency in FeeButtonContext * Fix anon sats added before act success * Optimistic updates for zaps * Fix modal not closed after custom zap * Optimistic update for custom zaps * Optimistic update for bounty payments * Consistent error handling for zaps and bounty payments * Optimistic update for poll votes * Use var balance in payment.request * Rename invoiceable to prepaid * Log cancelled invoices * Client notifications We now show notifications that are stored on the client to inform the user about following errors in the prepaid payment flow: - if a payment fails - if an invoice expires before it is paid - if a payment was interrupted (for example via page refresh) - if the action fails after payment * Remove unnecessary passing of act * Use AbortController for zap undos * Fix anon zap update not updating bolt color * Fix zap counted towards anon sats even if logged in * Fix duplicate onComplete call * Fix downzap type error * Fix "missing field 'path' while writing result" error * Pass full item in downzap props The previous commit fixed cache updates for downzaps but then the cache update for custom zaps failed because 'path' wasn't included in the server response. This commit is the proper fix. * Parse lnc rpc error messages * Add hash to InvoiceExpiredError
2024-05-28 17:18:54 +00:00
const handleRouteChangeStart = () => setToasts(toasts => toasts.length > 0 ? toasts.filter(({ persistOnNavigate }) => persistOnNavigate) : toasts)
router.events.on('routeChangeStart', handleRouteChangeStart)
return () => {
router.events.off('routeChangeStart', handleRouteChangeStart)
}
}, [router.events, setToasts])
2024-02-01 13:18:42 +00:00
// this function merges toasts with the same tag into one toast.
// for example: 3x 'zap pending' -> '(3) zap pending'
const tagReducer = (toasts, toast) => {
const { tag } = toast
// has tag?
if (!tag) return [...toasts, toast]
// existing tag?
const idx = toasts.findIndex(toast => toast.tag === tag)
if (idx === -1) return [...toasts, toast]
// merge toasts with same tag
const prevToast = toasts[idx]
let { amount } = prevToast
2024-02-01 13:18:42 +00:00
amount = amount ? amount + 1 : 2
const body = `(${amount}) ${toast.body}`
2024-02-01 13:18:42 +00:00
return [
...toasts.slice(0, idx),
{ ...toast, amount, body },
2024-02-01 13:18:42 +00:00
...toasts.slice(idx + 1)
]
}
// only show toast with highest ID of each tag
const visibleToasts = toasts.reduce(tagReducer, [])
return (
<ToastContext.Provider value={toaster}>
<ToastContainer className={`pb-3 pe-3 ${styles.toastContainer}`} position='bottom-end' containerPosition='fixed'>
2024-02-01 13:18:42 +00:00
{visibleToasts.map(toast => {
const textStyle = toast.variant === 'warning' ? 'text-dark' : ''
const onClose = () => {
toast.onClose?.()
removeToast(toast)
}
// a toast is unhidden if it was hidden before since it now gets rendered
const unhidden = toast.hidden
// we only need to start the animation at a different timing when it was hidden by another toast before.
// if we don't do this, the animation for rerendered toasts skips ahead and toast delay and animation get out of sync.
const elapsed = (+new Date() - toast.createdAt)
const animationDelay = unhidden ? `-${elapsed}ms` : undefined
const animationDuration = `${toast.delay}ms`
2024-02-01 13:18:42 +00:00
return (
<Toast
key={toast.id} bg={toast.variant} show autohide={toast.autohide}
2024-02-15 22:49:54 +00:00
delay={toast.delay} className={`${styles.toast} ${styles[toast.variant]} ${textStyle}`} onClose={() => removeToast(toast)}
2024-02-01 13:18:42 +00:00
>
<ToastBody>
<div className='d-flex align-items-center'>
<div className='flex-grow-1'>{toast.body}</div>
<Button
variant={null}
className='p-0 ps-2'
aria-label='close'
onClick={onClose}
Frontend payment UX cleanup (#1194) * Replace useInvoiceable with usePayment hook * Show WebLnError in QR code fallback * Fix missing removal of old zap undo code * Fix payment timeout message * Fix unused arg in super() * Also bail if invoice expired * Fix revert on reply error * Use JIT_INVOICE_TIMEOUT_MS constant * Remove unnecessary PaymentContext * Fix me as a dependency in FeeButtonContext * Fix anon sats added before act success * Optimistic updates for zaps * Fix modal not closed after custom zap * Optimistic update for custom zaps * Optimistic update for bounty payments * Consistent error handling for zaps and bounty payments * Optimistic update for poll votes * Use var balance in payment.request * Rename invoiceable to prepaid * Log cancelled invoices * Client notifications We now show notifications that are stored on the client to inform the user about following errors in the prepaid payment flow: - if a payment fails - if an invoice expires before it is paid - if a payment was interrupted (for example via page refresh) - if the action fails after payment * Remove unnecessary passing of act * Use AbortController for zap undos * Fix anon zap update not updating bolt color * Fix zap counted towards anon sats even if logged in * Fix duplicate onComplete call * Fix downzap type error * Fix "missing field 'path' while writing result" error * Pass full item in downzap props The previous commit fixed cache updates for downzaps but then the cache update for custom zaps failed because 'path' wasn't included in the server response. This commit is the proper fix. * Parse lnc rpc error messages * Add hash to InvoiceExpiredError
2024-05-28 17:18:54 +00:00
><div className={`${styles.toastClose} ${textStyle}`}>X</div>
2024-02-01 13:18:42 +00:00
</Button>
</div>
</ToastBody>
{toast.progressBar && <div className={`${styles.progressBar} ${styles[toast.variant]}`} style={{ animationDuration, animationDelay }} />}
2024-02-01 13:18:42 +00:00
</Toast>
)
})}
</ToastContainer>
{children}
</ToastContext.Provider>
)
}
export const useToast = () => useContext(ToastContext)