Index and show pages for auctions

This commit is contained in:
Jordan Prince 2021-04-26 16:59:16 -05:00
parent 2f036f7009
commit a46537221a
14 changed files with 583 additions and 136 deletions

View File

@ -45,6 +45,10 @@ export class BidState {
}
}
export const decodeAuction = (buffer: Buffer) => {
return deserializeBorsh(AUCTION_SCHEMA, AuctionData, buffer) as AuctionData;
};
export const BASE_AUCTION_DATA_SIZE = 32 + 32 + 32 + 8 + 8 + 1 + 9 + 9 + 9 + 9;
export class AuctionData {
@ -67,6 +71,9 @@ export class AuctionData {
/// Gap time is the amount of time in slots after the previous bid at which the auction ends.
endAuctionGap?: BN;
/// Used for precalculation on the front end, not a backend key
auctionManagerKey?: PublicKey;
constructor(args: {
authority: PublicKey;
resource: PublicKey;
@ -247,7 +254,7 @@ export const AUCTION_SCHEMA = new Map<any, any>([
['resource', 'pubkey'],
['tokenMint', 'pubkey'],
['state', 'u8'],
['bidState', 'BidState'],
['bidState', BidState],
['lastBid', { kind: 'option', type: 'u64' }],
['endedAt', { kind: 'option', type: 'u64' }],
['endAuctionAt', { kind: 'option', type: 'u64' }],

View File

@ -258,6 +258,14 @@ export const decodeVault = (buffer: Buffer) => {
return deserializeBorsh(VAULT_SCHEMA, Vault, buffer) as Vault;
};
export const decodeSafetyDeposit = (buffer: Buffer) => {
return deserializeBorsh(
VAULT_SCHEMA,
SafetyDepositBox,
buffer,
) as SafetyDepositBox;
};
export async function initVault(
allowFurtherShareCreation: boolean,
fractionalMint: PublicKey,

View File

@ -1,39 +1,46 @@
import React, { useEffect, useState } from 'react';
import {
Row,
Col,
Divider,
Button,
InputNumber
} from 'antd'
import { Row, Col, Divider, Button, InputNumber } from 'antd';
import { Auction, Presale } from '../../types'
import { Auction, Presale } from '../../types';
import './index.less'
import { getCountdown } from '../../utils/utils'
import { shortenAddress } from '@oyster/common';
import './index.less';
import { getCountdown } from '../../utils/utils';
import { shortenAddress, useConnection } from '@oyster/common';
import { AuctionView } from '../../hooks';
export const AuctionCard = ({ auction }: { auction: Auction }) => {
const [hours, setHours] = useState<number>(23)
const [minutes, setMinutes] = useState<number>(59)
const [seconds, setSeconds] = useState<number>(59)
export const AuctionCard = ({ auctionView }: { auctionView: AuctionView }) => {
const [hours, setHours] = useState<number>(23);
const [minutes, setMinutes] = useState<number>(59);
const [seconds, setSeconds] = useState<number>(59);
const [clock, setClock] = useState<number>(0);
const connection = useConnection();
useEffect(() => {
connection.getSlot().then(setClock);
}, [connection]);
useEffect(() => {
const interval = setInterval(() => {
const { hours, minutes, seconds } = getCountdown(auction.endingTS)
const slotDiff =
(auctionView.auction.info.endedAt?.toNumber() || 0) - clock;
setHours(hours)
setMinutes(minutes)
setSeconds(seconds)
}, 1000)
return () => clearInterval(interval)
}, [])
/* const { hours, minutes, seconds } = getCountdown(
auctionView.auction.info.endedAt?.toNumber(),
);
setHours(hours);
setMinutes(minutes);
setSeconds(seconds);*/
setHours(1);
}, 1000);
return () => clearInterval(interval);
}, [clock]);
return (
<div className="presale-card-container">
<div className="info-header">STARTING BID</div>
<div style={{ fontWeight: 700, fontSize: '1.6rem' }}>40.00</div>
<br/>
<br />
<div className="info-header">AUCTION ENDS IN</div>
<Row style={{ width: 300 }}>
<Col span={8}>
@ -49,13 +56,17 @@ export const AuctionCard = ({ auction }: { auction: Auction }) => {
<div className="cd-label">seconds</div>
</Col>
</Row>
<br/>
<div className="info-content" style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '0.9rem' }}>
Any bids placed in the last 15 minutes will extend the auction for another 15 minutes.
<br />
<div
className="info-content"
style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '0.9rem' }}
>
Any bids placed in the last 15 minutes will extend the auction for
another 15 minutes.
</div>
<br />
<div className="info-line"/>
<div className="info-line" />
<InputNumber
autoFocus
@ -70,29 +81,36 @@ export const AuctionCard = ({ auction }: { auction: Auction }) => {
// }
/>
<div className="info-content" style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '0.9rem' }}>
<div
className="info-content"
style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '0.9rem' }}
>
Your Balance: {0.0} (${0.0})
</div>
<Button
type="primary"
size="large"
className="action-btn"
style={{ marginTop: 20 }}
>
PLACE BID
type="primary"
size="large"
className="action-btn"
style={{ marginTop: 20 }}
>
PLACE BID
</Button>
</div>
)
}
);
};
export const AuctionBidders = (auctionID: string) => {
const bids: any[] = [];
return <Col>
{bids.map((bid, index) => {
return <Row>{index+1}. {shortenAddress(bid.address)} {bid.amount}</Row>
})}
</Col>
return (
<Col>
{bids.map((bid, index) => {
return (
<Row>
{index + 1}. {shortenAddress(bid.address)} {bid.amount}
</Row>
);
})}
</Col>
);
};

