235 lines
7.8 KiB
TypeScript
235 lines
7.8 KiB
TypeScript
import classNames from 'classnames'
|
|
import { ReactNode } from 'react'
|
|
import { Slide, ToastContainer, toast as toastify } from 'react-toastify'
|
|
import { mutate } from 'swr'
|
|
|
|
import { CheckMark } from 'components/common/CheckMark'
|
|
import { CircularProgress } from 'components/common/CircularProgress'
|
|
import { ChevronDown, Cross, CrossCircled, ExternalLink } from 'components/common/Icons'
|
|
import Text from 'components/common/Text'
|
|
import { TextLink } from 'components/common/TextLink'
|
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
|
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
|
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
|
|
import useChainConfig from 'hooks/useChainConfig'
|
|
import useTransactionStore from 'hooks/useTransactionStore'
|
|
import useStore from 'store'
|
|
import { formatAmountWithSymbol } from 'utils/formatters'
|
|
import { BN } from 'utils/helpers'
|
|
|
|
const toastBodyClasses = classNames(
|
|
'flex flex-wrap w-full group/transaction',
|
|
'rounded-sm p-4 shadow-overlay backdrop-blur-lg',
|
|
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-sm before:p-[1px] before:border-glas',
|
|
)
|
|
|
|
function isPromise(object?: any): object is ToastPending {
|
|
if (!object) return false
|
|
return 'promise' in object
|
|
}
|
|
|
|
export function generateToastContent(content: ToastSuccess['content'], assets: Asset[]): ReactNode {
|
|
return content.map((item, index) => (
|
|
<div className='flex flex-wrap w-full mb-1' key={index}>
|
|
{item.text && (
|
|
<>
|
|
<Text size='sm' className='w-full mb-1 text-white'>
|
|
{item.text}
|
|
</Text>
|
|
{item.coins.length > 0 && (
|
|
<ul className='flex flex-wrap w-full gap-1 p-1 pl-4 list-disc'>
|
|
{item.coins.map((coin, index) => {
|
|
let prefix = ''
|
|
if (item.text === 'Swapped') prefix = index === 0 ? 'from ' : 'to '
|
|
|
|
return BN(coin.amount).isZero() ? null : (
|
|
<li className='w-full p-0 text-sm text-white' key={coin.denom}>
|
|
{`${prefix}${formatAmountWithSymbol(coin, assets)}`}
|
|
</li>
|
|
)
|
|
})}
|
|
</ul>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
))
|
|
}
|
|
|
|
export default function Toaster() {
|
|
const [reduceMotion] = useLocalStorage<boolean>(
|
|
LocalStorageKeys.REDUCE_MOTION,
|
|
DEFAULT_SETTINGS.reduceMotion,
|
|
)
|
|
const chainConfig = useChainConfig()
|
|
|
|
const toast = useStore((s) => s.toast)
|
|
const { addTransaction } = useTransactionStore()
|
|
|
|
const handlePending = (toast: ToastPending) => {
|
|
const Content = () => (
|
|
<div className='relative flex flex-wrap w-full m-0 isolate'>
|
|
<div className='flex items-center w-full gap-2 mb-2'>
|
|
<div className='rounded-sm p-1.5 pt-1 bg-info w-7 h-7 flex items-center'>
|
|
<CircularProgress size={16} />
|
|
</div>
|
|
<Text className='flex items-center font-bold text-info'>Pending Transaction</Text>
|
|
</div>
|
|
<Text size='sm' className='w-full text-white'>
|
|
Approve the transaction
|
|
<br />
|
|
and wait for its confirmation.
|
|
</Text>
|
|
</div>
|
|
)
|
|
|
|
toastify(Content, {
|
|
toastId: toast.id,
|
|
className: classNames(toastBodyClasses, 'toast-pending'),
|
|
icon: false,
|
|
draggable: false,
|
|
closeOnClick: false,
|
|
hideProgressBar: true,
|
|
autoClose: false,
|
|
})
|
|
}
|
|
|
|
const handleResponse = (toast: ToastResponse, details?: boolean) => {
|
|
const isError = toast?.isError
|
|
if (!isError && toast.accountId) addTransaction(toast)
|
|
const generalMessage = isError ? 'Transaction failed!' : 'Transaction completed successfully!'
|
|
const showDetailElement = !!(!details && toast.hash)
|
|
const address = useStore.getState().address
|
|
|
|
let target: string
|
|
if (!isError) {
|
|
target = toast.accountId === address ? 'Red Bank' : `Credit Account ${toast.accountId}`
|
|
}
|
|
|
|
const Msg = () => (
|
|
<div className='relative flex flex-wrap w-full m-0 isolate'>
|
|
<div className='flex w-full gap-2 mb-2'>
|
|
{isError ? (
|
|
<div className='rounded-sm p-1.5 bg-error'>
|
|
<span className='block w-4 h-4 text-white'>
|
|
<CrossCircled />
|
|
</span>
|
|
</div>
|
|
) : (
|
|
<div className='rounded-sm p-1.5 pt-1 bg-success w-7 h-7 flex items-center'>
|
|
<CheckMark size={16} />
|
|
</div>
|
|
)}
|
|
<Text
|
|
className={classNames(
|
|
'flex items-center font-bold',
|
|
isError ? 'text-error' : 'text-success',
|
|
)}
|
|
>
|
|
{isError ? (toast.title ? toast.title : 'Error') : 'Success'}
|
|
</Text>
|
|
</div>
|
|
<Text size='sm' className='w-full mb-2 text-white'>
|
|
{showDetailElement ? generalMessage : toast.message}
|
|
</Text>
|
|
{showDetailElement && (
|
|
<Text
|
|
size='sm'
|
|
className='flex items-center w-auto pb-0.5 text-white border-b border-white/40 border-dashed group-hover/transaction:opacity-0'
|
|
>
|
|
<ChevronDown className='w-3 mr-1' />
|
|
Transaction Details
|
|
</Text>
|
|
)}
|
|
<div
|
|
className={classNames(
|
|
'w-full flex-wrap',
|
|
showDetailElement && 'hidden group-hover/transaction:flex',
|
|
)}
|
|
>
|
|
{!isError && toast.accountId && (
|
|
<Text className='mb-1 font-bold text-white'>{target}</Text>
|
|
)}
|
|
{showDetailElement && toast.message && (
|
|
<Text size='sm' className='w-full mb-1 text-white'>
|
|
{toast.message}
|
|
</Text>
|
|
)}
|
|
{!isError &&
|
|
toast.content?.length > 0 &&
|
|
generateToastContent(toast.content, chainConfig.assets)}
|
|
{toast.hash && (
|
|
<div className='w-full'>
|
|
<TextLink
|
|
href={`${chainConfig.endpoints.explorer}${toast.hash}`}
|
|
target='_blank'
|
|
className={classNames(
|
|
'leading-4 underline mt-4 hover:no-underline hover:text-white',
|
|
isError ? 'text-error' : 'text-success',
|
|
)}
|
|
title={`View on ${chainConfig.explorerName}`}
|
|
>
|
|
{`View on ${chainConfig.explorerName}`}
|
|
<ExternalLink className='-mt-0.5 ml-2 inline w-3.5' />
|
|
</TextLink>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className='absolute top-0 right-0'>
|
|
<Cross className={classNames('h-2 w-2', isError ? 'text-error' : 'text-success')} />
|
|
</div>
|
|
</div>
|
|
)
|
|
|
|
const toastElement = document.getElementById(String(toast.id))
|
|
|
|
if (toastElement) {
|
|
toastify.update(toast.id, {
|
|
render: Msg,
|
|
className: toastBodyClasses,
|
|
type: isError ? 'error' : 'success',
|
|
icon: false,
|
|
draggable: false,
|
|
closeOnClick: true,
|
|
autoClose: 5000,
|
|
progressClassName: classNames('h-[1px] bg-none', isError ? 'bg-error' : 'bg-success'),
|
|
hideProgressBar: false,
|
|
})
|
|
} else {
|
|
toastify(Msg, {
|
|
toastId: toast.id,
|
|
className: toastBodyClasses,
|
|
type: isError ? 'error' : 'success',
|
|
icon: false,
|
|
draggable: false,
|
|
closeOnClick: true,
|
|
autoClose: 5000,
|
|
progressClassName: classNames('h-[1px] bg-none', isError ? 'bg-error' : 'bg-success'),
|
|
})
|
|
}
|
|
|
|
useStore.setState({ toast: null })
|
|
mutate(() => true)
|
|
}
|
|
|
|
if (toast) {
|
|
if (isPromise(toast)) {
|
|
handlePending(toast)
|
|
} else {
|
|
handleResponse(toast)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ToastContainer
|
|
closeButton={false}
|
|
position='top-right'
|
|
newestOnTop
|
|
closeOnClick={false}
|
|
transition={reduceMotion ? undefined : Slide}
|
|
bodyClassName='p-0 m-0 -z-1'
|
|
className='mt-[81px] p-0'
|
|
/>
|
|
)
|
|
}
|