kodadot/nft-gallery

View on GitHub
components/items/ItemsGrid/useItemsGrid.ts

Summary

Maintainability
C
1 day
Test Coverage
File `useItemsGrid.ts` has 265 lines of code (exceeds 250 allowed). Consider refactoring.
import isEqual from 'lodash/isEqual'
import type { Ref } from 'vue'
import {
useItemsGridQueryParams,
useSearchParams,
} from './utils/useSearchParams'
import { getDenyList } from '@/utils/prefix'
import type { NFTWithMetadata, TokenEntity } from '@/composables/useNft'
import { nftToListingCartItem } from '@/components/common/shoppingCart/utils'
import { useListingCartStore } from '@/stores/listingCart'
import type { NFT, TokenId } from '@/types'
import { fetchOdaToken } from '@/services/oda'
import tokenListWithSearch from '@/queries/subsquid/general/tokenListWithSearch'
import nftListWithSearch from '@/queries/subsquid/general/nftListWithSearch'
 
const DEFAULT_RESET_SEARCH_QUERY_PARAMS = [
'sort',
'search',
'listed',
'min',
'max',
'owned',
'collections',
]
 
const EXCLUDED_TOKEN_USE_PAGES = [
'prefix-collection-id',
'prefix-drops-id',
'prefix-u-id',
...ATOMIC_SWAP_PAGES,
]
 