View File

@ -4,6 +4,7 @@ import {
useConnection,
decodeMetadata,
decodeNameSymbolTuple,
decodeAuction,
decodeEdition,
decodeMasterEdition,
Metadata,
@ -15,30 +16,55 @@ import {
Edition,
MasterEdition,
NameSymbolTuple,
AuctionData,
SafetyDepositBox,
VaultKey,
decodeSafetyDeposit,
} from '@oyster/common';
import { MintInfo } from '@solana/spl-token';
import { Connection, PublicKey, PublicKeyAndAccount } from '@solana/web3.js';
import BN from 'bn.js';
import React, { useContext, useEffect, useState } from 'react';
import {
AuctionManager,
AuctionManagerStatus,
decodeAuctionManager,
getAuctionManagerKey,
MetaplexKey,
} from '../models/metaplex';
const { MetadataKey } = actions;
export interface MetaContextState {
metadata: ParsedAccount<Metadata>[];
metadataByMint: Record<string, ParsedAccount<Metadata>>;
nameSymbolTuples: Record<string, ParsedAccount<NameSymbolTuple>>;
editions: Record<string, ParsedAccount<Edition>>;
masterEditions: Record<string, ParsedAccount<MasterEdition>>;
auctionManagers: Record<string, ParsedAccount<AuctionManager>>;
auctions: Record<string, ParsedAccount<AuctionData>>;
safetyDepositBoxesByVaultAndIndex: Record<
string,
ParsedAccount<SafetyDepositBox>
>;
}
const MetaContext = React.createContext<MetaContextState>({
metadata: [],
metadataByMint: {},
nameSymbolTuples: {},
masterEditions: {},
editions: {},
auctionManagers: {},
auctions: {},
safetyDepositBoxesByVaultAndIndex: {},
});
export function MetaProvider({ children = null as any }) {
const connection = useConnection();
const [metadata, setMetadata] = useState<ParsedAccount<Metadata>[]>([]);
const [metadataByMint, setMetadataByMint] = useState<
Record<string, ParsedAccount<Metadata>>
>({});
const [nameSymbolTuples, setNameSymbolTuples] = useState<
Record<string, ParsedAccount<NameSymbolTuple>>
>({});
@ -48,12 +74,181 @@ export function MetaProvider({ children = null as any }) {
const [editions, setEditions] = useState<
Record<string, ParsedAccount<Edition>>
>({});
const [auctionManagers, setAuctionManagers] = useState<
Record<string, ParsedAccount<AuctionManager>>
>({});
const [auctions, setAuctions] = useState<
Record<string, ParsedAccount<AuctionData>>
>({});
const [
safetyDepositBoxesByVaultAndIndex,
setSafetyDepositBoxesByVaultAndIndex,
] = useState<Record<string, ParsedAccount<SafetyDepositBox>>>({});
useEffect(() => {
let dispose = () => {};
(async () => {
const mintToMetadata = new Map<string, ParsedAccount<Metadata>>();
const processAuctions = async (a: PublicKeyAndAccount<Buffer>) => {
try {
const auction = await decodeAuction(a.account.data);
auction.auctionManagerKey = await getAuctionManagerKey(
auction.resource,
a.pubkey,
);
const account: ParsedAccount<AuctionData> = {
pubkey: a.pubkey,
account: a.account,
info: auction,
};
setAuctions(e => ({
...e,
[a.pubkey.toBase58()]: account,
}));
} catch {
// ignore errors
// add type as first byte for easier deserialization
}
};
const accounts = await connection.getProgramAccounts(
programIds().auction,
);
for (let i = 0; i < accounts.length; i++) {
await processAuctions(accounts[i]);
}
let subId = connection.onProgramAccountChange(
programIds().auction,
async info => {
const pubkey =
typeof info.accountId === 'string'
? new PublicKey((info.accountId as unknown) as string)
: info.accountId;
await processAuctions({
pubkey,
account: info.accountInfo,
});
},
);
dispose = () => {
connection.removeProgramAccountChangeListener(subId);
};
})();
return () => {
dispose();
};
}, [connection, setAuctions]);
useEffect(() => {
let dispose = () => {};
(async () => {
const processSafetyDeposits = async (a: PublicKeyAndAccount<Buffer>) => {
try {
if (a.account.data[0] == VaultKey.SafetyDepositBoxV1) {
const safetyDeposit = await decodeSafetyDeposit(a.account.data);
const account: ParsedAccount<SafetyDepositBox> = {
pubkey: a.pubkey,
account: a.account,
info: safetyDeposit,
};
setSafetyDepositBoxesByVaultAndIndex(e => ({
...e,
[safetyDeposit.vault.toBase58() +
'-' +
safetyDeposit.order]: account,
}));
}
} catch {
// ignore errors
// add type as first byte for easier deserialization
}
};
const accounts = await connection.getProgramAccounts(programIds().vault);
for (let i = 0; i < accounts.length; i++) {
await processSafetyDeposits(accounts[i]);
}
let subId = connection.onProgramAccountChange(
programIds().vault,
async info => {
const pubkey =
typeof info.accountId === 'string'
? new PublicKey((info.accountId as unknown) as string)
: info.accountId;
await processSafetyDeposits({
pubkey,
account: info.accountInfo,
});
},
);
dispose = () => {
connection.removeProgramAccountChangeListener(subId);
};
})();
return () => {
dispose();
};
}, [connection, setSafetyDepositBoxesByVaultAndIndex]);
useEffect(() => {
let dispose = () => {};
(async () => {
const processAuctionManagers = async (a: PublicKeyAndAccount<Buffer>) => {
try {
if (a.account.data[0] == MetaplexKey.AuctionManagerV1) {
const auctionManager = await decodeAuctionManager(a.account.data);
const account: ParsedAccount<AuctionManager> = {
pubkey: a.pubkey,
account: a.account,
info: auctionManager,
};
setAuctionManagers(e => ({
...e,
[a.pubkey.toBase58()]: account,
}));
}
} catch {
// ignore errors
// add type as first byte for easier deserialization
}
};
const accounts = await connection.getProgramAccounts(
programIds().metaplex,
);
for (let i = 0; i < accounts.length; i++) {
await processAuctionManagers(accounts[i]);
}
let subId = connection.onProgramAccountChange(
programIds().metaplex,
async info => {
const pubkey =
typeof info.accountId === 'string'
? new PublicKey((info.accountId as unknown) as string)
: info.accountId;
await processAuctionManagers({
pubkey,
account: info.accountInfo,
});
},
);
dispose = () => {
connection.removeProgramAccountChangeListener(subId);
};
})();
return () => {
dispose();
};
}, [connection, setAuctionManagers]);
useEffect(() => {
let dispose = () => {};
(async () => {
const processMetaData = async (meta: PublicKeyAndAccount<Buffer>) => {
try {
if (meta.account.data[0] == MetadataKey.MetadataV1) {
@ -67,7 +262,10 @@ export function MetaProvider({ children = null as any }) {
account: meta.account,
info: metadata,
};
mintToMetadata.set(metadata.mint.toBase58(), account);
setMetadataByMint(e => ({
...e,
[metadata.mint.toBase58()]: account,
}));
}
} else if (meta.account.data[0] == MetadataKey.EditionV1) {
const edition = decodeEdition(meta.account.data);
@ -113,20 +311,21 @@ export function MetaProvider({ children = null as any }) {
await processMetaData(accounts[i]);
}
await queryExtendedMetadata(connection, setMetadata, mintToMetadata);
await queryExtendedMetadata(connection, setMetadata, metadataByMint);
let subId = connection.onProgramAccountChange(
programIds().metadata,
async info => {
const pubkey = typeof info.accountId === 'string' ?
new PublicKey((info.accountId as unknown) as string) :
info.accountId;
const pubkey =
typeof info.accountId === 'string'
? new PublicKey((info.accountId as unknown) as string)
: info.accountId;
await processMetaData({
pubkey,
account: info.accountInfo,
});
queryExtendedMetadata(connection, setMetadata, mintToMetadata);
queryExtendedMetadata(connection, setMetadata, metadataByMint);
},
);
dispose = () => {
@ -147,7 +346,16 @@ export function MetaProvider({ children = null as any }) {
return (
<MetaContext.Provider
value={{ metadata, editions, masterEditions, nameSymbolTuples }}
value={{
metadata,
editions,
masterEditions,
nameSymbolTuples,
auctionManagers,
auctions,
metadataByMint,
safetyDepositBoxesByVaultAndIndex,
}}
>
{children}
</MetaContext.Provider>
@ -157,14 +365,14 @@ export function MetaProvider({ children = null as any }) {
const queryExtendedMetadata = async (
connection: Connection,
setMetadata: (metadata: ParsedAccount<Metadata>[]) => void,
mintToMeta: Map<string, ParsedAccount<Metadata>>,
mintToMeta: Record<string, ParsedAccount<Metadata>>,
) => {
const mintToMetadata = new Map<string, ParsedAccount<Metadata>>(mintToMeta);
const mintToMetadata = { ...mintToMeta };
const extendedMetadataFetch = new Map<string, Promise<any>>();
const mints = await getMultipleAccounts(
connection,
[...mintToMetadata.keys()].filter(k => !cache.get(k)),
[...Object.keys(mintToMetadata)].filter(k => !cache.get(k)),
'single',
);
mints.keys.forEach((key, index) => {
@ -176,9 +384,9 @@ const queryExtendedMetadata = async (
) as ParsedAccount<MintInfo>;
if (mint.info.supply.gt(new BN(1)) || mint.info.decimals !== 0) {
// naive not NFT check
mintToMetadata.delete(key);
delete mintToMetadata[key];
} else {
const metadata = mintToMetadata.get(key);
const metadata = mintToMetadata[key];
if (metadata && metadata.info.uri) {
extendedMetadataFetch.set(
key,
@ -190,19 +398,19 @@ const queryExtendedMetadata = async (
!metadata.info.extended ||
metadata.info.extended?.files?.length === 0
) {
mintToMetadata.delete(key);
delete mintToMetadata[key];
} else {
if (metadata.info.extended?.image) {
metadata.info.extended.image = `${metadata.info.uri}/${metadata.info.extended.image}`;
}
}
} catch {
mintToMetadata.delete(key);
delete mintToMetadata[key];
return undefined;
}
})
.catch(() => {
mintToMetadata.delete(key);
delete mintToMetadata[key];
return undefined;
}),
);
@ -212,7 +420,7 @@ const queryExtendedMetadata = async (
await Promise.all([...extendedMetadataFetch.values()]);
setMetadata([...mintToMetadata.values()]);
setMetadata([...Object.values(mintToMetadata)]);
};
export const useMeta = () => {

View File

@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { PublicKey } from '@solana/web3.js';
import { useMeta } from './../contexts';
import { useMeta } from '../contexts';
import { Art } from '../types';
export const useArt = (id: PublicKey | string) => {

View File

@ -0,0 +1,45 @@
import {
ParsedAccount,
Metadata,
SafetyDepositBox,
AuctionData,
useConnection,
AuctionState,
} from '@oyster/common';
import { useEffect, useState } from 'react';
import { AuctionView, processAccountsIntoAuctionView } from '.';
import { useMeta } from '../contexts';
import { AuctionManager } from '../models/metaplex';
import { sampleAuction } from '../views/home/sampleData';
export const useAuction = (id: string) => {
const connection = useConnection();
const [clock, setClock] = useState<number>(0);
const [auctionView, setAuctionView] = useState<AuctionView | null>(null);
useEffect(() => {
connection.getSlot().then(setClock);
}, [connection]);
const {
auctions,
auctionManagers,
safetyDepositBoxesByVaultAndIndex,
metadataByMint,
} = useMeta();
useEffect(() => {
const auction = auctions[id];
if (auction) {
const auctionView = processAccountsIntoAuctionView(
auction,
auctionManagers,
safetyDepositBoxesByVaultAndIndex,
metadataByMint,
clock,
undefined,
);
if (auctionView) setAuctionView(auctionView);
}
}, [clock]);
return auctionView;
};

View File

@ -1,9 +0,0 @@
import React, { useMemo } from 'react';
import { sampleAuction } from '../views/home/sampleData';
import { useMeta } from './../contexts';
export const useAuction = (id: string) => {
const { metadata } = useMeta();
return sampleAuction;
}

View File

@ -0,0 +1,155 @@
import {
ParsedAccount,
Metadata,
SafetyDepositBox,
AuctionData,
useConnection,
AuctionState,
} from '@oyster/common';
import { useEffect, useState } from 'react';
import { useMeta } from '../contexts';
import { AuctionManager } from '../models/metaplex';
export enum AuctionViewState {
Live = '0',
Upcoming = '1',
Ended = '2',
BuyNow = '3',
}
export interface AuctionViewItem {
metadata: ParsedAccount<Metadata>;
safetyDeposit: ParsedAccount<SafetyDepositBox>;
}
// Flattened surface item for easy display
export interface AuctionView {
items: AuctionViewItem[];
auction: ParsedAccount<AuctionData>;
auctionManager: ParsedAccount<AuctionManager>;
openEditionItem?: AuctionViewItem;
state: AuctionViewState;
thumbnail: AuctionViewItem;
}
export const useAuctions = (state: AuctionViewState) => {
const connection = useConnection();
const [clock, setClock] = useState<number>(0);
const [auctionViews, setAuctionViews] = useState<AuctionView[]>([]);
useEffect(() => {
connection.getSlot().then(setClock);
}, [connection]);
const {
auctions,
auctionManagers,
safetyDepositBoxesByVaultAndIndex,
metadataByMint,
} = useMeta();
useEffect(() => {
const newAuctionViews: AuctionView[] = [];
Object.keys(auctions).forEach(a => {
const auction = auctions[a];
const auctionView = processAccountsIntoAuctionView(
auction,
auctionManagers,
safetyDepositBoxesByVaultAndIndex,
metadataByMint,
clock,
state,
);
if (auctionView) newAuctionViews.push(auctionView);
});
setAuctionViews(newAuctionViews);
}, [clock, state, auctions]);
return auctionViews;
};
export function processAccountsIntoAuctionView(
auction: ParsedAccount<AuctionData>,
auctionManagers: Record<string, ParsedAccount<AuctionManager>>,
safetyDepositBoxesByVaultAndIndex: Record<
string,
ParsedAccount<SafetyDepositBox>
>,
metadataByMint: Record<string, ParsedAccount<Metadata>>,
clock: number,
desiredState: AuctionViewState | undefined,
) {
let state: AuctionViewState;
if (
auction.info.state == AuctionState.Ended ||
(auction.info.endedAt && auction.info.endedAt.toNumber() <= clock)
) {
state = AuctionViewState.Ended;
} else if (
auction.info.state == AuctionState.Started ||
(auction.info.endedAt && auction.info.endedAt.toNumber() > clock)
) {
state = AuctionViewState.Live;
} else if (auction.info.state == AuctionState.Created) {
state = AuctionViewState.Upcoming;
} else {
state = AuctionViewState.BuyNow;
}
if (desiredState && desiredState != state) return null;
const auctionManager =
auctionManagers[auction.info.auctionManagerKey?.toBase58() || ''];
if (auctionManager) {
let boxes: ParsedAccount<SafetyDepositBox>[] = [];
let box =
safetyDepositBoxesByVaultAndIndex[
auctionManager.info.vault.toBase58() + '-0'
];
if (box) {
boxes.push(box);
let i = 1;
while (box) {
box =
safetyDepositBoxesByVaultAndIndex[
auctionManager.info.vault.toBase58() + '-' + i.toString()
];
if (box) boxes.push(box);
i++;
}
}
if (boxes.length > 0) {
let view: any = {
auction,
auctionManager,
state,
items: auctionManager.info.settings.winningConfigs.map(w => ({
metadata:
metadataByMint[
boxes[w.safetyDepositBoxIndex].info.tokenMint.toBase58()
],
safetyDeposit: boxes[w.safetyDepositBoxIndex],
})),
openEditionItem:
auctionManager.info.settings.openEditionConfig != null
? {
metadata:
metadataByMint[
boxes[
auctionManager.info.settings.openEditionConfig
].info.tokenMint.toBase58()
],
safetyDeposit:
boxes[auctionManager.info.settings.openEditionConfig],
}
: undefined,
};
view.thumbnail = view.items[0] || view.openEditionItem;
if (!view.thumbnail || !view.thumbnail.metadata) return null;
return view;
}
}
return null;
}

View File

@ -1,15 +0,0 @@
import React, { useMemo } from 'react';
import { useMeta } from './../contexts';
export enum AuctionState {
Live = '0',
Upcoming = '1',
Ended = '2',
BuyNow = '3',
}
export const useAuctions = (state: AuctionState) => {
const { metadata } = useMeta();
return metadata;
}

View File

@ -140,6 +140,9 @@ export class WinningConfig {
Object.assign(this, args);
}
}
export const decodeAuctionManager = (buffer: Buffer) => {
return deserializeBorsh(SCHEMA, AuctionManager, buffer) as AuctionManager;
};
export class WinningConfigState {
amountMinted: number = 0;
@ -313,6 +316,20 @@ export const SCHEMA = new Map<any, any>([
],
]);
export async function getAuctionManagerKey(
vault: PublicKey,
auctionKey: PublicKey,
): Promise<PublicKey> {
const PROGRAM_IDS = programIds();
return (
await PublicKey.findProgramAddress(
[Buffer.from(METAPLEX_PREFIX), auctionKey.toBuffer()],
PROGRAM_IDS.metaplex,
)
)[0];
}
export async function getAuctionKeys(
vault: PublicKey,
): Promise<{ auctionKey: PublicKey; auctionManagerKey: PublicKey }> {
@ -329,12 +346,7 @@ export async function getAuctionKeys(
)
)[0];
const auctionManagerKey: PublicKey = (
await PublicKey.findProgramAddress(
[Buffer.from(METAPLEX_PREFIX), auctionKey.toBuffer()],
PROGRAM_IDS.metaplex,
)
)[0];
const auctionManagerKey = await getAuctionManagerKey(vault, auctionKey);
return { auctionKey, auctionManagerKey };
}

View File

@ -1,32 +1,40 @@
import React from 'react';
import { useParams } from 'react-router-dom';
import { Row, Col, Divider, Layout, Image } from 'antd';
import { Row, Col, Divider, Layout, Image, Spin } from 'antd';
import { AuctionCard } from '../../components/AuctionCard';
import { useArt, useAuction } from '../../hooks';
import { ArtContent } from '../../components/ArtContent';
import { sampleArtist } from '../home/sampleData';
const { Content } = Layout
const { Content } = Layout;
export const AuctionView = () => {
const { id } = useParams<{ id: string }>();
const auction = useAuction(id);
const art = useArt(id);
const artist = sampleArtist
const artist = sampleArtist;
return (
<Content>
<Col>
<Row>
<ArtContent category={art.category} content={art.image} className="artwork-image" />
<ArtContent
category={art.category}
content={art.image}
className="artwork-image"
/>
</Row>
<Divider />
<Row style={{ margin: '0 30px', textAlign: 'left', fontSize: '1.4rem' }}>
<Col span={12} >
<Row
style={{ margin: '0 30px', textAlign: 'left', fontSize: '1.4rem' }}
>
<Col span={12}>
<div style={{ fontWeight: 700 }}>{art.title}</div>
<br />
<div className="info-header">CREATED BY</div>
<div className="info-content"><img src={artist.image} className="artist-image" /> @{art.artist}</div>
<div className="info-content">
<img src={artist.image} className="artist-image" /> @{art.artist}
</div>
<br />
<div className="info-header">CREATOR ROYALTIES</div>
<div className="royalties">{art.royalties}%</div>
@ -38,7 +46,7 @@ export const AuctionView = () => {
<div className="info-content">{artist.about}</div>
</Col>
<Col span={12}>
<AuctionCard auction={auction} />
{auction ? <AuctionCard auctionView={auction} /> : <Spin />}
</Col>
</Row>
</Col>

View File

@ -126,7 +126,7 @@ export const AuctionCreateView = () => {
const [attributes, setAttributes] = useState<AuctionState>({
reservationPrice: 0,
items: [],
category: AuctionCategory.Open,
category: AuctionCategory.Single,
saleType: 'auction',
});
@ -157,6 +157,7 @@ export const AuctionCreateView = () => {
usize: new BN(0),
});
} else if (attributes.category == AuctionCategory.Single) {
console.log('Using single');
settings = new AuctionManagerSettings({
openEditionWinnerConstraint: WinningConstraint.NoOpenEdition,
openEditionNonWinningConstraint: NonWinningConstraint.NoOpenEdition,

View File

@ -1,52 +1,58 @@
import React, { useState } from 'react'
import { Layout, Row, Col, Tabs } from 'antd'
import Masonry from 'react-masonry-css'
import React, { useState } from 'react';
import { Layout, Row, Col, Tabs } from 'antd';
import Masonry from 'react-masonry-css';
import { PreSaleBanner } from '../../components/PreSaleBanner'
import { AuctionState, useAuctions } from '../../hooks'
import { PreSaleBanner } from '../../components/PreSaleBanner';
import { AuctionViewState, useAuctions } from '../../hooks';
import './index.less'
import { ArtCard } from '../../components/ArtCard'
import { Link } from 'react-router-dom'
import './index.less';
import { ArtCard } from '../../components/ArtCard';
import { Link } from 'react-router-dom';
const { TabPane } = Tabs;
const { Content } = Layout
const { Content } = Layout;
export const HomeView = () => {
const [activeKey, setActiveKey] = useState(AuctionState.Live);
const auctions = useAuctions(AuctionState.Live)
const [activeKey, setActiveKey] = useState(AuctionViewState.Live);
const auctions = useAuctions(activeKey);
console.log('Auctions', auctions);
const breakpointColumnsObj = {
default: 4,
1100: 3,
700: 2,
500: 1
}
500: 1,
};
const auctionGrid = <Masonry
const auctionGrid = (
<Masonry
breakpointCols={breakpointColumnsObj}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{auctions.map(m => {
const id = m.pubkey.toBase58();
return <Link to={`/art/${id}`}>
<ArtCard key={id}
image={m.info.extended?.image}
category={m.info.extended?.category}
name={m.info?.name}
symbol={m.info.symbol}
description={m.info.extended?.description}
preview={false} />
</Link>
})}
</Masonry>;
{auctions.map(m => {
const id = m.auction.pubkey.toBase58();
return (
<Link to={`/art/${id}`}>
<ArtCard
key={id}
image={m.thumbnail.metadata.info.extended?.image}
category={m.thumbnail.metadata.info.extended?.category}
name={m.thumbnail.metadata?.info.name}
symbol={m.thumbnail.metadata?.info.symbol}
description={m.thumbnail.metadata?.info.extended?.description}
preview={false}
/>
</Link>
);
})}
</Masonry>
);
return (
<Layout style={{ margin: 0, marginTop: 30 }}>
<PreSaleBanner
artistName={"RAC"}
productName={"THE BOY COLLECTION"}
artistName={'RAC'}
productName={'THE BOY COLLECTION'}
preSaleTS={1618690343000}
image="img/banner1.jpeg"
/>
@ -54,28 +60,31 @@ export const HomeView = () => {
<Content style={{ display: 'flex', flexWrap: 'wrap' }}>
<Col style={{ width: '100%', marginTop: 10 }}>
<Row>
<Tabs activeKey={activeKey} onTabClick={(key) => setActiveKey(key as AuctionState)}>
<Tabs
activeKey={activeKey}
onTabClick={key => setActiveKey(key as AuctionViewState)}
>
<TabPane
tab={<span className="tab-title">Live</span>}
key={AuctionState.Live}
key={AuctionViewState.Live}
>
{auctionGrid}
</TabPane>
<TabPane
tab={<span className="tab-title">Upcoming</span>}
key={AuctionState.Upcoming}
key={AuctionViewState.Upcoming}
>
{auctionGrid}
</TabPane>
<TabPane
tab={<span className="tab-title">Ended</span>}
key={AuctionState.Ended}
key={AuctionViewState.Ended}
>
{auctionGrid}
</TabPane>
<TabPane
tab={<span className="tab-title">Buy Now</span>}
key={AuctionState.BuyNow}
key={AuctionViewState.BuyNow}
>
{auctionGrid}
</TabPane>