diff --git a/core/sdk/src/CandyShopAuctionAPI.ts b/core/sdk/src/CandyShopAuctionAPI.ts index f82ab6d4d..42b8229a1 100644 --- a/core/sdk/src/CandyShopAuctionAPI.ts +++ b/core/sdk/src/CandyShopAuctionAPI.ts @@ -1,4 +1,4 @@ -import { Auction, AuctionBid, ListBase, SingleBase } from '@liqnft/candy-shop-types'; +import { Auction, AuctionBid, AuctionBidQuery, ListBase, SingleBase } from '@liqnft/candy-shop-types'; import { AuctionQuery, fetchAuctionBid, fetchAuctionHistoryByAddress, fetchAuctionsByShop } from './factory/backend'; import axiosInstance from './vendor/config'; @@ -13,6 +13,9 @@ export function fetchAuctionBidByWalletAddress( return fetchAuctionBid(axiosInstance, auctionAddress, walletAddress); } -export function fetchAuctionHistory(auctionAddress: string): Promise> { - return fetchAuctionHistoryByAddress(axiosInstance, auctionAddress); +export function fetchAuctionHistory( + auctionAddress: string, + auctionBidQuery?: AuctionBidQuery +): Promise> { + return fetchAuctionHistoryByAddress(axiosInstance, auctionAddress, auctionBidQuery); } diff --git a/core/sdk/src/factory/backend/AuctionAPI.ts b/core/sdk/src/factory/backend/AuctionAPI.ts index 9159e7919..082df7e02 100644 --- a/core/sdk/src/factory/backend/AuctionAPI.ts +++ b/core/sdk/src/factory/backend/AuctionAPI.ts @@ -1,4 +1,4 @@ -import { SingleBase, ListBase, Auction, AuctionBid, AuctionStatus } from '@liqnft/candy-shop-types'; +import { SingleBase, ListBase, Auction, AuctionBid, AuctionStatus, AuctionBidQuery } from '@liqnft/candy-shop-types'; import { AxiosInstance } from 'axios'; import qs from 'qs'; @@ -52,8 +52,19 @@ export function fetchAuction(axiosInstance: AxiosInstance, auctionAddress: strin export function fetchAuctionHistoryByAddress( axiosInstance: AxiosInstance, - auctionAddress: string + auctionAddress: string, + auctionBidQuery?: AuctionBidQuery ): Promise> { - console.log(`${Logger}: fetching Auction bid history by auctionAddress=`, auctionAddress); - return axiosInstance.get(`/auction/history/${auctionAddress}`).then((res) => res.data); + let queryString = ''; + + if (auctionBidQuery) { + if (auctionBidQuery.orderByArr) { + queryString = qs.stringify({ ...auctionBidQuery, orderByArr: JSON.stringify(auctionBidQuery.orderByArr) }); + } else { + queryString = qs.stringify(auctionBidQuery); + } + } + + console.log(`${Logger}: fetching Auction bid history by auctionAddress=`, auctionAddress, `query=`, queryString); + return axiosInstance.get(`/auction/history/${auctionAddress}?${queryString}`).then((res) => res.data); } diff --git a/core/types/src/query/query.ts b/core/types/src/query/query.ts index 0520f046d..afe52365f 100644 --- a/core/types/src/query/query.ts +++ b/core/types/src/query/query.ts @@ -58,3 +58,7 @@ export interface CollectionQuery extends CommonQuery { export interface ShopQuery extends CommonQuery { name?: string; } + +export interface AuctionBidQuery extends CommonQuery { + orderByArr?: SortBy; +} diff --git a/core/ui/src/components/CollectionFilter/index.tsx b/core/ui/src/components/CollectionFilter/index.tsx index 39f797598..f84f96851 100644 --- a/core/ui/src/components/CollectionFilter/index.tsx +++ b/core/ui/src/components/CollectionFilter/index.tsx @@ -4,7 +4,7 @@ import { ListBase, NftCollection } from '@liqnft/candy-shop-types'; import { Processing } from 'components/Processing'; import { CollectionFilter as CollectionFilterType } from 'model'; import { Search } from 'components/Search'; -import { removeDuplicate } from 'utils/array'; +import { removeDuplicate } from 'utils/helperFunc'; import { LoadStatus } from 'constant'; import '../../style/order-filter.less'; diff --git a/core/ui/src/components/ShopFilter/index.tsx b/core/ui/src/components/ShopFilter/index.tsx index 492bc5039..251d76c5c 100644 --- a/core/ui/src/components/ShopFilter/index.tsx +++ b/core/ui/src/components/ShopFilter/index.tsx @@ -5,7 +5,7 @@ import { LoadStatus } from 'constant'; import { ListBase, CandyShop as CandyShopResponse } from '@liqnft/candy-shop-types'; import { Processing } from 'components/Processing'; import { ShopFilter as ShopFilterInfo } from 'model'; -import { removeDuplicate } from 'utils/array'; +import { removeDuplicate } from 'utils/helperFunc'; import '../../style/order-filter.less'; diff --git a/core/ui/src/index.tsx b/core/ui/src/index.tsx index 9b3276453..f5f5a18a1 100644 --- a/core/ui/src/index.tsx +++ b/core/ui/src/index.tsx @@ -5,6 +5,7 @@ export { OrderDetail } from './public/OrderDetail'; export { Sell } from './public/Sell'; export { Stat } from './public/Stat'; export { Activity } from './public/Activity'; +export { AuctionActivity } from './public/AuctionActivity'; export * from './public/Auction'; export * from './public/Modal'; diff --git a/core/ui/src/public/Activity/index.tsx b/core/ui/src/public/Activity/index.tsx index 2a622478e..7dfaf10a2 100644 --- a/core/ui/src/public/Activity/index.tsx +++ b/core/ui/src/public/Activity/index.tsx @@ -10,7 +10,7 @@ import { Trade, ListBase, ShopStatusType, SortBy } from '@liqnft/candy-shop-type import { useValidateStatus } from 'hooks/useValidateStatus'; import { useUpdateSubject } from 'public/Context/CandyShopDataValidator'; import { ActivityActionsStatus } from 'constant'; -import { removeDuplicate } from 'utils/array'; +import { removeDuplicate, EMPTY_FUNCTION } from 'utils/helperFunc'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; @@ -25,9 +25,6 @@ interface ActivityProps { } const LIMIT = 10; -const DO_NOTHING_FUNC = () => { - // this prevent double call api transaction in useEffect and infinity lib -}; const Logger = 'CandyShopUI/Activity'; @@ -103,7 +100,7 @@ export const Activity: React.FC = ({ candyShop, identifiers, orde } hasMore={hasMore} > diff --git a/core/ui/src/public/AuctionActivity/index.tsx b/core/ui/src/public/AuctionActivity/index.tsx new file mode 100644 index 000000000..35de6a6ad --- /dev/null +++ b/core/ui/src/public/AuctionActivity/index.tsx @@ -0,0 +1,98 @@ +import React, { useState, useCallback, useEffect } from 'react'; + +import { ExplorerLink } from 'components/ExplorerLink'; +import { Processing } from 'components/Processing'; +import InfiniteScroll from 'react-infinite-scroll-component'; + +import { CandyShop, fetchAuctionHistory } from '@liqnft/candy-shop-sdk'; +import { AuctionBid, AuctionStatus, ListBase, ShopStatusType, SortBy } from '@liqnft/candy-shop-types'; +import { useUpdateSubject } from 'public/Context/CandyShopDataValidator'; +import { removeDuplicate, EMPTY_FUNCTION } from 'utils/helperFunc'; + +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +dayjs.extend(relativeTime); +import './style.less'; + +interface AuctionActivityProps { + candyShop: CandyShop; + orderBy?: SortBy; + auctionAddress: string; +} + +const LIMIT = 10; +const Logger = 'CandyShopUI/AuctionActivity'; + +export const AuctionActivity: React.FC = ({ candyShop, auctionAddress, orderBy }) => { + const [bids, setBids] = useState([]); + const [hasMore, setHasMore] = useState(true); + const [offset, setOffset] = useState(0); + + useUpdateSubject({ subject: ShopStatusType.Auction }); + + const getAuctionBids = useCallback( + (offset: number, limit: number, firstLoad?: boolean) => () => { + fetchAuctionHistory(auctionAddress, { offset, limit, orderByArr: orderBy }) + .then((res: ListBase) => { + const { result, offset, totalCount, count, success } = res; + if (!success) { + return setHasMore(false); + } + const hasMore = offset + count < Number(totalCount); + if (hasMore) { + setOffset(offset + count + 1); + } + + setHasMore(hasMore); + setBids((list) => { + if (firstLoad) return result || []; + return removeDuplicate(list, result, 'bidAddress'); + }); + }) + .catch((error: any) => { + console.log(`${Logger}: fetchAuctionsByShopAddress failed, error=`, error); + }); + }, + [auctionAddress, orderBy] + ); + + useEffect(() => { + getAuctionBids(0, LIMIT)(); + }, [getAuctionBids]); + + return ( +
+
+
+ BUYER ADDRESS + PRICE + STATUS +
+ + } + hasMore={hasMore} + > + {bids.map((auction) => { + return ( +
+
+ +
+
+ {`${(Number(auction.price) / candyShop.baseUnitsPerCurrency).toLocaleString(undefined, { + minimumFractionDigits: candyShop.priceDecimalsMin, + maximumFractionDigits: candyShop.priceDecimals + })} ${candyShop.currencySymbol}`} +
+
{AuctionStatus[auction.status]}
+
+ ); + })} +
+
+
+ ); +}; diff --git a/core/ui/src/public/AuctionActivity/style.less b/core/ui/src/public/AuctionActivity/style.less new file mode 100644 index 000000000..7e18ecfd2 --- /dev/null +++ b/core/ui/src/public/AuctionActivity/style.less @@ -0,0 +1,63 @@ +@import '../../variable.less'; + +.candy-activity-auction { + padding: 20px 50px; + font-size: 16px; + + @media @desktopS { + padding: 20px 15px; + } + .infinite-scroll-component { + overflow: unset !important; + } + + .candy-activity-auction-item { + display: grid; + text-align: left; + align-items: center; + + grid-template-columns: minmax(300px, 1fr) 1fr 200px; + > * { + padding: 8px 12px; + border-bottom: 1px solid @gray-light; + min-height: 67px; + display: flex; + align-items: center; + } + + @media @desktopL { + grid-template-columns: minmax(300px, 1fr) 1fr 200px; + } + + img { + width: 50px; + height: 50px; + background-color: @gray-light; + object-fit: cover; + margin-right: 20px; + } + } + + .candy-activity-auction-header { + span { + border-bottom: 2px solid @gray-medium; + font-size: 14px; + color: #757575; + font-weight: 700; + text-align: left; + min-height: unset; + } + } + + .candy-activity-auction-table-container { + overflow: auto; + + .candy-activity-auction-price { + white-space: nowrap; + } + } + + .candy-processing { + margin: 60px auto; + } +} diff --git a/core/ui/src/public/Orders/index.tsx b/core/ui/src/public/Orders/index.tsx index 391d929be..cae336ce1 100644 --- a/core/ui/src/public/Orders/index.tsx +++ b/core/ui/src/public/Orders/index.tsx @@ -23,7 +23,7 @@ import { ShopFilter as ShopFilterComponent } from 'components/ShopFilter'; import { useValidateStatus } from 'hooks/useValidateStatus'; import { useUpdateSubject } from 'public/Context/CandyShopDataValidator'; import { CollectionFilter, ShopFilter, OrderDefaultFilter } from 'model'; -import { removeDuplicate } from 'utils/array'; +import { removeDuplicate } from 'utils/helperFunc'; import { OrdersActionsStatus } from 'constant'; import { ORDER_FETCH_LIMIT, SORT_OPTIONS } from 'constant/Orders'; import './index.less'; diff --git a/core/ui/src/utils/array.ts b/core/ui/src/utils/helperFunc.ts similarity index 87% rename from core/ui/src/utils/array.ts rename to core/ui/src/utils/helperFunc.ts index 774fe926f..271bada10 100644 --- a/core/ui/src/utils/array.ts +++ b/core/ui/src/utils/helperFunc.ts @@ -9,3 +9,7 @@ export function removeDuplicate(oldList: T[], addList: T[], key: keyof T): T[ } return newList; } + +export function EMPTY_FUNCTION(): void { + // +} diff --git a/example/AuctionExample.tsx b/example/AuctionExample.tsx index 14d7c7736..90ccb26fa 100644 --- a/example/AuctionExample.tsx +++ b/example/AuctionExample.tsx @@ -3,8 +3,8 @@ import { WalletMultiButton } from '@solana/wallet-adapter-ant-design'; import { useAnchorWallet } from '@solana/wallet-adapter-react'; import { CandyShop, SingleTokenInfo } from '../core/sdk/.'; -import { CreateAuction, Auctions } from '../core/ui/.'; -import { AuctionStatus } from '../core/types/.'; +import { CreateAuction, Auctions, AuctionActivity } from '../core/ui/.'; +import { AuctionStatus, SortBy } from '../core/types/.'; import 'antd/dist/antd.min.css'; @@ -38,8 +38,17 @@ export const AuctionExample: React.FC = ({ candyShop }) => walletConnectComponent={} statusFilters={AUCTION_FILTER} /> + +

Auction Activities

+ ); }; +const AUCTION_ORDER: SortBy = { column: 'price', order: 'desc' }; + const AUCTION_FILTER = [AuctionStatus.CREATED, AuctionStatus.STARTED, AuctionStatus.EXPIRED, AuctionStatus.COMPLETE];