Skip to content

Commit

Permalink
LIQ-727 Auction Bid Activity (#322)
Browse files Browse the repository at this point in the history
Co-authored-by: tvminh <[email protected]>
  • Loading branch information
vanminh1701 and vanminhtran authored Aug 3, 2022
1 parent 8e9b126 commit 13edf50
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 39 deletions.
9 changes: 6 additions & 3 deletions core/sdk/src/CandyShopAuctionAPI.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -13,6 +13,9 @@ export function fetchAuctionBidByWalletAddress(
return fetchAuctionBid(axiosInstance, auctionAddress, walletAddress);
}

export function fetchAuctionHistory(auctionAddress: string): Promise<ListBase<AuctionBid>> {
return fetchAuctionHistoryByAddress(axiosInstance, auctionAddress);
export function fetchAuctionHistory(
auctionAddress: string,
auctionBidQuery?: AuctionBidQuery
): Promise<ListBase<AuctionBid>> {
return fetchAuctionHistoryByAddress(axiosInstance, auctionAddress, auctionBidQuery);
}
31 changes: 21 additions & 10 deletions core/sdk/src/factory/backend/AuctionAPI.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -23,17 +23,15 @@ export function fetchAuctionsByShop(
if (offset !== undefined) {
queryObj = { offset, limit };
}

if (status?.length) {
queryObj = { ...queryObj, status };
}

if (walletAddress) {
queryObj = { ...queryObj, walletAddress };
}

const queryString = qs.stringify(queryObj, { indices: false });
return axiosInstance.get<ListBase<Auction>>(`/auction/${shopId}?${queryString}`).then((response) => response.data);
const url = `/auction/${shopId}?${queryString}`;
return axiosInstance.get<ListBase<Auction>>(url).then((response) => response.data);
}

export function fetchAuctionBid(
Expand All @@ -42,18 +40,31 @@ export function fetchAuctionBid(
walletAddress: string
): Promise<SingleBase<AuctionBid>> {
console.log(`${Logger}: fetching Auction bid by auctionAddress=${auctionAddress}, walletAddress=${walletAddress}`);
return axiosInstance.get(`/auction/${auctionAddress}/wallet/${walletAddress}`).then((res) => res.data);
const url = `/auction/${auctionAddress}/wallet/${walletAddress}`;
return axiosInstance.get(url).then((res) => res.data);
}

export function fetchAuction(axiosInstance: AxiosInstance, auctionAddress: string): Promise<SingleBase<Auction>> {
console.log(`${Logger}: fetching Auction detail, auctionAddress=${auctionAddress}`);
return axiosInstance.get(`/auction/${auctionAddress}`).then((res) => res.data);
const url = `/auction/${auctionAddress}`;
return axiosInstance.get(url).then((res) => res.data);
}

export function fetchAuctionHistoryByAddress(
axiosInstance: AxiosInstance,
auctionAddress: string
auctionAddress: string,
auctionBidQuery?: AuctionBidQuery
): Promise<ListBase<AuctionBid>> {
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);
const url = `/auction/history/${auctionAddress}?${queryString}`;
return axiosInstance.get(url).then((res) => res.data);
}
3 changes: 3 additions & 0 deletions core/types/src/query/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,6 @@ export interface ShopQuery extends CommonQuery {
}

export interface DropQuery extends CommonQuery {}
export interface AuctionBidQuery extends CommonQuery {
orderByArr?: SortBy;
}
7 changes: 2 additions & 5 deletions core/ui/src/components/Auction/AuctionForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);

import './style.less';
import { EMPTY_FUNCTION } from 'utils/helperFunc';

interface AuctionFormProps {
onSubmit: (...args: any) => void;
Expand Down Expand Up @@ -304,7 +305,7 @@ export const AuctionForm: React.FC<AuctionFormProps> = ({
id="auctionClockFormat"
name="auctionClockFormat"
onInvalid={(e) => (e.target as HTMLInputElement).setCustomValidity('Clock format is required.')}
onChange={DO_NOTHING}
onChange={EMPTY_FUNCTION}
/>
</div>
</div>
Expand All @@ -320,10 +321,6 @@ export const AuctionForm: React.FC<AuctionFormProps> = ({
);
};

const DO_NOTHING = () => {
//
};

const PERIODS = [
{ label: '1h', value: 1 },
{ label: '6h', value: 6 },
Expand Down
2 changes: 1 addition & 1 deletion core/ui/src/components/CollectionFilter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion core/ui/src/components/ShopFilter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
1 change: 1 addition & 0 deletions core/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
7 changes: 2 additions & 5 deletions core/ui/src/public/Activity/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -103,7 +100,7 @@ export const Activity: React.FC<ActivityProps> = ({ candyShop, identifiers, orde

<InfiniteScroll
dataLength={trades.length}
next={offset === 0 ? DO_NOTHING_FUNC : getTrades(offset, LIMIT)}
next={offset === 0 ? EMPTY_FUNCTION : getTrades(offset, LIMIT)}
loader={<Processing />}
hasMore={hasMore}
>
Expand Down
99 changes: 99 additions & 0 deletions core/ui/src/public/AuctionActivity/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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<AuctionActivityProps> = ({ candyShop, auctionAddress, orderBy }) => {
const [bids, setBids] = useState<AuctionBid[]>([]);
const [hasMore, setHasMore] = useState<boolean>(true);
const [offset, setOffset] = useState<number>(0);

useUpdateSubject({ subject: ShopStatusType.Auction });

const getAuctionBids = useCallback(
(offset: number, limit: number, firstLoad?: boolean) => () => {
fetchAuctionHistory(auctionAddress, { offset, limit, orderByArr: orderBy })
.then((res: ListBase<AuctionBid>) => {
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<AuctionBid>(list, result, 'bidAddress');
});
})
.catch((error: any) => {
setHasMore(false);
console.log(`${Logger}: fetchAuctionHistory failed, error=`, error);
});
},
[auctionAddress, orderBy]
);

useEffect(() => {
getAuctionBids(0, LIMIT)();
}, [getAuctionBids]);

return (
<div className="candy-activity-auction">
<div className="candy-activity-auction-table-container" id="candy-activity-auction-scroll-target">
<div className="candy-activity-auction-header candy-activity-auction-item">
<span>BUYER ADDRESS</span>
<span>PRICE</span>
<span>STATUS</span>
</div>

<InfiniteScroll
dataLength={bids.length}
next={offset === 0 ? EMPTY_FUNCTION : getAuctionBids(offset, LIMIT)}
loader={<Processing />}
hasMore={hasMore}
>
{bids.map((auction) => {
return (
<div key={auction.bidAddress} className="candy-activity-auction-item">
<div>
<ExplorerLink type="address" address={auction.buyerAddress} />
</div>
<div className="candy-activity-auction-price">
{`${(Number(auction.price) / candyShop.baseUnitsPerCurrency).toLocaleString(undefined, {
minimumFractionDigits: candyShop.priceDecimalsMin,
maximumFractionDigits: candyShop.priceDecimals
})} ${candyShop.currencySymbol}`}
</div>
<div>{AuctionStatus[auction.status]}</div>
</div>
);
})}
</InfiniteScroll>
</div>
</div>
);
};
63 changes: 63 additions & 0 deletions core/ui/src/public/AuctionActivity/style.less
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 1 addition & 1 deletion core/ui/src/public/Orders/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
11 changes: 0 additions & 11 deletions core/ui/src/utils/array.ts

This file was deleted.

28 changes: 28 additions & 0 deletions core/ui/src/utils/helperFunc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Some component has infinity load logic. Because APIs with pagination can return same item,
* so this function to help filter that item out of list
* and guarantee React render function and duplicate item UI
*
* @param oldList current list is saved in local state
* @param addList new list is from api response
* @param key unique field in each item of list
* @returns
*/
export function removeDuplicate<T>(oldList: T[], addList: T[], key: keyof T): T[] {
const duplicateList = [...oldList, ...addList];
const newList: T[] = [];
const memo: any = {};
for (const item of duplicateList) {
if (memo[item[key]]) break;
newList.push(item);
memo[item[key]] = true;
}
return newList;
}

/**
* This function prevents double call api transaction in useEffect and package react-infinite-scroll-component
*/
export function EMPTY_FUNCTION(): void {
//
}
Loading

0 comments on commit 13edf50

Please sign in to comment.