synapsecns/sanguine

View on GitHub
packages/explorer-ui/pages/tx/[kappa].tsx

Summary

Maintainability
F
4 days
Test Coverage
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
import { TRANSACTIONS_PATH, ACCOUNTS_PATH } from '@urls'
import { ChainInfo } from '@components/misc/ChainInfo'
import { Error } from '@components/Error'
import { StandardPageContainer } from '@components/layouts/StandardPageContainer'
import { useRouter } from 'next/router'
import { CHAINS } from '@synapsecns/synapse-constants'
import { GET_BRIDGE_TRANSACTIONS_QUERY } from '@graphql/queries'
import { API_URL } from '@graphql'
import { HorizontalDivider } from '@components/misc/HorizontalDivider'
import { formatDateTimestamp } from '@utils/formatDate'
import { IconAndAmount } from '@components/misc/IconAndAmount'
import { addressToSymbol } from '@utils/addressToSymbol'
const CHAINS_BY_ID = CHAINS.CHAINS_BY_ID

const link = new HttpLink({
  uri: API_URL,
  useGETForQueries: true,
})

const client = new ApolloClient({
  link,
  cache: new InMemoryCache(),
})

const truncateAddress = (address) => {
  return `${address.slice(0, 6)}...${address.slice(-4)}`
}

const truncateHash = (hash) => {
  return `${hash.slice(0, 8)}...${hash.slice(-5)}`
}