Function `useFetchSearch` has a Cognitive Complexity of 46 (exceeds 5 allowed). Consider refactoring.
export function useFetchSearch({
first,
total,
isFetchingData,
resetSearch,
isLoading,
resetSearchQueryParams = DEFAULT_RESET_SEARCH_QUERY_PARAMS,
fetchOnchainData = false,
}: {
first: Ref<number>
total: Ref<number>
isFetchingData: Ref<boolean>
isLoading: Ref<boolean>
resetSearch: () => void
resetSearchQueryParams?: string[]
fetchOnchainData?: boolean
}) {
const { client, urlPrefix } = usePrefix()
const { isAssetHub } = useIsChain(urlPrefix)
const route = useRoute()
 
const useTokens = computed(
() =>
isAssetHub.value
&& !EXCLUDED_TOKEN_USE_PAGES.includes(route.name as string),
)
 
const items = ref<(NFTWithMetadata | TokenEntity)[]>([])
const loadedPages = ref([] as number[])
 
const { searchParams } = useSearchParams()
const { searchBySn } = useItemsGridQueryParams()
 
interface FetchSearchParams {
page?: number
loadDirection?: 'up' | 'down'
search?: { [key: string]: string | number }[]
}
 
const hasBlockNumberSort = (items: string[]) =>
['blockNumber_DESC', 'blockNumber_ASC'].some(sort => items.includes(sort))
 
const getSearchCriteria = (searchParams, reducer = {}) => {
const mapping = {
currentOwner_eq: value => ({ owner: value }),
issuer_eq: value => ({ issuer: value }),
price_gt: value => ({ price_gt: Number(value) }),
collection: value => ({ collections: value.id_in }),
name_containsInsensitive: value => ({
name: value,
}),
}
 
return searchParams.reduce((acc, curr) => {
for (const [key, value] of Object.entries(curr)) {
if (Array.isArray(value)) {
return getSearchCriteria(value, acc)
}
 
if (mapping[key]) {
Object.assign(acc, mapping[key](value))
}
}
return acc
}, reducer)
}
 
async function fetchSearch({
page = 1,
loadDirection = 'down',
search,
}: FetchSearchParams) {
if (isFetchingData.value) {
return false
}
isFetchingData.value = true
 
const queryName = useTokens.value
? tokenListWithSearch
: nftListWithSearch
 
const getRouteQueryOrderByDefault = (query, defaultValue: string[]) => {
query = [query].filter(Boolean).flat() as string[]
let orderBy = query.length ? query : defaultValue
if (searchBySn.value && !hasBlockNumberSort(query)) {
orderBy = [...orderBy, 'blockNumber_ASC'].filter(
o => o !== 'blockNumber_DESC',
)
}
return orderBy
}
 
const getQueryResults = (query) => {
if (useTokens.value) {
return {
entities: query.tokenEntities as TokenEntity[],
count: query.tokenEntityCount.totalCount as number,
}
}
return {
entities: query.nFTEntities as NFTWithMetadata[],
count: query.nftEntitiesConnection.totalCount as number,
}
}
 
// Query path and variables
 
const defaultSearchVariables = {
first: first.value,
offset: (page - 1) * first.value,
orderBy: getRouteQueryOrderByDefault(route.query.sort, [
'blockNumber_DESC',
]),
}
 
const isPriceSortActive = (sort?: string | null | (string | null)[]) => {
if (!sort) {
return false
}
const sortArray = Array.isArray(sort) ? sort : [sort]
return sortArray.some(sortBy => (sortBy ?? '').includes('price'))
}
watch(
() => route.query.sort,
(newSort, oldSort) => {
const priceSortHasBeenActivated
= isPriceSortActive(newSort) && !isPriceSortActive(oldSort)
if (priceSortHasBeenActivated && route.query.listed !== 'true') {
route.query.listed = 'true'
}
},
)
 
const searchForToken = getSearchCriteria(
search?.length ? search : searchParams.value,
)
 
const tokenQueryVariables = {
...searchForToken,
denyList: getDenyList(urlPrefix.value),
price_lte: Number(route.query.max) || undefined,
price_gte: Number(route.query.min) || undefined,
}
 
const nftQueryVariables = search?.length
? { search }
: {
search: searchParams.value,
priceMin: Number(route.query.min),
priceMax: Number(route.query.max),
}
 
const queryVariables = useTokens.value
? { ...defaultSearchVariables, ...tokenQueryVariables }
: { ...defaultSearchVariables, ...nftQueryVariables, orderBy: getRouteQueryOrderByDefault(route.query.sort, [
'blockNumber_DESC',
'sn_DESC',
]) }
 
const { $apolloClient } = useNuxtApp()
const { data: result } = await $apolloClient.query({
query: queryName,
variables: queryVariables,
context: {
endpoint: client.value,
},
})
 
// handle results
const { entities, count } = getQueryResults(result)
total.value = count
 
if (!loadedPages.value.includes(page)) {
if (loadDirection === 'up') {
items.value = [...entities, ...items.value]
}
else {
items.value = [...items.value, ...entities]
}
loadedPages.value.push(page)
}
 
isFetchingData.value = false
isLoading.value = false
return true
}
 
function clearFetchResults() {
items.value = []
loadedPages.value = []
}
 
const refetch = (search?: { [key: string]: string | number }[]) => {
clearFetchResults()
fetchSearch({ search })
}
 
const resetSearchQueryParamsValues = computed(() =>
Object.fromEntries(
resetSearchQueryParams.map(key => [key, route.query[key]]),
),
)
 
watch(resetSearchQueryParamsValues, (currentQuery, prevQuery) => {
if (isEqual(currentQuery, prevQuery)) {
return
}
loadedPages.value = []
resetSearch()
})
 
Function `processOnchainData` has 27 lines of code (exceeds 25 allowed). Consider refactoring.
const processOnchainData = useDebounceFn(async () => {
items.value = await Promise.all(items.value.map(async (item) => {
if ('onchainData' in item && item.onchainData) {
return item
}
 
if (item.sn && !isTokenEntity(item)) {
const tokenData = await fetchOdaToken(urlPrefix.value, item.collection.id, item.sn)
 
Consider simplifying this complex logical expression.
if (tokenData.metadata && tokenData.metadata_uri) {
const odaItem = {
...item,
name: tokenData.metadata?.name || item.meta.name,
meta: {
...item.meta,
name: tokenData.metadata?.name || item.meta.name,
id: tokenData.metadata?.image || item.meta.id,
image: tokenData.metadata?.image || item.meta.image,
animationUrl: tokenData.metadata?.animation_url || item.meta.animationUrl,
},
onchainData: true,
}
 
return odaItem
}
}
 
return {
...item,
name: item.collection.name,
}
}))
}, 500)
 
watch(() => items.value.length, () => {
if (isAssetHub.value && items.value.length && fetchOnchainData) {
processOnchainData()
}
}, { immediate: true })
 
return {
items,
fetchSearch,
refetch,
clearFetchResults,
usingTokens: useTokens,
}
}
 
export const updatePotentialNftsForListingCart = async (
nfts: (NFT & TokenId)[],
) => {
const listingCartStore = useListingCartStore()
const { isCurrentAccount } = useAuth()
const potentialNfts = nfts
.filter(nft => !Number(nft.price) && isCurrentAccount(nft.currentOwner))
.map((nft) => {
const floorPrice = nft.collection.floorPrice[0]?.price || '0'
return nftToListingCartItem(nft, floorPrice)
})
 
listingCartStore.setUnlistedItems(potentialNfts)
}