-
Notifications
You must be signed in to change notification settings - Fork 0
SOV-5223: tx dialog #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4637fdf
b363b35
eb69817
ae86c03
728e402
2cf51b1
91decc8
a557f94
b6476f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,9 @@ | ||
| { | ||
| "accept": "Accept", | ||
| "cancel": "Cancel", | ||
| "close": "Close" | ||
| "close": "Close", | ||
| "confirm": "Confirm", | ||
| "continue": "Continue", | ||
| "abort": "Abort", | ||
| "loading": "Loading..." | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "title": "Transaction Confirmation", | ||
| "description": "Please review and confirm transactions in your wallet", | ||
| "preparing": "Preparing transaction...", | ||
| "connectWallet": "Connect your wallet to proceed.", | ||
| "switchNetwork": "Switch to {{name}} network", | ||
| "signMessage": "Sign Message", | ||
| "signTypedData": "Sign Typed Data", | ||
| "sendTransaction": "Send Transaction" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { useMemo } from 'react'; | ||
| import type { Address, Chain, Hash } from 'viem'; | ||
| import { useChains } from 'wagmi'; | ||
|
|
||
| type ChainProps = | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Create
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think moving it to another file just increases compilation time but does not add any benefit as these types aren't supposed to be used anywhere else atm |
||
| | { | ||
| chainId: number; | ||
| } | ||
| | { | ||
| chain: Chain; | ||
| }; | ||
|
|
||
| type ValueProps = | ||
| | { | ||
| address: Address; | ||
| } | ||
| | { | ||
| txHash: Hash; | ||
| }; | ||
|
|
||
| type LinkToExplorerProps = ValueProps & | ||
| ChainProps & { | ||
| className?: string; | ||
| }; | ||
|
|
||
| export const LinkToExplorer = (props: LinkToExplorerProps) => { | ||
| const chains = useChains(); | ||
|
|
||
| const chain = useMemo(() => { | ||
| if ('chain' in props) { | ||
| return props.chain; | ||
| } | ||
| return chains.find((c) => c.id === props.chainId); | ||
| }, [props, chains]); | ||
|
|
||
| const path = useMemo(() => { | ||
| if ('address' in props) { | ||
| return `/address/${props.address}`; | ||
| } | ||
| if ('txHash' in props) { | ||
| return `/tx/${props.txHash}`; | ||
| } | ||
| return '/'; | ||
| }, [props]); | ||
|
|
||
| const title = useMemo(() => { | ||
| if ('address' in props) { | ||
| return props.address; | ||
| } | ||
| if ('txHash' in props) { | ||
| return props.txHash; | ||
| } | ||
| }, [props, chain]); | ||
|
|
||
| return ( | ||
| <a | ||
| href={chain?.blockExplorers?.default.url + path} | ||
| className={props.className} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| > | ||
| {title?.slice(0, 6)}...{title?.slice(-4)} | ||
| </a> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { txStore } from '@/lib/transactions/store'; | ||
| import clsx from 'clsx'; | ||
| import { Loader2Icon } from 'lucide-react'; | ||
| import { useTranslation } from 'react-i18next'; | ||
| import { useStoreWithEqualityFn } from 'zustand/traditional'; | ||
| import { | ||
| Dialog, | ||
| DialogContent, | ||
| DialogDescription, | ||
| DialogHeader, | ||
| DialogTitle, | ||
| } from '../ui/dialog'; | ||
| import { TxList } from './TxList'; | ||
|
|
||
| export const TransactionDialogProvider = () => { | ||
| const { t } = useTranslation('tx'); | ||
|
|
||
| const [isOpen, isReady] = useStoreWithEqualityFn( | ||
| txStore, | ||
| (state) => [state.isFetching || state.isReady, state.isReady] as const, | ||
| ); | ||
|
|
||
| const onClose = (open: boolean) => { | ||
| if (!open) { | ||
| txStore.getState().reset(); | ||
| } | ||
| }; | ||
|
|
||
| const handleEscapes = (e: Event) => { | ||
tiltom marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (!isReady) { | ||
| txStore.getState().reset(); | ||
| return; | ||
| } | ||
| e.preventDefault(); | ||
| }; | ||
|
|
||
| return ( | ||
| <Dialog open={isOpen} onOpenChange={onClose}> | ||
| <DialogContent | ||
| className={clsx(!isReady && 'w-64 h-64')} | ||
| onInteractOutside={handleEscapes} | ||
| onEscapeKeyDown={handleEscapes} | ||
| > | ||
| {isReady ? ( | ||
| <TxList /> | ||
| ) : ( | ||
| <> | ||
| <DialogHeader className="sr-only"> | ||
| <DialogTitle>{t(($) => $.title)}</DialogTitle> | ||
| <DialogDescription>{t(($) => $.description)}</DialogDescription> | ||
| </DialogHeader> | ||
| <div className="flex flex-col justify-center items-center gap-4"> | ||
| <Loader2Icon className="mr-2 animate-spin" size={48} /> | ||
| <p className="text-sm">{t(($) => $.preparing)}</p> | ||
| </div> | ||
| </> | ||
| )} | ||
| </DialogContent> | ||
| </Dialog> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { TRANSACTION_STATE, type SlayerTx } from '@/lib/transactions/store'; | ||
| import { isTransactionRequest } from '@sovryn/slayer-sdk'; | ||
| import { | ||
| CircleCheckBig, | ||
| CircleDashed, | ||
| CircleX, | ||
| ExternalLink, | ||
| Loader2Icon, | ||
| } from 'lucide-react'; | ||
| import { useMemo, type FC } from 'react'; | ||
| import { useChains } from 'wagmi'; | ||
| import { LinkToExplorer } from '../LinkToExplorer/LinkToExplorer'; | ||
|
|
||
| type TransactionItemProps = { | ||
| index: number; | ||
| item: SlayerTx; | ||
| }; | ||
|
|
||
| export const TransactionItem: FC<TransactionItemProps> = ({ item, index }) => { | ||
| const isTx = isTransactionRequest(item); | ||
| const chains = useChains(); | ||
| const chain = useMemo(() => { | ||
| if (isTx) { | ||
| const chainId = item.request.data.chain?.id; | ||
| return chains.find((c) => c.id === chainId); | ||
| } | ||
| return undefined; | ||
| }, [chains, isTx, item.request.data]); | ||
|
|
||
| return ( | ||
| <div className="flex flex-row justify-start items-start gap-4 mb-3"> | ||
| <div className="w-8 shrink-0 grow-0 text-center"> | ||
| <div className="flex flex-col items-center gap-1"> | ||
| {item.state === TRANSACTION_STATE.idle && <CircleDashed size={24} />} | ||
| {item.state === TRANSACTION_STATE.pending && ( | ||
| <Loader2Icon className="animate-spin" size={24} /> | ||
| )} | ||
| {item.state === TRANSACTION_STATE.success && ( | ||
| <CircleCheckBig size={24} className="text-green-500" /> | ||
| )} | ||
| {item.state === TRANSACTION_STATE.error && ( | ||
| <CircleX size={24} className="text-red-500" /> | ||
| )} | ||
| <div className="text-xs">#{index + 1}</div> | ||
| </div> | ||
| </div> | ||
| <div className="grow"> | ||
| <p>{item.title}</p> | ||
| <p className="text-sm">{item.description}</p> | ||
| {isTx && chain && item.res?.transactionHash && ( | ||
| <p className="text-sm flex flex-row justify-start gap-2 items-center mt-2"> | ||
| <ExternalLink size={16} /> | ||
| <LinkToExplorer chain={chain} txHash={item.res.transactionHash} /> | ||
| </p> | ||
| )} | ||
|
|
||
| {item.error && ( | ||
| <p className="mt-2 text-xs text-red-500">{item.error}</p> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; |
Uh oh!
There was an error while loading. Please reload this page.