Skip to content
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

feat(neuron-ui): update the view of transaction detail #965

Merged
merged 2 commits into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/neuron-ui/src/components/Addresses/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const Addresses = ({
maxWidth: 500,
onRender: (item?: State.Address, _index?: number, column?: IColumn) => {
if (item) {
if (column && (column.calculatedWidth || 0) < 400) {
if (column && (column.calculatedWidth || 0) < 420) {
return (
<div
title={item.address}
Expand Down
310 changes: 203 additions & 107 deletions packages/neuron-ui/src/components/Transaction/index.tsx
Original file line number Diff line number Diff line change
@@ -1,115 +1,193 @@
import React, { useEffect, useState, useMemo } from 'react'
import React, { useEffect, useState, useMemo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Stack, DetailsList, Text, CheckboxVisibility, IColumn } from 'office-ui-fabric-react'
import { Stack, DetailsList, Text, CheckboxVisibility, IColumn, Icon } from 'office-ui-fabric-react'
import { currentWallet as currentWalletCache } from 'services/localCache'
import { getTransaction, showErrorMessage } from 'services/remote'
import { getTransaction, showErrorMessage, getAllNetworks, getCurrentNetworkID, openExternal } from 'services/remote'
import { ckbCore } from 'services/chain'

import { transactionState } from 'states/initStates/chain'

import { localNumberFormatter, uniformTimeFormatter, shannonToCKBFormatter } from 'utils/formatters'
import { ErrorCode } from 'utils/const'
import { explorerNavButton } from './style.module.scss'

const MIN_CELL_WIDTH = 70

const inputColumns: IColumn[] = [
{
key: 'lockHash',
name: 'Lock Hash',
minWidth: 100,
maxWidth: 200,
onRender: (item: any) => (
<span title={item.lockHash || 'none'} className="textOverflow">
{item.lockHash || 'none'}
</span>
),
},
{
key: 'outPointCell',
name: 'OutPoint Cell',
minWidth: 150,
onRender: (item: any) => {
const text = item.previousOutput ? `${item.previousOutput.txHash}[${item.previousOutput.index}]` : 'none'
return (
<span title={text} className="textOverflow">
{text}
</span>
)
},
},
{
key: 'capacity',
name: 'Capacity',
minWidth: 200,
maxWidth: 250,
},
].map(
(col): IColumn => ({
ariaLabel: col.name,
fieldName: col.key,
...col,
})
)
const outputColumns: IColumn[] = [
{
key: 'index',
name: 'Index',
minWidth: 80,
maxWidth: 150,
onRender: (item?: any | State.DetailedOutput) => {
if (item) {
return item.outPoint.index
}
return null
},
},
{
key: 'lockHash',
name: 'Lock Hash',
minWidth: 150,
},
{
key: 'capacity',
name: 'Capacity',
minWidth: 200,
maxWidth: 250,
onRender: (output?: State.DetailedOutput) => {
if (output) {
return `${shannonToCKBFormatter(output.capacity)} CKB`
}
return null
},
},
].map(col => ({
ariaLabel: col.name,
fieldName: col.key,
...col,
}))

const basicInfoColumns: IColumn[] = [
{
key: 'label',
name: 'Label',
minWidth: 100,
maxWidth: 150,
},
{
key: 'value',
name: 'value',
minWidth: 450,
},
].map(
(col): IColumn => ({
minWidth: MIN_CELL_WIDTH,
ariaLabel: col.name,
fieldName: col.key,
...col,
})
)
const Transaction = () => {
const [t] = useTranslation()
const [transaction, setTransaction] = useState(transactionState)
const [addressPrefix, setAddressPrefix] = useState(ckbCore.utils.AddressPrefix.Mainnet)
const [error, setError] = useState({ code: '', message: '' })

const inputColumns: IColumn[] = useMemo(
() =>
[
{
key: 'index',
name: t('transaction.index'),
minWidth: 60,
maxWidth: 60,
onRender: (_item?: any, index?: number) => {
if (undefined !== index) {
return index
}
return null
},
},
{
key: 'outPointCell',
name: 'OutPoint Cell',
minWidth: 150,
maxWidth: 600,
onRender: (item: any) => {
const text = item.previousOutput ? `${item.previousOutput.txHash}[${item.previousOutput.index}]` : 'none'
return (
<span title={text} className="textOverflow">
{text}
</span>
)
},
},
{
key: 'capacity',
name: t('transaction.amount'),
minWidth: 100,
maxWidth: 250,
onRender: (input?: State.DetailedOutput) => {
if (input) {
return `${shannonToCKBFormatter(input.capacity)} CKB`
}
return null
},
},
].map(
(col): IColumn => ({
ariaLabel: col.name,
fieldName: col.key,
...col,
})
),
[t]
)

const outputColumns: IColumn[] = useMemo(
() =>
[
{
key: 'index',
name: t('transaction.index'),
minWidth: 60,
maxWidth: 60,
onRender: (item?: any | State.DetailedOutput) => {
if (item) {
return item.outPoint.index
}
return null
},
},
{
key: 'address',
name: t('transaction.address'),
minWidth: 200,
maxWidth: 500,
onRender: (output?: State.DetailedOutput, _index?: number, column?: IColumn) => {
if (!output) {
return null
}
try {
const address = ckbCore.utils.bech32Address(output.lock.args[0], {
prefix: addressPrefix,
type: ckbCore.utils.AddressType.HashIdx,
codeHashIndex: '0x00',
})
if (column && (column.calculatedWidth || 0) < 450) {
return (
<div
title={address}
style={{
overflow: 'hidden',
display: 'flex',
}}
className="monospacedFont"
>
<span className="textOverflow">{address.slice(0, -6)}</span>
<span>{address.slice(-6)}</span>
</div>
)
}
return (
<span title={address} className="monospacedFont">
{address}
</span>
)
} catch {
return null
}
},
},
{
key: 'capacity',
name: t('transaction.amount'),
minWidth: 100,
maxWidth: 250,
onRender: (output?: State.DetailedOutput) => {
if (output) {
return `${shannonToCKBFormatter(output.capacity)} CKB`
}
return null
},
},
].map(col => ({
ariaLabel: col.name,
fieldName: col.key,
...col,
})),
[addressPrefix, t]
)

const basicInfoColumns: IColumn[] = useMemo(
() =>
[
{
key: 'label',
name: 'label',
minWidth: 100,
maxWidth: 120,
},
{
key: 'value',
name: 'value',
minWidth: 150,
},
].map(
(col): IColumn => ({
minWidth: MIN_CELL_WIDTH,
ariaLabel: col.name,
fieldName: col.key,
...col,
})
),
[]
)

useEffect(() => {
Promise.all([getAllNetworks(), getCurrentNetworkID()])
.then(([networksRes, idRes]) => {
if (networksRes.status === 1 && idRes.status === 1) {
const network = networksRes.result.find((n: any) => n.id === idRes.result)
if (!network) {
throw new Error('Cannot find current network in the network list')
}

setAddressPrefix(
network.chain === process.env.REACT_APP_MAINNET_TAG
? ckbCore.utils.AddressPrefix.Mainnet
: ckbCore.utils.AddressPrefix.Testnet
)
}
})
.catch(err => console.warn(err))

const currentWallet = currentWalletCache.load()
if (currentWallet) {
const hash = window.location.href.split('/').pop()
Expand Down Expand Up @@ -142,21 +220,26 @@ const Transaction = () => {
})
}, [])

// TODO: add conditional branch on mainnet and testnet
const onExplorerBtnClick = useCallback(() => {
openExternal(`https://explorer.nervos.org/transaction/${transaction.hash}`)
}, [transaction.hash])

const basicInfoItems = useMemo(
() => [
{ label: t('history.transaction-hash'), value: transaction.hash || 'none' },
{ label: t('transaction.transaction-hash'), value: transaction.hash || 'none' },
{
label: t('history.date'),
label: t('transaction.block-number'),
value: localNumberFormatter(transaction.blockNumber) || 'none',
},
{
label: t('transaction.date'),
value: +(transaction.timestamp || transaction.createdAt)
? uniformTimeFormatter(+(transaction.timestamp || transaction.createdAt))
: 'none',
},
{
label: t('history.blockNumber'),
value: localNumberFormatter(transaction.blockNumber) || 'none',
},
{
label: t('history.amount'),
label: t('transaction.income'),
value: `${shannonToCKBFormatter(transaction.value)} CKB`,
},
],
Expand Down Expand Up @@ -185,10 +268,12 @@ const Transaction = () => {
isHeaderVisible={false}
/>
</Stack>
<Stack tokens={{ childrenGap: 15 }}>
<Stack tokens={{ childrenGap: 15 }} verticalFill>
<Stack.Item>
<Text variant="xLarge" as="h1">
Inputs
{`${t('transaction.inputs')} (${transaction.inputs.length}/${localNumberFormatter(
transaction.inputsCount
)})`}
</Text>
<DetailsList
items={transaction.inputs}
Expand All @@ -200,7 +285,9 @@ const Transaction = () => {
</Stack.Item>
<Stack.Item>
<Text variant="xLarge" as="h1">
Outputs
{`${t('transaction.outputs')} (${transaction.outputs.length}/${localNumberFormatter(
transaction.outputsCount
)})`}
</Text>
<DetailsList
items={transaction.outputs}
Expand All @@ -211,6 +298,15 @@ const Transaction = () => {
/>
</Stack.Item>
</Stack>
<button
type="button"
className={explorerNavButton}
title={t('transaction.view-in-explorer-button-title')}
onClick={onExplorerBtnClick}
>
<Icon iconName="Explorer" />
<span>{t('transaction.view-in-explorer')}</span>
</button>
</Stack>
)
}
Expand Down
Loading