diff --git a/packages/neuron-ui/src/components/PageContainer/hooks.ts b/packages/neuron-ui/src/components/PageContainer/hooks.ts
new file mode 100644
index 0000000000..5b9fc7cb4a
--- /dev/null
+++ b/packages/neuron-ui/src/components/PageContainer/hooks.ts
@@ -0,0 +1,109 @@
+import { useCallback, useEffect, useState } from 'react'
+import { importedWalletDialogShown } from 'services/localCache'
+import { isDark, openExternal, updateWallet, setTheme as setThemeAPI } from 'services/remote'
+import { Migrate } from 'services/subjects'
+import { getExplorerUrl, isSuccessResponse } from 'utils'
+
+export const useSetBlockNumber = ({
+ firstAddress,
+ walletID,
+ isMainnet,
+ isLightClient,
+ isHomePage,
+}: {
+ firstAddress: string
+ walletID: string
+ isMainnet: boolean
+ isLightClient: boolean
+ isHomePage?: boolean
+}) => {
+ const [showSetStartBlock, setShowSetStartBlock] = useState(false)
+ const [startBlockNumber, setStartBlockNumber] = useState('')
+ const [blockNumberErr, setBlockNumberErr] = useState('')
+ const onChangeStartBlockNumber = useCallback((e: React.SyntheticEvent) => {
+ const { value } = e.currentTarget
+ const blockNumber = value.replace(/,/g, '')
+ if (Number.isNaN(+blockNumber) || /[^\d]/.test(blockNumber)) {
+ return
+ }
+ setStartBlockNumber(blockNumber)
+ setBlockNumberErr('')
+ }, [])
+ const onOpenAddressInExplorer = useCallback(() => {
+ const explorerUrl = getExplorerUrl(isMainnet)
+ openExternal(`${explorerUrl}/address/${firstAddress}`)
+ }, [firstAddress])
+ const onViewBlock = useCallback(() => {
+ const explorerUrl = getExplorerUrl(isMainnet)
+ openExternal(`${explorerUrl}/block/${startBlockNumber}`)
+ }, [startBlockNumber, isMainnet])
+ const onConfirm = useCallback(() => {
+ updateWallet({ id: walletID, startBlockNumberInLight: `0x${BigInt(startBlockNumber).toString(16)}` }).then(res => {
+ if (isSuccessResponse(res)) {
+ setShowSetStartBlock(false)
+ } else {
+ setBlockNumberErr(typeof res.message === 'string' ? res.message : res.message.content!)
+ }
+ })
+ }, [startBlockNumber, walletID])
+ const openDialog = useCallback(() => {
+ setShowSetStartBlock(true)
+ setStartBlockNumber('')
+ }, [])
+ useEffect(() => {
+ if (isHomePage) {
+ const needShow = importedWalletDialogShown.needShow(walletID)
+ if (needShow && isLightClient) {
+ openDialog()
+ }
+ importedWalletDialogShown.setShown(walletID)
+ }
+ }, [walletID, isLightClient, isHomePage, openDialog])
+ return {
+ openDialog,
+ closeDialog: useCallback(() => setShowSetStartBlock(false), []),
+ showSetStartBlock,
+ startBlockNumber,
+ onChangeStartBlockNumber,
+ onOpenAddressInExplorer,
+ onViewBlock,
+ onConfirm,
+ blockNumberErr,
+ }
+}
+
+export const useTheme = () => {
+ const [theme, setTheme] = useState<'dark' | 'light'>()
+ useEffect(() => {
+ isDark().then(res => {
+ if (isSuccessResponse(res)) {
+ setTheme(res.result ? 'dark' : 'light')
+ }
+ })
+ }, [])
+ const onSetTheme = useCallback(() => {
+ const newTheme = theme === 'dark' ? 'light' : 'dark'
+ setThemeAPI(newTheme).then(res => {
+ if (isSuccessResponse(res)) {
+ setTheme(newTheme)
+ }
+ })
+ }, [theme])
+ return {
+ theme,
+ onSetTheme,
+ }
+}
+
+export const useMigrate = () => {
+ const [isMigrate, setIsMigrate] = useState(false)
+ useEffect(() => {
+ const migrateSubscription = Migrate.subscribe(migrateStatus => {
+ setIsMigrate(migrateStatus === 'migrating')
+ })
+ return () => {
+ migrateSubscription.unsubscribe()
+ }
+ }, [])
+ return isMigrate
+}
diff --git a/packages/neuron-ui/src/components/PageContainer/index.tsx b/packages/neuron-ui/src/components/PageContainer/index.tsx
index 3a37d105c2..e2fa9eca70 100755
--- a/packages/neuron-ui/src/components/PageContainer/index.tsx
+++ b/packages/neuron-ui/src/components/PageContainer/index.tsx
@@ -1,15 +1,19 @@
-import React, { FC, PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react'
+import React, { FC, PropsWithChildren, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
-import { ErrorCode, getExplorerUrl, isMainnet, isSuccessResponse, localNumberFormatter } from 'utils'
+import { ErrorCode, getExplorerUrl, isMainnet as isMainnetUtil, localNumberFormatter } from 'utils'
import Alert from 'widgets/Alert'
-import { Close } from 'widgets/Icons/icon'
+import { Close, NewTab } from 'widgets/Icons/icon'
import { ReactComponent as Sun } from 'widgets/Icons/Sun.svg'
import { ReactComponent as Moon } from 'widgets/Icons/Moon.svg'
import SyncStatusComponent from 'components/SyncStatus'
import { AppActions, useDispatch, useState as useGlobalState } from 'states'
-import { isDark, openExternal, setTheme as setThemeAPI } from 'services/remote'
+import { openExternal } from 'services/remote'
import Tooltip from 'widgets/Tooltip'
-import { Migrate } from 'services/subjects'
+import { LIGHT_NETWORK_TYPE } from 'utils/const'
+import Dialog from 'widgets/Dialog'
+import TextField from 'widgets/TextField'
+
+import { useMigrate, useSetBlockNumber, useTheme } from './hooks'
import styles from './pageContainer.module.scss'
const PageHeadNotice = ({ notice }: { notice: State.PageNotice }) => {
@@ -39,6 +43,7 @@ type ComponentProps = {
head: React.ReactNode
notice?: State.PageNotice
className?: string
+ isHomePage?: boolean
} & React.AllHTMLAttributes
const PageContainer: React.FC = props => {
const {
@@ -48,9 +53,10 @@ const PageContainer: React.FC = props => {
connectionStatus,
networkID,
},
+ wallet: { addresses, id },
settings: { networks },
} = useGlobalState()
- const { children, head, notice, className } = props
+ const { children, head, notice, className, isHomePage } = props
const [t] = useTranslation()
const dispatch = useDispatch()
const closeSyncNotice = useCallback(() => {
@@ -58,23 +64,12 @@ const PageContainer: React.FC = props => {
type: AppActions.HideWaitForFullySynced,
})
}, [dispatch])
- const [theme, setTheme] = useState<'dark' | 'light'>()
- useEffect(() => {
- isDark().then(res => {
- if (isSuccessResponse(res)) {
- setTheme(res.result ? 'dark' : 'light')
- }
- })
- }, [])
- const onSetTheme = useCallback(() => {
- const newTheme = theme === 'dark' ? 'light' : 'dark'
- setThemeAPI(newTheme).then(res => {
- if (isSuccessResponse(res)) {
- setTheme(newTheme)
- }
- })
- }, [theme])
+ const { theme, onSetTheme } = useTheme()
const network = useMemo(() => networks.find(n => n.id === networkID), [networks, networkID])
+ const isLightClient = useMemo(
+ () => networks.find(n => n.id === networkID)?.type === LIGHT_NETWORK_TYPE,
+ [networkID, networks]
+ )
const netWorkTypeLabel = useMemo(() => (network ? getNetworkTypeLabel(network.chain) : ''), [network])
const [syncPercents, syncBlockNumbers] = useMemo(() => {
const bestBlockNumber = Math.max(cacheTipBlockNumber, bestKnownBlockNumber)
@@ -87,23 +82,32 @@ const PageContainer: React.FC = props => {
}`,
]
}, [cacheTipBlockNumber, bestKnownBlockNumber])
+ const isMainnet = useMemo(() => isMainnetUtil(networks, networkID), [networks, networkID])
const onOpenValidTarget = useCallback(
(e: React.SyntheticEvent) => {
e.stopPropagation()
- const explorerUrl = getExplorerUrl(isMainnet(networks, networkID))
+ const explorerUrl = getExplorerUrl(isMainnet)
openExternal(`${explorerUrl}/block/${validTarget}`)
},
- [networks, networkID, validTarget]
+ [isMainnet, validTarget]
)
- const [isMigrate, setIsMigrate] = useState(false)
- useEffect(() => {
- const migrateSubscription = Migrate.subscribe(migrateStatus => {
- setIsMigrate(migrateStatus === 'migrating')
- })
- return () => {
- migrateSubscription.unsubscribe()
- }
- }, [])
+ const isMigrate = useMigrate()
+ const {
+ showSetStartBlock,
+ openDialog,
+ closeDialog,
+ onChangeStartBlockNumber,
+ startBlockNumber,
+ onOpenAddressInExplorer,
+ onViewBlock,
+ onConfirm,
+ } = useSetBlockNumber({
+ firstAddress: addresses[0]?.address,
+ isMainnet,
+ isLightClient,
+ walletID: id,
+ isHomePage,
+ })
return (
@@ -136,6 +140,8 @@ const PageContainer: React.FC = props => {
isLookingValidTarget={isLookingValidTarget}
onOpenValidTarget={onOpenValidTarget}
isMigrate={isMigrate}
+ isLightClient={isLightClient}
+ onOpenSetStartBlock={openDialog}
/>
@@ -148,6 +154,33 @@ const PageContainer: React.FC = props => {
)}
{children}
+
)
}
diff --git a/packages/neuron-ui/src/components/PageContainer/pageContainer.module.scss b/packages/neuron-ui/src/components/PageContainer/pageContainer.module.scss
index 98a148e1bf..e104030a7b 100755
--- a/packages/neuron-ui/src/components/PageContainer/pageContainer.module.scss
+++ b/packages/neuron-ui/src/components/PageContainer/pageContainer.module.scss
@@ -168,3 +168,22 @@
color: var(--main-text-color);
}
}
+
+.startBlockTip {
+ font-weight: 400;
+ color: var(--secondary-text-color);
+}
+
+.viewAction {
+ border: none;
+ background-color: transparent;
+ cursor: pointer;
+ color: var(--primary-color);
+ display: flex;
+ align-items: center;
+
+ & > svg {
+ width: 12px;
+ height: 12px;
+ }
+}
diff --git a/packages/neuron-ui/src/components/SyncStatus/index.tsx b/packages/neuron-ui/src/components/SyncStatus/index.tsx
index a6f3307449..6ff0fb9f70 100644
--- a/packages/neuron-ui/src/components/SyncStatus/index.tsx
+++ b/packages/neuron-ui/src/components/SyncStatus/index.tsx
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'
import { SyncStatus as SyncStatusEnum, ConnectionStatus } from 'utils'
import { Confirming, NewTab } from 'widgets/Icons/icon'
import { ReactComponent as UnexpandStatus } from 'widgets/Icons/UnexpandStatus.svg'
+import { ReactComponent as StartBlock } from 'widgets/Icons/StartBlock.svg'
import Tooltip from 'widgets/Tooltip'
import styles from './syncStatus.module.scss'
@@ -10,10 +11,14 @@ const SyncDetail = ({
syncBlockNumbers,
isLookingValidTarget,
onOpenValidTarget,
+ isLightClient,
+ onOpenSetStartBlock,
}: {
syncBlockNumbers: string
isLookingValidTarget: boolean
onOpenValidTarget: (e: React.SyntheticEvent) => void
+ isLightClient: boolean
+ onOpenSetStartBlock: () => void
}) => {
const [t] = useTranslation()
return (
@@ -26,7 +31,7 @@ const SyncDetail = ({
{isLookingValidTarget && (
)}
+ {isLightClient && (
+
+
+ {t('network-status.tooltip.set-start-block-number')}
+
+
+
+ )}
>
)
}
@@ -49,6 +68,8 @@ const SyncStatus = ({
isLookingValidTarget,
onOpenValidTarget,
isMigrate,
+ isLightClient,
+ onOpenSetStartBlock,
}: React.PropsWithoutRef<{
syncStatus: SyncStatusEnum
connectionStatus: State.ConnectionStatus
@@ -57,12 +78,14 @@ const SyncStatus = ({
isLookingValidTarget: boolean
onOpenValidTarget: (e: React.SyntheticEvent) => void
isMigrate: boolean
+ isLightClient: boolean
+ onOpenSetStartBlock: () => void
}>) => {
const [t] = useTranslation()
const [isOpen, setIsOpen] = useState(false)
- const onChangeIsOpen = useCallback(() => {
- setIsOpen(v => !v)
- }, [setIsOpen])
+ const onVisibleChange = useCallback((v: boolean) => {
+ setIsOpen(v)
+ }, [])
if (isMigrate) {
return
{t('network-status.migrating')}
@@ -91,20 +114,23 @@ const SyncStatus = ({
syncBlockNumbers={syncBlockNumbers}
isLookingValidTarget={isLookingValidTarget}
onOpenValidTarget={onOpenValidTarget}
+ isLightClient={isLightClient}
+ onOpenSetStartBlock={onOpenSetStartBlock}
/>
}
trigger="click"
className={styles.tipContainer}
tipClassName={styles.tip}
+ onVisibleChange={onVisibleChange}
showTriangle
>
{SyncStatusEnum.SyncCompleted === syncStatus ? (
-
)}