export const BridgeTransaction = ({ queryResult }) => {
  const router = useRouter()
  const { kappa } = router.query
  const transaction = queryResult.bridgeTransactions[0]
  const { pending, fromInfo, toInfo } = transaction

  // Get time taken to complete tx w/ appropriate units.
  const getTimeElapsedStr = (start, end) => {
    const diff = end - start
    if (diff <= 0) {
      return '1 second'
    }
    return diff === 1 ? '1 second' : `${diff} seconds`
  }
  let content

  if (!!transaction) {
    content = (
      <>
        <div className="flex flex-row pb-12 mt-5 mb-1 text-white">
          <a
            className="text-[#99E6FF] cursor-pointer mr-2"
            href={TRANSACTIONS_PATH}
          >
            ← Explorer
          </a>
          <span className="mr-2 text-gray-500">/</span>
          <span
            className="text-white cursor-pointer hover:text-gray-500"
            onClick={() => {
              try {
                navigator.clipboard
                  .writeText(String(kappa))
                  .then(() => '')
                  .catch((err) => alert('Failed to copy text: ' + err))
              } catch (err) {
                alert('Clipboard not supported on this browser')
              }
            }}
          >
            <span className="hidden sm:inline">{kappa}</span>
            <span className="sm:hidden">{truncateAddress(kappa)}</span>
          </span>
        </div>
        <div className="py-4 border border-[#252537] rounded-xl w-4/5 mx-auto">
          <div className="flex flex-col px-4 gap-y-2">
            <div className="flex flex-col mt-4">
              <div className="flex flex-col">
                <div className="flex items-center justify-between w-full">
                  <div className="flex gap-x-[1.8rem] py-1">
                    <p className="w-24 text-white text-opacity-60">Timestamp</p>
                    <p className="text-white">
                      {formatDateTimestamp(new Date(fromInfo.time * 1000))}
                    </p>
                  </div>
                  <div className="flex gap-x-[1.8rem] py-1">
                    <p className="hidden text-white sm:block">
                      {new Date(fromInfo.time * 1000)
                        .toISOString()
                        .replace('T', ' ')
                        .slice(0, 16) + ' UTC'}
                    </p>
                  </div>
                </div>

                <div className="flex gap-x-[1.8rem] py-1">
                  <p className="w-24 text-white text-opacity-60">Status</p>
                  <p className="text-white ">
                    {pending ? 'Pending' : 'Confirmed'}{' '}
                  </p>
                </div>
                <div className="flex gap-x-[1.8rem] py-1 pb-4">
                  <p className="w-24 text-white text-opacity-60">Elapsed</p>
                  <p className="text-white ">
                    {toInfo
                      ? getTimeElapsedStr(fromInfo.time, toInfo.time)
                      : '--'}{' '}
                  </p>
                </div>
                <HorizontalDivider className="mx-[-1rem] bg-[#252537]" />
                <div className="flex gap-x-[1.8rem] py-1 pt-4">
                  <p className="w-24 text-white text-opacity-60">From</p>
                  <a
                    target="_blank"
                    rel="noreferrer"
                    className="text-sm text-white break-all cursor-pointer hover:text-gray-500"
                    href={ACCOUNTS_PATH + '/' + fromInfo.address}
                  >
                    <span className="hidden sm:inline">{fromInfo.address}</span>
                    <span className="sm:hidden">
                      {truncateAddress(fromInfo.address)}
                    </span>
                  </a>
                </div>
                <div className="flex gap-x-[1.8rem] py-1 pb-4">
                  <p className="w-24 text-white text-opacity-60">To</p>
                  <a
                    target="_blank"
                    rel="noreferrer"
                    className="text-sm text-white break-all cursor-pointer hover:text-gray-500"
                    href={toInfo ? ACCOUNTS_PATH + '/' + toInfo.address : ''}
                  >
                    {toInfo ? (
                      <>
                        <span className="hidden sm:inline">
                          {toInfo.address}
                        </span>
                        <span className="sm:hidden">
                          {truncateAddress(toInfo.address)}
                        </span>
                      </>
                    ) : (
                      '--'
                    )}
                  </a>
                </div>
                <HorizontalDivider className="mx-[-1rem] bg-[#252537]" />

                <div className="flex gap-x-[1.8rem] pt-4">
                  <p className="w-24 text-white text-opacity-60">Sent</p>
                  <div className="flex flex-col items-center sm:flex-row">
                    <IconAndAmount
                      value={fromInfo.value}
                      tokenAddress={fromInfo.tokenAddress}
                      chainId={fromInfo.chainID}
                      tokenSymbol={addressToSymbol({
                        tokenAddress: fromInfo.tokenAddress,
                        chainId: fromInfo.chainID,
                      })}
                      iconSize="w-4 h-4"
                      // textSize="text-sm"
                      // styledCoin={true}
                    />
                    <div className="flex flex-col items-center sm:flex-row">
                      <span className="px-2 text-white text-opacity-60">
                        on
                      </span>
                      <ChainInfo
                        chainId={fromInfo?.chainID}
                        noLink={true}
                        imgClassName="w-4 h-4 rounded-full"
                      />
                    </div>
                  </div>
                </div>
                <HorizontalDivider className="mx-[-1rem] bg-[#252537] block sm:hidden my-4" />
                <div className="flex gap-x-[1.8rem] mt-3 pb-1 items-center">
                  <p className="w-24 text-white text-opacity-60">Received</p>
                  {toInfo ? (
                    <div className="flex flex-col items-center sm:flex-row">
                      <IconAndAmount
                        value={toInfo.value}
                        tokenAddress={toInfo.tokenAddress}
                        chainId={toInfo.chainID}
                        tokenSymbol={addressToSymbol({
                          tokenAddress: toInfo.tokenAddress,
                          chainId: toInfo.chainID,
                        })}
                        iconSize="w-4 h-4"
                        // textSize="text-sm"
                        // styledCoin={true}
                      />
                      <div className="flex flex-col items-center sm:flex-row">
                        <span className="px-2 text-white text-opacity-60">
                          on
                        </span>
                        <ChainInfo
                          chainId={toInfo?.chainID}
                          noLink={true}
                          imgClassName="w-4 h-4 rounded-full"
                        />
                      </div>
                    </div>
                  ) : (
                    <p className="text-sm text-white break-all">--</p>
                  )}
                </div>
              </div>
              <br />

              <HorizontalDivider className="mx-[-1rem] bg-[#252537]" />

              <div className="flex flex-col mt-8">
                <div className="flex gap-x-[1.8rem] py-1">
                  <p className="w-24 text-white text-opacity-60">Txn Hash</p>
                  <p className="text-sm text-white break-all">
                    <span className="hidden sm:inline">{kappa}</span>
                    <span className="sm:hidden">{truncateHash(kappa)}</span>
                  </p>
                </div>

                <div className="flex gap-x-[1.8rem] py-1">
                  <p className="w-24 text-white text-opacity-60">From Txn</p>
                  {fromInfo ? (
                    <a
                      target="_blank"
                      rel="noreferrer"
                      className="text-sm text-white break-all cursor-pointer hover:text-gray-500"
                      href={
                        CHAINS_BY_ID[fromInfo.chainID]?.explorerUrl +
                        '/tx/' +
                        fromInfo.hash
                      }
                    >
                      <span className="hidden sm:inline">{fromInfo.hash}</span>
                      <span className="sm:hidden">
                        {truncateHash(fromInfo.hash)}
                      </span>
                    </a>
                  ) : (
                    <p className="text-sm text-white break-all">--</p>
                  )}
                </div>

                <div className="flex gap-x-[1.8rem] py-1">
                  <p className="w-24 text-white text-opacity-60">Dest Txn</p>
                  {toInfo ? (
                    <a
                      target="_blank"
                      rel="noreferrer"
                      className="text-sm text-white break-all cursor-pointer hover:text-gray-500"
                      href={
                        CHAINS_BY_ID[toInfo.chainID]?.explorerUrl +
                        '/tx/' +
                        toInfo.hash
                      }
                    >
                      <span className="hidden sm:inline">{toInfo.hash}</span>
                      <span className="sm:hidden">
                        {truncateHash(toInfo.hash)}
                      </span>
                    </a>
                  ) : (
                    <p className="text-sm text-white break-all">--</p>
                  )}
                </div>
              </div>
            </div>
          </div>
          <br />
        </div>
      </>
    )
  } else {
    content = (
      <Error
        text="Sorry, there was a problem with that transaction hash."
        param={kappa}
        subtitle="Unknown"
      />
    )
  }

  return <StandardPageContainer>{content}</StandardPageContainer>
}

const TransactionPage = ({ queryResult }) => {
  return <BridgeTransaction queryResult={queryResult} />
}

export default TransactionPage

export const getServerSideProps = async (context) => {
  const { data } = await client.query({
    query: GET_BRIDGE_TRANSACTIONS_QUERY,
    variables: {
      chainId: context.params.chainIdFrom,
      kappa: context.params.kappa,
      useMv: true,
    },
  })

  return {
    props: {
      queryResult: data,
    }, // will be passed to the page component as props
  }
}