Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LIQ-727 Auction Bid Activity #322

Merged
merged 1 commit into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
};
vanminh1701 marked this conversation as resolved.
Show resolved Hide resolved

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;
}

vanminh1701 marked this conversation as resolved.
Show resolved Hide resolved
/**
* This function prevents double call api transaction in useEffect and package react-infinite-scroll-component
*/
export function EMPTY_FUNCTION(): void {
//
}
Loading