diff --git a/packages/neuron-ui/src/components/BalanceSyncingIcon/balanceSyncIcon.module.scss b/packages/neuron-ui/src/components/BalanceSyncingIcon/balanceSyncIcon.module.scss index 116c7a0466..c51b395b6a 100644 --- a/packages/neuron-ui/src/components/BalanceSyncingIcon/balanceSyncIcon.module.scss +++ b/packages/neuron-ui/src/components/BalanceSyncingIcon/balanceSyncIcon.module.scss @@ -5,6 +5,6 @@ margin-left: 4px; path { - fill: #ffffff; + fill: var(--main-text-color); } } diff --git a/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss b/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss index c9fb6d0603..e55142e967 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss +++ b/packages/neuron-ui/src/components/GeneralSetting/generalSetting.module.scss @@ -85,6 +85,12 @@ $action-button-width: 11.25rem; background: var(--table-head-background-color); color: var(--dialog-secondary-text-color); + h1 { + &:first-child { + display: none; + } + } + ul { list-style-type: disc; padding-left: 30px; diff --git a/packages/neuron-ui/src/components/GeneralSetting/index.tsx b/packages/neuron-ui/src/components/GeneralSetting/index.tsx index 8819e4b5eb..7866c4eaaa 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/index.tsx +++ b/packages/neuron-ui/src/components/GeneralSetting/index.tsx @@ -18,7 +18,6 @@ interface UpdateDownloadStatusProps { onCancel: () => void progress: number newVersion: string - releaseDate: string releaseNotes: string progressInfo: null | State.ProgressInfo } @@ -28,13 +27,13 @@ const UpdateDownloadStatus = ({ onCancel, progress = 0, newVersion = '', - releaseDate = '', releaseNotes = '', progressInfo, }: UpdateDownloadStatusProps) => { const [t] = useTranslation() const available = newVersion !== '' && progress < 0 const downloaded = progress >= 1 + const [publishedAt, setPublishedAt] = useState('') const handleConfirm = useCallback( (e: React.FormEvent) => { @@ -51,6 +50,18 @@ const UpdateDownloadStatus = ({ [downloadUpdate, installUpdate] ) + useEffect(() => { + if (available) { + fetch(`https://api.github.com/repos/nervosnetwork/neuron/releases/tags/v${newVersion}`) + .then(async res => { + // eslint-disable-next-line camelcase + const { published_at } = await res.json() + setPublishedAt(published_at) + }) + .catch(() => {}) + } + }, [available, setPublishedAt]) + if (available) { const releaseNotesHtml = () => { return { __html: releaseNotes } @@ -72,7 +83,7 @@ const UpdateDownloadStatus = ({ >

- {newVersion} ({uniformTimeFormatter(new Date(releaseDate)).split(' ')[0]}) + {newVersion} {publishedAt ? `(${uniformTimeFormatter(new Date(publishedAt)).split(' ')[0]})` : null}

@@ -217,7 +228,6 @@ const GeneralSetting = ({ updater }: GeneralSettingProps) => { progress={updater.downloadProgress} progressInfo={updater.progressInfo} newVersion={updater.version} - releaseDate={updater.releaseDate} releaseNotes={updater.releaseNotes} /> diff --git a/packages/neuron-ui/src/components/History/history.module.scss b/packages/neuron-ui/src/components/History/history.module.scss index 6f88e8e24d..c389de4ac8 100644 --- a/packages/neuron-ui/src/components/History/history.module.scss +++ b/packages/neuron-ui/src/components/History/history.module.scss @@ -37,6 +37,13 @@ body { font-weight: 500; padding-right: 10px; } + .searchBoxSuffix { + font-family: inherit; + display: flex; + align-items: center; + padding-left: 10px; + cursor: pointer; + } .exportButton { color: var(--main-text-color); border-color: var(--history-button-border); diff --git a/packages/neuron-ui/src/components/History/index.tsx b/packages/neuron-ui/src/components/History/index.tsx index 1329d5281b..38c96c5429 100644 --- a/packages/neuron-ui/src/components/History/index.tsx +++ b/packages/neuron-ui/src/components/History/index.tsx @@ -6,7 +6,7 @@ import SUDTAvatar from 'widgets/SUDTAvatar' import Button from 'widgets/Button' import Table, { TableProps } from 'widgets/Table' import TextField from 'widgets/TextField' -import { Download, Search, ArrowNext } from 'widgets/Icons/icon' +import { Download, Search, ArrowNext, Clean } from 'widgets/Icons/icon' import PageContainer from 'components/PageContainer' import TransactionStatusWrap from 'components/TransactionStatusWrap' @@ -47,6 +47,7 @@ const History = () => { const { keywords, onKeywordsChange } = useSearch(search, id, dispatch) const onSearch = useCallback(() => navigate(`${RoutePath.History}?keywords=${keywords}`), [navigate, keywords]) + const onClean = useCallback(() => onKeywordsChange(undefined, ''), [onKeywordsChange]) const onExport = useCallback(() => { setIsExporting(true) const timer = setTimeout(() => { @@ -183,6 +184,13 @@ const History = () => { } + suffix={ + keywords ? ( + + + + ) : null + } placeholder={t('history.search.placeholder')} className={styles.tableHeaderInput} stack={false} diff --git a/packages/neuron-ui/src/components/NetworkSetting/index.tsx b/packages/neuron-ui/src/components/NetworkSetting/index.tsx index e82447e235..7af39881f5 100644 --- a/packages/neuron-ui/src/components/NetworkSetting/index.tsx +++ b/packages/neuron-ui/src/components/NetworkSetting/index.tsx @@ -101,7 +101,11 @@ const NetworkSetting = ({ chain = chainState, settings: { networks = [] } }: Sta value: network.id, label: (
-

{`${network.name} (${network.remote})`}

+

{`${network.name} (${ + network.remote.length > 40 + ? `${network.remote.slice(0, 20)}...${network.remote.slice(-20)}` + : network.remote + })`}

{t(getNetworkLabelI18nkey(network.chain))}
), diff --git a/packages/neuron-ui/src/components/Send/index.tsx b/packages/neuron-ui/src/components/Send/index.tsx index 97240daadb..05b2cb61ca 100644 --- a/packages/neuron-ui/src/components/Send/index.tsx +++ b/packages/neuron-ui/src/components/Send/index.tsx @@ -169,7 +169,7 @@ const Send = () => { !send.generatedTx || outputs.some(v => !v.address) - const outputErrors = useOutputErrors(outputs, isMainnet) + const outputErrors = useOutputErrors(outputs, isMainnet, isSendMax) const isMaxBtnDisabled = (() => { try { diff --git a/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts b/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts index e183db9ff0..e2463e3c31 100644 --- a/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts +++ b/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts @@ -133,7 +133,8 @@ export const useSendInfo = ({ }, [setSendInfoList] ) - const outputErrors = useOutputErrors(sendInfoList, isMainnet) + const [isSendMax, setIsSendMax] = useState(false) + const outputErrors = useOutputErrors(sendInfoList, isMainnet, isSendMax) const totalAmount = useMemo( () => outputsToTotalAmount(sendInfoList.filter((v, idx) => !!v.amount && !outputErrors[idx].amountError)), [sendInfoList, outputErrors] @@ -172,7 +173,6 @@ export const useSendInfo = ({ } }, 300) }, [sendInfoList, setErrorMessage, multisigConfig, dispatch, t, isMainnet, outputErrors]) - const [isSendMax, setIsSendMax] = useState(false) const onSendMaxClick = useCallback(() => { if (!isSendMax) { setIsSendMax(true) diff --git a/packages/neuron-ui/src/components/TransactionStatusWrap/transactionStatusWrap.module.scss b/packages/neuron-ui/src/components/TransactionStatusWrap/transactionStatusWrap.module.scss index 7e87b9f483..c19f1df9b9 100644 --- a/packages/neuron-ui/src/components/TransactionStatusWrap/transactionStatusWrap.module.scss +++ b/packages/neuron-ui/src/components/TransactionStatusWrap/transactionStatusWrap.module.scss @@ -20,5 +20,6 @@ color: #999; font-size: 12px; margin-left: 4px; + white-space: nowrap; } } diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts index b332e2ca9d..3111c1ca43 100644 --- a/packages/neuron-ui/src/containers/Main/hooks.ts +++ b/packages/neuron-ui/src/containers/Main/hooks.ts @@ -198,6 +198,7 @@ export const useSubscription = ({ }) CONNECTING_DEADLINE = Date.now() + CONNECTING_BUFFER currentNetworkIDCache.save(currentNetworkID) + updateAddressListAndBalance(walletID)(dispatch) }) const connectionStatusSubscription = ConnectionStatusSubject.subscribe(status => { if (isCurrentUrl(status.url)) { diff --git a/packages/neuron-ui/src/exceptions/amount.ts b/packages/neuron-ui/src/exceptions/amount.ts index f24fd01d78..f148c17714 100644 --- a/packages/neuron-ui/src/exceptions/amount.ts +++ b/packages/neuron-ui/src/exceptions/amount.ts @@ -56,3 +56,15 @@ export class AmountNegativeException extends RangeError { this.i18n.fieldValue = value } } + +export class CapacityTooSmallException extends Error { + public code = ErrorCode.CapacityTooSmall + public i18n: { + bytes: string + } + + constructor(bytes: string) { + super(`${I18N_PATH}${ErrorCode.CapacityTooSmall}`) + this.i18n = { bytes } + } +} diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 0720469ccc..08c1ba3add 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -678,7 +678,7 @@ "cancel": "Cancel", "proceed": "Proceed", "compensation": "Compensation", - "notice-wait-time": "Notice: You are initiating withdrawn and the final withdrawn will need to wait for {{epochs}} epochs {{blocks}} blocks (about {{days} days).", + "notice-wait-time": "Notice: You are initiating withdrawn and the final withdrawn will need to wait for {{epochs}} epochs {{blocks}} blocks (about {{days}} days).", "deposit-dialog-title": "Deposit", "deposit-terms": "Nervos DAO needs 102 CKBytes for deposit cell storage, which is not compensation-bearing.
Please visit the <0>Nervos DAO RFC for more information about Nervos DAO", "minimal-fee-required": "The minimum deposit capacity is {{minimal}} CKBytes", @@ -728,7 +728,7 @@ "nervos-dao-detail": { "tx-detail": "Transaction Detail", "deposited": "Deposited", - "withdrawn": "Withdrawn", + "withdrawn": "Withdraw", "unlocked": "Unlocked", "basic-information": "Basic Information", "transaction-hash": "Transaction Hash", diff --git a/packages/neuron-ui/src/tests/validators/capacity/fixtures.ts b/packages/neuron-ui/src/tests/validators/capacity/fixtures.ts new file mode 100644 index 0000000000..002fcad53b --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/capacity/fixtures.ts @@ -0,0 +1,20 @@ +import { ErrorCode } from 'utils/enums' + +export default { + 'Should throw an error when address is not a string': { + params: { + address: 'ckb1qrgqep8saj8agswr30pls73hra28ry8jlnlc3ejzh3dl2ju7xxpjxqgqqy28n5m69va267nv0wvsudngtxz8t9jwss3xkhpk', + amount: '62', + unit: 'ckb', + }, + exception: ErrorCode.CapacityTooSmall, + }, + 'Should throw an error when address is required but not provided': { + params: { + address: 'ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2n0egquval9ljqm2ga6t9509kwcex5ejq3hy6dk', + amount: '62', + unit: 'ckb', + }, + exception: null, + }, +} diff --git a/packages/neuron-ui/src/tests/validators/capacity/index.test.ts b/packages/neuron-ui/src/tests/validators/capacity/index.test.ts new file mode 100644 index 0000000000..d750f0b52f --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/capacity/index.test.ts @@ -0,0 +1,25 @@ +import { describe, test, expect } from '@jest/globals' +import { isErrorWithI18n } from 'exceptions' +import { validateCapacity } from 'utils/validators' +import fixtures from './fixtures' + +const fixtureTable: Fixture.Validator[] = Object.entries(fixtures).map( + ([title, { params, exception }]) => [title, [params], exception] +) + +describe(`Test capacity validator`, () => { + test.each(fixtureTable)(`%s`, (_title, [params], exception) => { + if (exception) { + expect.assertions(2) + try { + validateCapacity(params) + } catch (err) { + expect(isErrorWithI18n(err)).toBeTruthy() + expect((err as { code: number }).code).toBe(exception) + } + } else { + expect.assertions(1) + expect(validateCapacity(params)).toBeTruthy() + } + }) +}) diff --git a/packages/neuron-ui/src/utils/hooks/index.ts b/packages/neuron-ui/src/utils/hooks/index.ts index 0d2d63f131..9496c06422 100644 --- a/packages/neuron-ui/src/utils/hooks/index.ts +++ b/packages/neuron-ui/src/utils/hooks/index.ts @@ -23,6 +23,7 @@ import { validateAmount, validateAddress, validateAmountRange, + validateCapacity, } from 'utils/validators' import { MenuItemConstructorOptions, shell } from 'electron' import { ErrorWithI18n, isErrorWithI18n } from 'exceptions' @@ -499,18 +500,22 @@ export const useForceUpdate = void>(cb: T) => { } export const useOutputErrors = ( - outputs: Partial>[], - isMainnet: boolean + outputs: Partial>[], + isMainnet: boolean, + isSendMax?: boolean ) => { return useMemo( () => - outputs.map(({ address, amount, date }) => { + outputs.map(({ address, amount, date, unit }, index) => { let amountError: ErrorWithI18n | undefined if (amount !== undefined) { try { const extraSize = date ? CONSTANTS.SINCE_FIELD_SIZE : 0 validateAmount(amount) validateAmountRange(amount, extraSize) + if (!(isSendMax && index === outputs.length - 1)) { + validateCapacity({ address, amount, unit }) + } } catch (err) { if (isErrorWithI18n(err)) { amountError = err diff --git a/packages/neuron-ui/src/utils/validators/capacity.ts b/packages/neuron-ui/src/utils/validators/capacity.ts new file mode 100644 index 0000000000..2f3a53f0c6 --- /dev/null +++ b/packages/neuron-ui/src/utils/validators/capacity.ts @@ -0,0 +1,19 @@ +import { CKBToShannonFormatter } from 'utils/formatters' +import { CapacityTooSmallException } from 'exceptions' +import { bytes as byteUtils } from '@ckb-lumos/codec' +import { ckbCore } from 'services/chain' + +export const validateCapacity = (item: State.Output) => { + const { amount, unit, address } = item + const capacity = CKBToShannonFormatter(amount, unit) + const script = ckbCore.utils.addressToScript(address as string) + const size = 1 + byteUtils.concat(script.args, script.codeHash).byteLength + const outputSize = 8 + byteUtils.bytify('0x').byteLength + size + + if (BigInt(capacity) < BigInt(outputSize) * BigInt(10 ** 8)) { + throw new CapacityTooSmallException(outputSize.toString()) + } + + return true +} +export default validateCapacity diff --git a/packages/neuron-ui/src/utils/validators/index.ts b/packages/neuron-ui/src/utils/validators/index.ts index f17bd58683..9a2e61c4e6 100644 --- a/packages/neuron-ui/src/utils/validators/index.ts +++ b/packages/neuron-ui/src/utils/validators/index.ts @@ -13,3 +13,4 @@ export * from './tokenId' export * from './tokenName' export * from './symbol' export * from './decimal' +export * from './capacity' diff --git a/packages/neuron-ui/src/widgets/Icons/Clean.svg b/packages/neuron-ui/src/widgets/Icons/Clean.svg new file mode 100644 index 0000000000..9edcc0df86 --- /dev/null +++ b/packages/neuron-ui/src/widgets/Icons/Clean.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/neuron-ui/src/widgets/Icons/icon.tsx b/packages/neuron-ui/src/widgets/Icons/icon.tsx index 464dc6e7d1..e58d9aa183 100644 --- a/packages/neuron-ui/src/widgets/Icons/icon.tsx +++ b/packages/neuron-ui/src/widgets/Icons/icon.tsx @@ -46,6 +46,7 @@ import { ReactComponent as AddressbookSvg } from './Addressbook.svg' import { ReactComponent as AddSvg } from './Add.svg' import { ReactComponent as AddSimpleSvg } from './AddSimple.svg' import { ReactComponent as SwitchSvg } from './Switch.svg' +import { ReactComponent as CleanSvg } from './Clean.svg' import { ReactComponent as CellManageSvg } from './CellManage.svg' import { ReactComponent as LockSvg } from './Lock.svg' import { ReactComponent as LockCellSvg } from './LockCell.svg' @@ -113,6 +114,7 @@ export const Addressbook = WrapSvg(AddressbookSvg, styles.withTheme) export const Add = WrapSvg(AddSvg) export const Switch = WrapSvg(SwitchSvg) export const AddSimple = WrapSvg(AddSimpleSvg) +export const Clean = WrapSvg(CleanSvg) export const CellManage = WrapSvg(CellManageSvg, styles.withTheme) export const Lock = WrapSvg(LockSvg, styles.withTheme) export const LockCell = WrapSvg(LockCellSvg)