Skip to content

Commit ca46665

Browse files
committed
feat(neuron-ui): add nervos dao view
1 parent 5845ca8 commit ca46665

30 files changed

Lines changed: 878 additions & 73 deletions

File tree

packages/neuron-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"qr.js": "0.0.0",
5454
"react": "16.9.0",
5555
"react-dom": "16.9.0",
56-
"react-i18next": "10.12.2",
56+
"react-i18next": "11.0.1",
5757
"react-router-dom": "5.0.1",
5858
"react-scripts": "3.2.0",
5959
"styled-components": "5.0.0-beta.0"
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import React, { useEffect, useState } from 'react'
2+
import { DefaultButton } from 'office-ui-fabric-react'
3+
import { useTranslation } from 'react-i18next'
4+
import { ckbCore, getBlockByNumber } from 'services/chain'
5+
import calculateAPY from 'utils/calculateAPY'
6+
import { shannonToCKBFormatter, uniformTimeFormatter, localNumberFormatter } from 'utils/formatters'
7+
import { epochParser } from 'utils/parsers'
8+
import { WITHDRAW_EPOCHS } from 'utils/const'
9+
10+
import * as styles from './daoRecordRow.module.scss'
11+
12+
const DAORecord = ({
13+
daoData,
14+
blockNumber,
15+
blockHash,
16+
outPoint: { txHash, index },
17+
tipBlockNumber,
18+
tipBlockHash,
19+
capacity,
20+
actionLabel,
21+
onClick,
22+
timestamp,
23+
depositOutPoint,
24+
epoch,
25+
}: State.NervosDAORecord & {
26+
actionLabel: string
27+
onClick: any
28+
tipBlockNumber: string
29+
tipBlockHash: string
30+
epoch: string
31+
}) => {
32+
const [t] = useTranslation()
33+
const [withdrawValue, setWithdrawValue] = useState('')
34+
const [depositEpoch, setDepositEpoch] = useState('')
35+
36+
useEffect(() => {
37+
const withdrawBlockHash = depositOutPoint ? blockHash : tipBlockHash
38+
if (!withdrawBlockHash) {
39+
return
40+
}
41+
;(ckbCore.rpc as any)
42+
.calculateDaoMaximumWithdraw({ txHash, index: `0x${BigInt(index).toString(16)}` }, withdrawBlockHash)
43+
.then((res: string) => {
44+
setWithdrawValue(BigInt(res).toString())
45+
})
46+
.catch((err: Error) => {
47+
console.error(err)
48+
})
49+
}, [txHash, index, tipBlockHash, depositOutPoint, blockHash])
50+
51+
useEffect(() => {
52+
if (!depositOutPoint) {
53+
return
54+
}
55+
const depositBlockNumber = ckbCore.utils.bytesToHex(ckbCore.utils.hexToBytes(daoData).reverse())
56+
getBlockByNumber(BigInt(depositBlockNumber))
57+
.then(b => {
58+
setDepositEpoch(b.header.epoch)
59+
})
60+
.catch((err: Error) => {
61+
console.error(err)
62+
})
63+
}, [daoData, depositOutPoint])
64+
65+
const interest = BigInt(withdrawValue) - BigInt(capacity)
66+
67+
let ready = false
68+
let metaInfo = 'Ready'
69+
if (!depositOutPoint) {
70+
const duration = BigInt(tipBlockNumber) - BigInt(blockNumber)
71+
metaInfo = t('nervos-dao.interest-accumulated', {
72+
blockNumber: localNumberFormatter(duration >= BigInt(0) ? duration : 0),
73+
})
74+
} else {
75+
const depositEpochInfo = epochParser(depositEpoch)
76+
const currentEpochInfo = epochParser(epoch)
77+
78+
let depositedEpochs = currentEpochInfo.number - depositEpochInfo.number
79+
const depositEpochFraction = depositEpochInfo.index * currentEpochInfo.length
80+
const currentEpochFraction = currentEpochInfo.index * depositEpochInfo.length
81+
if (currentEpochFraction > depositEpochFraction) {
82+
depositedEpochs += BigInt(1)
83+
}
84+
const minLockEpochs =
85+
((depositedEpochs + BigInt(WITHDRAW_EPOCHS - 1)) / BigInt(WITHDRAW_EPOCHS)) * BigInt(WITHDRAW_EPOCHS)
86+
const targetEpochNumber = depositEpochInfo.number + minLockEpochs
87+
88+
if (targetEpochNumber < currentEpochInfo.number + BigInt(1) && targetEpochNumber >= currentEpochInfo.number) {
89+
metaInfo = 'Ready'
90+
ready = true
91+
} else {
92+
metaInfo = t('nervos-dao.blocks-left', {
93+
epochs: localNumberFormatter(targetEpochNumber - currentEpochInfo.number - BigInt(1)),
94+
blocks: currentEpochInfo.length - currentEpochInfo.index,
95+
})
96+
}
97+
}
98+
99+
return (
100+
<div className={styles.daoRecord}>
101+
<div className={styles.primaryInfo}>
102+
<div>{interest >= BigInt(0) ? `${shannonToCKBFormatter(interest.toString()).toString()} CKB` : ''}</div>
103+
<div>{`${shannonToCKBFormatter(capacity)} CKB`}</div>
104+
<div>
105+
<DefaultButton
106+
text={actionLabel}
107+
data-tx-hash={txHash}
108+
data-index={index}
109+
onClick={onClick}
110+
disabled={depositOutPoint && !ready}
111+
styles={{
112+
flexContainer: {
113+
pointerEvents: 'none',
114+
},
115+
textContainer: {
116+
pointerEvents: 'none',
117+
},
118+
label: {
119+
pointerEvents: 'none',
120+
},
121+
}}
122+
/>
123+
</div>
124+
</div>
125+
<div className={styles.secondaryInfo}>
126+
<span>
127+
{`APY: ~${calculateAPY(
128+
interest >= BigInt(0) ? interest.toString() : '0',
129+
capacity,
130+
`${Date.now() - +timestamp}`
131+
)}%`}
132+
</span>
133+
<span>{uniformTimeFormatter(+timestamp)}</span>
134+
<span>{metaInfo}</span>
135+
</div>
136+
</div>
137+
)
138+
}
139+
140+
DAORecord.displayName = 'DAORecord'
141+
142+
export default DAORecord
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.daoRecord {
2+
display: flex;
3+
flex-direction: column;
4+
border: 1px solid #000;
5+
border-radius: 5px;
6+
margin: 10px 0;
7+
padding: 5px 15px;
8+
9+
.primaryInfo,
10+
.secondaryInfo {
11+
display: flex;
12+
justify-content: space-between;
13+
14+
&>div,
15+
&>span {
16+
flex: 1;
17+
text-align: center;
18+
19+
&:first-child {
20+
text-align: left;
21+
}
22+
23+
&:last-child {
24+
text-align: right;
25+
}
26+
}
27+
28+
}
29+
30+
.secondaryInfo {
31+
font-size: 12px;
32+
color: #666;
33+
}
34+
35+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import React from 'react'
2+
import {
3+
Stack,
4+
Dialog,
5+
TextField,
6+
Slider,
7+
Text,
8+
DefaultButton,
9+
PrimaryButton,
10+
DialogType,
11+
DialogFooter,
12+
Spinner,
13+
SpinnerSize,
14+
} from 'office-ui-fabric-react'
15+
import { useTranslation } from 'react-i18next'
16+
import { SHANNON_CKB_RATIO } from 'utils/const'
17+
18+
const DepositDialog = ({
19+
show,
20+
value,
21+
fee,
22+
balance,
23+
onChange,
24+
onSlide,
25+
onSubmit,
26+
onDismiss,
27+
isDepositing,
28+
errorMessage,
29+
}: any) => {
30+
const [t] = useTranslation()
31+
const maxValue = +(BigInt(balance) / BigInt(SHANNON_CKB_RATIO)).toString()
32+
33+
if (!show) {
34+
return null
35+
}
36+
37+
return (
38+
<Dialog
39+
hidden={false}
40+
onDismiss={onDismiss}
41+
dialogContentProps={{
42+
type: DialogType.close,
43+
title: t('nervos-dao.deposit-to-nervos-dao'),
44+
}}
45+
modalProps={{
46+
isBlocking: false,
47+
styles: { main: { maxWidth: '500px!important' } },
48+
}}
49+
>
50+
{isDepositing ? (
51+
<Spinner size={SpinnerSize.large} />
52+
) : (
53+
<>
54+
<TextField label={t('nervos-dao.deposit')} value={value} onChange={onChange} suffix="CKB" />
55+
<Slider value={value} min={0} max={maxValue} step={1} showValue={false} onChange={onSlide} />
56+
<Text as="p" variant="small" block>
57+
{`${t('nervos-dao.fee')}: ${fee}`}
58+
</Text>
59+
<Text as="span" variant="tiny" block styles={{ root: { color: 'red' } }}>
60+
{errorMessage}
61+
</Text>
62+
<Stack>
63+
<Text as="h2" variant="large">
64+
{t('nervos-dao.notice')}
65+
</Text>
66+
{t('nervos-dao.deposit-terms')
67+
.split('\n')
68+
.map(term => (
69+
<Text as="p" key={term}>
70+
{term}
71+
</Text>
72+
))}
73+
</Stack>
74+
<DialogFooter>
75+
<DefaultButton onClick={onDismiss} text={t('nervos-dao.cancel')} />
76+
<PrimaryButton onClick={onSubmit} text={t('nervos-dao.proceed')} disabled={errorMessage} />
77+
</DialogFooter>
78+
</>
79+
)}
80+
</Dialog>
81+
)
82+
}
83+
84+
DepositDialog.displayName = 'DepositDialog'
85+
86+
export default DepositDialog
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, { useState, useEffect } from 'react'
2+
import { Dialog, DialogFooter, DefaultButton, PrimaryButton, DialogType } from 'office-ui-fabric-react'
3+
import { useTranslation } from 'react-i18next'
4+
import { shannonToCKBFormatter } from 'utils/formatters'
5+
import { ckbCore } from 'services/chain'
6+
7+
const WithdrawDialog = ({ onDismiss, onSubmit, record, tipBlockHash }: any) => {
8+
const [t] = useTranslation()
9+
const [withdrawValue, setWithdrawValue] = useState('')
10+
useEffect(() => {
11+
if (!record || !tipBlockHash) {
12+
return
13+
}
14+
;(ckbCore.rpc as any)
15+
.calculateDaoMaximumWithdraw(
16+
{
17+
txHash: record.outPoint.txHash,
18+
index: `0x${BigInt(record.outPoint.index).toString(16)}`,
19+
},
20+
tipBlockHash
21+
)
22+
.then((res: string) => {
23+
setWithdrawValue(res)
24+
})
25+
.catch((err: Error) => {
26+
console.error(err)
27+
})
28+
}, [record, tipBlockHash])
29+
return (
30+
<Dialog
31+
hidden={!record}
32+
onDismiss={onDismiss}
33+
dialogContentProps={{ type: DialogType.close, title: t('nervos-dao.withdraw-from-nervos-dao') }}
34+
modalProps={{
35+
isBlocking: false,
36+
styles: { main: { maxWidth: '500px!important' } },
37+
}}
38+
>
39+
{record ? (
40+
<>
41+
<div>
42+
<span>{`${t('nervos-dao.deposit')}:`}</span>
43+
<span>{`${shannonToCKBFormatter(record.capacity)} CKB`}</span>
44+
</div>
45+
<div>
46+
<span>{`${t('nervos-dao.interest')}:`}</span>
47+
<span>
48+
{withdrawValue
49+
? `${shannonToCKBFormatter((BigInt(withdrawValue) - BigInt(record.capacity)).toString())} CKB`
50+
: ''}
51+
</span>
52+
</div>
53+
</>
54+
) : null}
55+
<DialogFooter>
56+
<DefaultButton text={t('nervos-dao.cancel')} onClick={onDismiss} />
57+
<PrimaryButton text={t('nervos-dao.proceed')} onClick={onSubmit} />
58+
</DialogFooter>
59+
</Dialog>
60+
)
61+
}
62+
63+
WithdrawDialog.displayName = 'WithdrawDialog'
64+
65+
export default WithdrawDialog

0 commit comments

Comments
 (0)