Skip to content

Commit

Permalink
Refactor: some fetcher-related code (#121)
Browse files Browse the repository at this point in the history
* refactor: not using requesterV2 directly outside of ExplorerService

* refactor: export the api in fetcher wrapped in an object

* refactor: provide multiple utility functions for fetcher

* refactor: use more appropriate utility functions for some fetch functions

* refactor: TransactionCellScript

* refactor: remove unnecessary types from fetcher

* refactor: search component
  • Loading branch information
WhiteMinds authored Oct 25, 2023
1 parent 8394e75 commit 0d82655
Show file tree
Hide file tree
Showing 49 changed files with 1,145 additions and 1,373 deletions.
6 changes: 3 additions & 3 deletions src/components/Card/HashCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ export default ({
}

const handleExportTxClick = async () => {
const res = await explorerService.api.requesterV2(`transactions/${hash}/raw`).catch(error => {
const raw = await explorerService.api.fetchTransactionRaw(hash).catch(error => {
setToast({ message: error.message })
})
if (!res) return
if (typeof raw !== 'object') return

const blob = new Blob([JSON.stringify(res.data, null, 2)])
const blob = new Blob([JSON.stringify(raw, null, 2)])

const link = document.createElement('a')
link.download = `tx-${hash}.json`
Expand Down
7 changes: 3 additions & 4 deletions src/components/Header/BlockchainComp/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,9 @@ export default memo(() => {
const query = useQuery(
['node_version'],
async () => {
const wrapper = await explorerService.api.fetchNodeVersion()
const nodeVersion = wrapper.attributes.version
storeCachedData(AppCachedKeys.Version, `${nodeVersion}&${new Date().getTime()}`)
return nodeVersion
const { version } = await explorerService.api.fetchNodeVersion()
storeCachedData(AppCachedKeys.Version, `${version}&${new Date().getTime()}`)
return version
},
{
keepPreviousData: true,
Expand Down
26 changes: 6 additions & 20 deletions src/components/NftCollectionInventory/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { AxiosResponse } from 'axios'
import { Link } from 'react-router-dom'
import { useQuery } from 'react-query'
import { Base64 } from 'js-base64'
Expand All @@ -10,31 +9,18 @@ import styles from './styles.module.scss'
import { getPrimaryColor } from '../../constants/common'
import { explorerService } from '../../services/ExplorerService'
import { handleNftImgError, patchMibaoImg } from '../../utils/util'
import type { NFTItem } from '../../services/ExplorerService/fetcher'

const primaryColor = getPrimaryColor()

type NftCollectionInventoryItem = {
icon_url: string | null
id: number
token_id: string
owner?: string
standard: string
cell: {
cell_index: number
data: string
status: string
tx_hash: string
} | null
}

const NftCollectionInventory: React.FC<{
list: Array<NftCollectionInventoryItem>
list: Array<NFTItem>
collection: string
isLoading: boolean
}> = ({ list, collection, isLoading }) => {
const { t } = useTranslation()
const { data: info } = useQuery<AxiosResponse<{ icon_url: string | null }>>(['collection-info', collection], () =>
explorerService.api.requesterV2(`nft/collections/${collection}`),
const { data: info } = useQuery(['collection-info', collection], () =>
explorerService.api.fetchNFTCollection(collection),
)

if (!list.length) {
Expand All @@ -53,8 +39,8 @@ const NftCollectionInventory: React.FC<{
)
}

const renderCover = (item: NftCollectionInventoryItem) => {
const coverUrl = item.icon_url ?? info?.data.icon_url
const renderCover = (item: NFTItem) => {
const coverUrl = item.icon_url ?? info?.icon_url
const cell = item?.cell
const standard = item?.standard

Expand Down
17 changes: 1 addition & 16 deletions src/components/NftCollectionOverview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { AxiosResponse } from 'axios'
import { Link } from 'react-router-dom'
import { useQuery } from 'react-query'
import { Tooltip } from 'antd'
Expand All @@ -10,23 +9,9 @@ import { getPrimaryColor } from '../../constants/common'

const primaryColor = getPrimaryColor()

interface InfoRes {
id: number
standard: string
name: string
description: string
creator: string | null
icon_url: string | null
items_count: number | null
holders_count: number | null
}

const NftCollectionOverview = ({ id }: { id: string }) => {
const { t } = useTranslation()
const { isLoading, data } = useQuery<AxiosResponse<InfoRes>>(['collection-info', id], () =>
explorerService.api.requesterV2(`nft/collections/${id}`),
)
const info = data?.data
const { isLoading, data: info } = useQuery(['collection-info', id], () => explorerService.api.fetchNFTCollection(id))

return (
<div className={styles.container}>
Expand Down
12 changes: 6 additions & 6 deletions src/components/NftCollectionTransfers/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { AxiosResponse } from 'axios'
import { FC, useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import { useQuery } from 'react-query'
Expand All @@ -7,7 +6,8 @@ import { Base64 } from 'js-base64'
import { hexToBytes } from '@nervosnetwork/ckb-sdk-utils'
import { useTranslation } from 'react-i18next'
import { parseSporeCellData } from '../../utils/spore'
import type { TransferListRes, TransferRes } from '../../pages/NftCollectionInfo'
// TODO: Refactor is needed. Should not directly import anything from the descendants of ExplorerService.
import type { TransferListRes, TransferRes } from '../../services/ExplorerService/fetcher'
import styles from './styles.module.scss'
import { getPrimaryColor } from '../../constants/common'
import { handleNftImgError, patchMibaoImg } from '../../utils/util'
Expand All @@ -28,14 +28,14 @@ interface TransferCollectionProps {
const NftCollectionTransfers: FC<TransferCollectionProps> = props => {
const { collection } = props

const { data: info } = useQuery<AxiosResponse<{ icon_url: string | null }>>(['collection-info', collection], () =>
explorerService.api.requesterV2(`nft/collections/${collection}`),
const { data: info } = useQuery(['collection-info', collection], () =>
explorerService.api.fetchNFTCollection(collection),
)

return (
<div className={styles.list}>
<TransferTable {...props} iconURL={info?.data.icon_url} />
<TransferCardGroup {...props} iconURL={info?.data.icon_url} />
<TransferTable {...props} iconURL={info?.icon_url} />
<TransferCardGroup {...props} iconURL={info?.icon_url} />
</div>
)
}
Expand Down
33 changes: 2 additions & 31 deletions src/components/NftItemTransfers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,11 @@ import { getPrimaryColor } from '../../constants/common'
import { dayjs, useParseDate } from '../../utils/date'
import styles from './styles.module.scss'
import { useCurrentLanguage } from '../../utils/i18n'
import type { TransferRes } from '../../services/ExplorerService/fetcher'

const primaryColor = getPrimaryColor()

export interface TransferListRes {
data: Array<{
id: number
from: string | null
to: string | null
action: 'mint' | 'normal' | 'destruction'
item: {
id: number
collection_id: number
token_id: string
name: string | null
icon_url: string | null
owner_id: number
metadata_url: string | null
cell_id: number | null
}
transaction: {
tx_hash: string
block_number: number
block_timestamp: number
}
}>
pagination: {
count: number
page: number
next: number | null
prev: number | null
last: number
}
}
const NftItemTransfers: React.FC<{ list: TransferListRes['data']; isLoading: boolean }> = ({ list, isLoading }) => {
const NftItemTransfers: React.FC<{ list: TransferRes[]; isLoading: boolean }> = ({ list, isLoading }) => {
const [isShowInAge, setIsShowInAge] = useState(false)
const { t } = useTranslation()
const parseDate = useParseDate()
Expand Down
97 changes: 50 additions & 47 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState, useRef, useEffect, FC, memo, RefObject, ChangeEvent } from 'react'
import { useHistory } from 'react-router'
import { AxiosError } from 'axios'
import { TFunction, useTranslation } from 'react-i18next'
import { SearchImage, SearchInputPanel, SearchPanel, SearchButton, SearchContainer } from './styled'
import { explorerService, Response } from '../../services/ExplorerService'
Expand All @@ -10,14 +9,9 @@ import { addPrefixForHash, containSpecialChar } from '../../utils/string'
import { HttpErrorCode, SearchFailType } from '../../constants/common'
import { useIsMobile } from '../../utils/hook'
import { isChainTypeError } from '../../utils/chain'

enum SearchResultType {
Block = 'block',
Transaction = 'ckb_transaction',
Address = 'address',
LockHash = 'lock_hash',
UDT = 'udt',
}
import { isAxiosError } from '../../utils/error'
// TODO: Refactor is needed. Should not directly import anything from the descendants of ExplorerService.
import { SearchResultType } from '../../services/ExplorerService/fetcher'

const clearSearchInput = (inputElement: RefObject<HTMLInputElement>) => {
const input = inputElement.current
Expand All @@ -41,7 +35,7 @@ const setSearchContent = (inputElement: RefObject<HTMLInputElement>, content: st
}
}

const handleSearchResult = (
const handleSearchResult = async (
searchValue: string,
inputElement: RefObject<HTMLInputElement>,
setSearchValue: Function,
Expand All @@ -59,44 +53,53 @@ const handleSearchResult = (
}

setSearchLoading(inputElement, t)
explorerService.api
.fetchSearchResult(addPrefixForHash(query))
.then((response: any) => {
const { data } = response
if (!response || !data.type) {
history.push(`/search/fail?q=${query}`)
return
}
clearSearchInput(inputElement)
setSearchValue('')
if (data.type === SearchResultType.Block) {
history.push(`/block/${(data as Response.Wrapper<State.Block>).attributes.blockHash}`)
} else if (data.type === SearchResultType.Transaction) {
history.push(`/transaction/${(data as Response.Wrapper<State.Transaction>).attributes.transactionHash}`)
} else if (data.type === SearchResultType.Address) {
history.push(`/address/${(data as Response.Wrapper<State.Address>).attributes.addressHash}`)
} else if (data.type === SearchResultType.LockHash) {
history.push(`/address/${(data as Response.Wrapper<State.Address>).attributes.lockHash}`)
} else if (data.type === SearchResultType.UDT) {

try {
const { data } = await explorerService.api.fetchSearchResult(addPrefixForHash(query))
clearSearchInput(inputElement)
setSearchValue('')

switch (data.type) {
case SearchResultType.Block:
history.push(`/block/${data.attributes.blockHash}`)
break

case SearchResultType.Transaction:
history.push(`/transaction/${data.attributes.transactionHash}`)
break

case SearchResultType.Address:
history.push(`/address/${data.attributes.addressHash}`)
break

case SearchResultType.LockHash:
history.push(`/address/${data.attributes.lockHash}`)
break

case SearchResultType.UDT:
history.push(`/sudt/${query}`)
}
})
.catch((error: AxiosError) => {
setSearchContent(inputElement, query)
if (
error.response &&
error.response.data &&
error.response.status === 404 &&
(error.response.data as Response.Error[]).find(
(errorData: Response.Error) => errorData.code === HttpErrorCode.NOT_FOUND_ADDRESS,
)
) {
clearSearchInput(inputElement)
history.push(`/address/${query}`)
} else {
history.push(`/search/fail?q=${query}`)
}
})
break

default:
break
}
} catch (error) {
setSearchContent(inputElement, query)

if (
isAxiosError(error) &&
error.response?.data &&
error.response.status === 404 &&
(error.response.data as Response.Error[]).find(
(errorData: Response.Error) => errorData.code === HttpErrorCode.NOT_FOUND_ADDRESS,
)
) {
clearSearchInput(inputElement)
history.push(`/address/${query}`)
} else {
history.push(`/search/fail?q=${query}`)
}
}
}

const Search: FC<{
Expand Down
25 changes: 16 additions & 9 deletions src/components/Toast/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { useTimeoutWithUnmount } from '../../utils/hook'
import { ToastItemPanel, ToastPanel } from './styled'
import { createGlobalState, useGlobalState } from '../../utils/state'

const getColor = (type: 'success' | 'warning' | 'danger') => {
interface ToastMessage {
message: string
type: 'success' | 'warning' | 'danger'
duration?: number
id: number
}

const getColor = (type: ToastMessage['type']) => {
switch (type) {
case 'success':
return '#3cc68a'
Expand All @@ -20,7 +27,7 @@ const ANIMATION_DISAPPEAR_TIME = 2000
const MAX_FRAME: number = (ANIMATION_DISAPPEAR_TIME / 1000) * 40 // suppose fps = 40
const DEFAULT_TOAST_DURATION = 3000

const ToastItem = ({ data, willLeave }: { data: State.ToastMessage; willLeave: Function }) => {
const ToastItem = ({ data, willLeave }: { data: ToastMessage; willLeave: Function }) => {
const [opacity, setOpacity] = useState(1)
let animationId: number = 0
useTimeoutWithUnmount(
Expand Down Expand Up @@ -60,19 +67,19 @@ const ToastItem = ({ data, willLeave }: { data: State.ToastMessage; willLeave: F
}

interface State {
toasts: State.ToastMessage[]
toasts: ToastMessage[]
toast: string
}

interface Action {
type: 'ADD' | 'REMOVE'
payload: {
toast: State.ToastMessage
toast: ToastMessage
}
}

const initialState: State = {
toasts: [] as State.ToastMessage[],
toasts: [],
toast: '',
}

Expand All @@ -86,20 +93,20 @@ const reducer = (state: State, action: Action) => {
case 'REMOVE':
return {
...state,
toasts: state.toasts.filter((toast: State.ToastMessage) => toast.id !== action.payload.toast.id),
toasts: state.toasts.filter((toast: ToastMessage) => toast.id !== action.payload.toast.id),
}
default:
return state
}
}

const globalToast = createGlobalState<State.ToastMessage | null>(null)
const globalToast = createGlobalState<ToastMessage | null>(null)

export function useSetToast() {
const [, setToast] = useGlobalState(globalToast)

return useCallback(
(data: Pick<State.ToastMessage, 'message' | 'duration'> & Partial<Pick<State.ToastMessage, 'type'>>) =>
(data: Pick<ToastMessage, 'message' | 'duration'> & Partial<Pick<ToastMessage, 'type'>>) =>
setToast({
id: new Date().getTime(),
message: data.message,
Expand Down Expand Up @@ -128,7 +135,7 @@ export default () => {
return state.toasts.length === 0 ? null : (
<ToastPanel className="toast">
{state.toasts &&
state.toasts.map((item: State.ToastMessage) => (
state.toasts.map((item: ToastMessage) => (
<ToastItem
willLeave={() => {
dispatch({
Expand Down
Loading

1 comment on commit 0d82655

@vercel
Copy link

@vercel vercel bot commented on 0d82655 Oct 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.