Files
nym/explorer-nextjs/app/delegations/page.tsx
Fouad e5f41731ae Explorer NextJS Rebuild (#4534)
* bootstrap next app + add overview page

* fix AssetList type

* fix up nav stuff

* Refactor Nav component and add network components pages

* Refactor WorldMap component and update TelegramIcon, GitHubIcon, NymVpnIcon, DiscordIcon, and TwitterIcon components

* add service providers page

* mixnodes page

* delegations page + use material react table for all tables

* nodes map page

* Refactor StyledLink component and remove unnecessary console.log statements

* Refactor ESLint configuration, remove unused dependencies, and update component imports

* update deps

* Refactor imports and update dependencies

* fix dark mode

* build single mixnode page

* build single gateway page

* Refactor handleOnDelegate function to use useCallback in mixnodes page.tsx

* Add defaults for constants

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2024-05-16 16:12:06 +01:00

312 lines
7.8 KiB
TypeScript

'use client'
import React, { useCallback, useEffect, useMemo } from 'react'
import {
Alert,
AlertTitle,
Box,
Button,
Card,
Chip,
IconButton,
Tooltip,
Typography,
} from '@mui/material'
import { DelegationModal, DelegationModalProps, Title } from '@/app/components'
import { useWalletContext } from '@/app/context/wallet'
import { unymToNym } from '@/app/utils/currency'
import {
DelegationWithRewards,
DelegationsProvider,
PendingEvent,
useDelegationsContext,
} from '@/app/context/delegations'
import { urls } from '@/app/utils'
import { useClipboard } from 'use-clipboard-copy'
import { Close } from '@mui/icons-material'
import { useRouter } from 'next/navigation'
import {
MRT_ColumnDef,
MaterialReactTable,
useMaterialReactTable,
} from 'material-react-table'
const mapToDelegationsRow = (
delegation: DelegationWithRewards,
index: number
) => ({
identity: delegation.identityKey,
mix_id: delegation.mix_id,
amount: `${unymToNym(delegation.amount.amount)} NYM`,
rewards: `${unymToNym(delegation.rewards)} NYM`,
id: index,
pending: delegation.pending,
})
const Banner = ({ onClose }: { onClose: () => void }) => {
const { copy } = useClipboard()
return (
<Alert
severity="info"
sx={{ mb: 3, fontSize: 'medium', width: '100%' }}
action={
<IconButton size="small" onClick={onClose}>
<Close fontSize="small" />
</IconButton>
}
>
<AlertTitle> Mobile Delegations Beta</AlertTitle>
<Box>
<Typography>
This is a beta release for mobile delegations If you have any feedback
or feature suggestions contact us at support@nymte.ch
<Button
size="small"
onClick={() => copy('support@nymte.ch')}
sx={{ display: 'inline-block' }}
>
Copy
</Button>
</Typography>
</Box>
</Alert>
)
}
const DelegationsPage = () => {
const [confirmationModalProps, setConfirmationModalProps] = React.useState<
DelegationModalProps | undefined
>()
const [isLoading, setIsLoading] = React.useState(false)
const [showBanner, setShowBanner] = React.useState(true)
const { isWalletConnected } = useWalletContext()
const { handleGetDelegations, handleUndelegate, delegations } =
useDelegationsContext()
const router = useRouter()
useEffect(() => {
let timeoutId: NodeJS.Timeout
const fetchDelegations = async () => {
setIsLoading(true)
try {
await handleGetDelegations()
} catch (error) {
setConfirmationModalProps({
status: 'error',
message: "Couldn't fetch delegations. Please try again later.",
})
} finally {
setIsLoading(false)
timeoutId = setTimeout(() => {
fetchDelegations()
}, 60_000)
}
}
fetchDelegations()
return () => {
clearTimeout(timeoutId)
}
}, [handleGetDelegations])
const getTooltipTitle = (pending: PendingEvent) => {
if (pending?.kind === 'undelegate') {
return 'You have an undelegation pending'
}
if (pending?.kind === 'delegate') {
return `You have a delegation pending worth ${unymToNym(
pending.amount.amount
)} NYM`
}
return undefined
}
const onUndelegate = useCallback(
async (mixId: number) => {
setConfirmationModalProps({ status: 'loading' })
try {
const tx = await handleUndelegate(mixId)
if (tx) {
setConfirmationModalProps({
status: 'success',
message: 'Undelegation can take up to one hour to process',
transactions: [
{
url: `${urls('MAINNET').blockExplorer}/transaction/${
tx.transactionHash
}`,
hash: tx.transactionHash,
},
],
})
}
} catch (error) {
if (error instanceof Error) {
setConfirmationModalProps({ status: 'error', message: error.message })
}
}
},
[handleUndelegate]
)
const columns = useMemo<
MRT_ColumnDef<ReturnType<typeof mapToDelegationsRow>>[]
>(() => {
return [
{
id: 'delegations-data',
header: 'Delegations Data',
columns: [
{
id: 'identity',
accessorKey: 'identity',
header: 'Identity Key',
width: 400,
},
{
id: 'mix_id',
accessorKey: 'mix_id',
header: 'Mix ID',
size: 150,
},
{
id: 'amount',
accessorKey: 'amount',
header: 'Amount',
width: 150,
},
{
id: 'rewards',
accessorKey: 'rewards',
header: 'Rewards',
width: 150,
enableColumnFilters: false,
},
{
id: 'undelegate',
accessorKey: 'undelegate',
header: '',
enableSorting: false,
enableColumnActions: false,
Filter: () => null,
Cell: ({ row }) => {
return (
<Box
sx={{ width: '100%', display: 'flex', justifyContent: 'end' }}
>
{row.original.pending ? (
<Tooltip
placement="left"
title={getTooltipTitle(row.original.pending)}
onClick={(e) => e.stopPropagation()}
PopperProps={{}}
>
<Chip size="small" label="Pending events" />
</Tooltip>
) : (
<Button
size="small"
variant="outlined"
onClick={(e) => {
e.stopPropagation()
onUndelegate(row.original.mix_id)
}}
>
Undelegate
</Button>
)}
</Box>
)
},
},
],
},
]
}, [onUndelegate])
const data = useMemo(() => {
return (delegations || []).map(mapToDelegationsRow)
}, [delegations])
const table = useMaterialReactTable({
columns,
data,
enableFullScreenToggle: false,
state: {
isLoading,
},
initialState: {
columnPinning: { right: ['undelegate'] },
},
})
return (
<Box>
{confirmationModalProps && (
<DelegationModal
{...confirmationModalProps}
open={Boolean(confirmationModalProps)}
onClose={async () => {
if (confirmationModalProps.status === 'success') {
await handleGetDelegations()
}
setConfirmationModalProps(undefined)
}}
sx={{
width: {
xs: '90%',
sm: 600,
},
}}
/>
)}
{showBanner && <Banner onClose={() => setShowBanner(false)} />}
<Box display="flex" justifyContent="space-between" alignItems="center">
<Title text="Your Delegations" />
<Button
variant="contained"
color="primary"
onClick={() => router.push('/network-components/mixnodes')}
>
Delegate
</Button>
</Box>
{!isWalletConnected ? (
<Box>
<Typography mb={2} variant="h6">
Connect your wallet to view your delegations.
</Typography>
</Box>
) : null}
<Card
sx={{
mt: 2,
padding: 2,
height: '100%',
}}
>
<MaterialReactTable table={table} />
</Card>
</Box>
)
}
const Delegations = () => (
<DelegationsProvider>
<DelegationsPage />
</DelegationsProvider>
)
export default Delegations