Merge branch 'feature/m' into main

This commit is contained in:
bartosz-lipinski 2021-04-21 18:45:27 -05:00
commit e316021029
35 changed files with 1215 additions and 376 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
REACT_APP_CLIENT_ID=BKBTX-SmaEFGddZQrwqd65YFoImRQLca_Tj2IdmKyD2UbDpzrtN2WQ-NYLuej6gP0DfF3jSpEkI13wPt1uPedm0

19
assets/wallets/torus.svg Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="226.000000pt" height="248.000000pt" viewBox="0 0 226.000000 248.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.16, written by Peter Selinger 2001-2019
</metadata>
<g transform="translate(0.000000,248.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M482 2143 c-43 -21 -79 -64 -92 -111 -13 -48 -13 -246 0 -294 29
-103 92 -128 322 -128 l158 0 0 -588 0 -589 23 -43 c16 -30 37 -51 67 -67 39
-21 56 -23 182 -23 94 0 149 4 171 14 43 17 95 78 102 118 3 18 5 389 3 825
l-3 791 -31 39 c-59 74 -52 73 -484 73 -334 0 -389 -3 -418 -17z"/>
<path d="M1743 2144 c-57 -21 -126 -84 -154 -141 -35 -73 -32 -171 6 -241 54
-100 132 -146 245 -146 115 0 197 50 246 151 108 220 -110 459 -343 377z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 957 B

View File

@ -21,11 +21,14 @@ export const CurrentUserBadge = (props: { showBalance?: boolean, showAddress?: b
{
marginLeft: '0.5rem',
display: 'flex',
width: props.iconSize
width: props.iconSize,
borderRadius: 50,
} :{
display: 'flex',
width: props.iconSize,
paddingLeft: 0,
borderRadius: 50,
};
const baseWalletKey: React.CSSProperties = { height: props.iconSize, cursor: 'pointer', userSelect: 'none' };
@ -33,6 +36,21 @@ export const CurrentUserBadge = (props: { showBalance?: boolean, showAddress?: b
baseWalletKey
:{ ...baseWalletKey, paddingLeft: 0 };
let name = props.showAddress ? shortenAddress(`${wallet.publicKey}`) : '';
const unknownWallet = wallet as any;
if(unknownWallet.name) {
name = unknownWallet.name;
}
let image = <Identicon
address={wallet.publicKey?.toBase58()}
style={iconStyle}
/>;
if(unknownWallet.image) {
image = <img src={unknownWallet.image} style={iconStyle} />;
}
return (
<div className="wallet-wrapper">
{props.showBalance && <span>
@ -46,11 +64,8 @@ export const CurrentUserBadge = (props: { showBalance?: boolean, showAddress?: b
trigger="click"
>
<div className="wallet-key" style={walletKeyStyle}>
{props.showAddress && shortenAddress(`${wallet.publicKey}`)}
<Identicon
address={wallet.publicKey?.toBase58()}
style={iconStyle}
/>
{name && (<span style={{ marginRight: '0.5rem' }}>{name}</span>)}
{image}
</div>
</Popover>
</div>

View File

@ -21,16 +21,16 @@ export const Identicon = (props: {
if (address && ref.current) {
try {
ref.current.innerHTML = '';
ref.current.className = className || '';
ref.current.appendChild(
Jazzicon(
style?.width || 16,
parseInt(bs58.decode(address).toString('hex').slice(5, 15), 16),
),
);
ref.current.innerHTML = '';
ref.current.className = className || '';
ref.current.appendChild(
Jazzicon(
style?.width || 16,
parseInt(bs58.decode(address).toString('hex').slice(5, 15), 16),
),
);
}catch (err) {
} catch (err) {
// TODO
}
}

View File

@ -2,13 +2,17 @@ import React from 'react';
import { Button, Select } from 'antd';
import { useWallet } from '../../contexts/wallet';
import { ENDPOINTS, useConnectionConfig } from '../../contexts/connection';
import { shortenAddress } from '../../utils';
import {
CopyOutlined
} from '@ant-design/icons';
export const Settings = ({
additionalSettings,
}: {
additionalSettings?: JSX.Element;
}) => {
const { connected, disconnect } = useWallet();
const { connected, disconnect, select, wallet } = useWallet();
const { endpoint, setEndpoint } = useConnectionConfig();
return (
@ -27,9 +31,23 @@ export const Settings = ({
))}
</Select>
{connected && (
<Button type="primary" onClick={disconnect}>
Disconnect
</Button>
<>
<span>Wallet:</span>
{wallet?.publicKey && (<Button
style={{ marginBottom: 5 }} onClick={() => navigator.clipboard.writeText(wallet.publicKey?.toBase58() || '')}>
<CopyOutlined />
{shortenAddress(wallet?.publicKey.toBase58())}
</Button>)}
<Button onClick={select}
style={{ marginBottom: 5 }}>
Change
</Button>
<Button type="primary" onClick={disconnect}
style={{ marginBottom: 5 }}>
Disconnect
</Button>
</>
)}
{additionalSettings}
</div>

View File

@ -416,35 +416,35 @@ export function AccountsProvider({ children = null as any }) {
if (!connection || !publicKey) {
setTokenAccounts([]);
} else {
precacheUserTokenAccounts(connection, LEND_HOST_FEE_ADDRESS);
// precacheUserTokenAccounts(connection, LEND_HOST_FEE_ADDRESS);
precacheUserTokenAccounts(connection, publicKey).then(() => {
setTokenAccounts(selectUserAccounts());
});
// precacheUserTokenAccounts(connection, publicKey).then(() => {
// setTokenAccounts(selectUserAccounts());
// });
// This can return different types of accounts: token-account, mint, multisig
// TODO: web3.js expose ability to filter.
// this should use only filter syntax to only get accounts that are owned by user
const tokenSubID = connection.onProgramAccountChange(
programIds().token,
info => {
// TODO: fix type in web3.js
const id = (info.accountId as unknown) as string;
// TODO: do we need a better way to identify layout (maybe a enum identifing type?)
if (info.accountInfo.data.length === AccountLayout.span) {
const data = deserializeAccount(info.accountInfo.data);
// const tokenSubID = connection.onProgramAccountChange(
// programIds().token,
// info => {
// // TODO: fix type in web3.js
// const id = (info.accountId as unknown) as string;
// // TODO: do we need a better way to identify layout (maybe a enum identifing type?)
// if (info.accountInfo.data.length === AccountLayout.span) {
// const data = deserializeAccount(info.accountInfo.data);
if (PRECACHED_OWNERS.has(data.owner.toBase58())) {
cache.add(id, info.accountInfo, TokenAccountParser);
setTokenAccounts(selectUserAccounts());
}
}
},
'singleGossip',
);
// if (PRECACHED_OWNERS.has(data.owner.toBase58())) {
// cache.add(id, info.accountInfo, TokenAccountParser);
// setTokenAccounts(selectUserAccounts());
// }
// }
// },
// 'singleGossip',
// );
return () => {
connection.removeProgramAccountChangeListener(tokenSubID);
// connection.removeProgramAccountChangeListener(tokenSubID);
};
}
}, [connection, connected, publicKey, selectUserAccounts]);

View File

@ -9,6 +9,8 @@ import { useLocalStorageState } from "../utils/utils";
import { LedgerProvider } from "@solana/wallet-ledger";
import { SolongWalletAdapter } from "../wallet-adapters/solong";
import { PhantomWalletAdapter } from "../wallet-adapters/phantom";
import { TorusWalletAdapter } from "../wallet-adapters/torus";
import { useLocation } from "react-router";
const ASSETS_URL = 'https://raw.githubusercontent.com/solana-labs/oyster/main/assets/wallets/';
export const WALLET_PROVIDERS = [
@ -37,6 +39,12 @@ export const WALLET_PROVIDERS = [
icon: `https://www.phantom.app/img/logo.png`,
adapter: PhantomWalletAdapter,
},
{
name: 'Torus',
url: 'https://tor.us',
icon: `${ASSETS_URL}torus.svg`,
adapter: TorusWalletAdapter,
}
];
const WalletContext = React.createContext<{
@ -53,8 +61,8 @@ const WalletContext = React.createContext<{
export function WalletProvider({ children = null as any }) {
const { endpoint } = useConnectionConfig();
const [autoConnect, setAutoConnect] = useState(false);
const location = useLocation();
const [autoConnect, setAutoConnect] = useState(location.pathname.indexOf('result=') >= 0 || false);
const [providerUrl, setProviderUrl] = useLocalStorageState("walletProvider");
const provider = useMemo(() => WALLET_PROVIDERS.find(({ url }) => url === providerUrl), [providerUrl]);
@ -90,6 +98,7 @@ export function WalletProvider({ children = null as any }) {
wallet.on("disconnect", () => {
setConnected(false);
// setProviderUrl(null)
notify({
message: "Wallet update",
description: "Disconnected from wallet",
@ -99,6 +108,7 @@ export function WalletProvider({ children = null as any }) {
return () => {
setConnected(false);
// setProviderUrl(null)
if (wallet) {
wallet.disconnect();
}

View File

@ -0,0 +1,91 @@
import EventEmitter from "eventemitter3"
import { Account, PublicKey, Transaction } from "@solana/web3.js"
import { WalletAdapter } from "@solana/wallet-base"
import OpenLogin from "@toruslabs/openlogin"
import { getED25519Key } from "@toruslabs/openlogin-ed25519"
const getSolanaPrivateKey = (openloginKey: string)=>{
const { sk } = getED25519Key(openloginKey)
return sk
}
export class TorusWalletAdapter extends EventEmitter implements WalletAdapter {
_provider: OpenLogin | undefined;
endpoint: string;
providerUrl: string;
account: Account | undefined;
image: string = '';
name: string = '';
constructor(providerUrl: string, endpoint: string) {
super()
this.connect = this.connect.bind(this)
this.endpoint = endpoint;
this.providerUrl = providerUrl;
}
async signAllTransactions(transactions: Transaction[]): Promise<Transaction[]> {
if(this.account) {
let account = this.account;
transactions.forEach(t => t.partialSign(account));
}
return transactions
}
get publicKey() {
return this.account?.publicKey || null;
}
async signTransaction(transaction: Transaction) {
if(this.account) {
transaction.partialSign(this.account)
}
return transaction
}
connect = async () => {
this._provider = new OpenLogin({
clientId: process.env.REACT_APP_CLIENT_ID || 'BKBTX-SmaEFGddZQrwqd65YFoImRQLca_Tj2IdmKyD2UbDpzrtN2WQ-NYLuej6gP0DfF3jSpEkI13wPt1uPedm0',
network: "mainnet", // mainnet, testnet, development
});
try {
await this._provider.init();
} catch (ex) {
console.error('init failed', ex)
}
console.error(this._provider?.state.store);
if (this._provider.privKey) {
const privateKey = this._provider.privKey;
const secretKey = getSolanaPrivateKey(privateKey);
this.account = new Account(secretKey);
} else {
try {
const { privKey } = await this._provider.login();
const secretKey = getSolanaPrivateKey(privKey);
this.account = new Account(secretKey);
} catch(ex) {
console.error('login failed', ex);
}
}
this.name = this._provider?.state.store.get('name');;
this.image = this._provider?.state.store.get('profileImage');;
this.emit("connect");
}
disconnect = async () => {
console.log("Disconnecting...")
if (this._provider) {
await this._provider.logout();
await this._provider._cleanup();
this._provider = undefined;
this.emit("disconnect");
}
}
}

View File

@ -18,6 +18,9 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@toruslabs/openlogin": "^0.7.0",
"@toruslabs/openlogin-ed25519": "^0.7.0",
"@toruslabs/openlogin-utils": "^0.7.0",
"@toruslabs/torus-embed": "^1.9.10",
"@types/animejs": "^3.1.3",
"@types/chart.js": "^2.9.29",

View File

@ -0,0 +1,3 @@
export const createAuction = () => {
// TODO:
};

View File

@ -1 +1,3 @@
export const nop = () => {};
export * from './nft';
export * from './vault';
export * from './auction';

View File

@ -0,0 +1,3 @@
export const createVault = () => {
// TODO:
};

View File

@ -10,7 +10,7 @@ const UserActions = () => {
<Link to={`/art/create`}>
<Button className="app-btn">Create</Button>
</Link>
<Link to={`/auction/create`}>
<Link to={`/auction/create/0`}>
<Button type="primary">Sell</Button>
</Link>
</>;

View File

@ -1,21 +1,12 @@
import React, { useLayoutEffect, useState } from 'react';
import { Card, Avatar } from 'antd';
import { Card, Avatar, CardProps } from 'antd';
import { MetadataCategory } from '@oyster/common';
import { ArtContent } from './../ArtContent';
import './index.less';
const { Meta } = Card;
export const ArtCard = ({
image,
category,
name,
symbol,
description,
artist,
preview,
small,
}: {
export interface ArtCardProps extends CardProps {
image?: string;
category?: MetadataCategory
name?: string;
@ -23,13 +14,17 @@ export const ArtCard = ({
description?: string;
artist?: string;
preview?: boolean;
small?: boolean
}) => {
small?: boolean;
}
export const ArtCard = (props: ArtCardProps) => {
const { className, small, category, image, name, preview, artist, ...rest } = props;
return (
<Card
hoverable={true}
className={`art-card ${small ? 'small' : ''}`}
cover={<ArtContent category={category} content={image} />}
className={`art-card ${small ? 'small' : ''} ${className}`}
cover={<ArtContent category={category} content={image} preview={preview} />}
{...rest}
>
<Meta
title={`${name}`}

View File

@ -2,11 +2,27 @@ import React, { useMemo } from 'react';
import { Image } from 'antd';
import { MetadataCategory } from '@oyster/common'
export const ArtContent = ({ content, category, className }: { category?: MetadataCategory, content?: string, className?: string }) => {
export const ArtContent = ({
content,
category,
className,
preview
}: {
category?: MetadataCategory,
content?: string,
className?: string,
preview?: boolean,
}) => {
return category === 'video' ?
<video src={content} className={className} playsInline={true} autoPlay={true} controlsList="nodownload" loop={true} /> :
<video src={content}
className={className}
playsInline={true}
autoPlay={true}
controlsList="nodownload"
loop={true} /> :
<Image
src={content}
preview={preview}
wrapperClassName={className}
/>;
}

View File

@ -1,2 +1,3 @@
export * from './useArt';
export * from './useAuctions';
export * from './useUserArts';

View File

@ -0,0 +1,16 @@
import { TokenAccount, useUserAccounts } from '@oyster/common';
import React, { useMemo } from 'react';
import { useMeta } from './../contexts';
export const useUserArts = () => {
const { metadata } = useMeta();
const { userAccounts } = useUserAccounts();
const accountByMint = userAccounts.reduce((prev, acc) => {
prev.set(acc.info.mint.toBase58(), acc);
return prev;
}, new Map<string, TokenAccount>());
const ownedMetadata = metadata.filter(m => accountByMint.has(m.info.mint.toBase58()));
return ownedMetadata;
}

View File

@ -1,2 +1 @@
export * from './dex';
export * from './nft';

View File

@ -0,0 +1,173 @@
import { PublicKey } from '@solana/web3.js';
export * from './initAuctionManager';
export * from './redeemBid';
export * from './redeemLimitedEditionBid';
export * from './redeemMasterEditionBid';
export * from './redeemOpenEditionBid';
export * from './startAuction';
export * from './validateSafetyDepositBox';
export class AuctionManager {
key?: number;
authority?: PublicKey;
auction?: PublicKey;
vault?: PublicKey;
auctionProgram?: PublicKey;
tokenVaultProgram?: PublicKey;
tokenMetadataProgram?: PublicKey;
tokenProgram?: PublicKey;
state?: AuctionManagerState;
settings?: AuctionManagerSettings;
}
export class AuctionManagerSettings {
openEditionWinnerConstraint?: WinningConstraint;
openEditionNonWinningConstraint?: NonWinningConstraint;
winningConfigs?: WinningConfig[];
openEditionConfig?: number;
openEditionFixedPrice?: number;
}
export enum WinningConstraint {
NoOpenEdition,
OpenEditionGiven,
}
export enum NonWinningConstraint {
NoOpenEdition,
GivenForFixedPrice,
GivenForBidPrice,
}
export enum EditionType {
// Not an edition
NA,
/// Means you are auctioning off the master edition record
MasterEdition,
/// Means you are using the master edition to print off new editions during the auction (limited or open edition)
LimitedEdition,
}
export class WinningConfig {
safetyDepositBoxIndex?: number;
amount?: number;
hasAuthority?: boolean;
editionType?: EditionType;
}
export class WinningConfigState {
/// Used for cases of minting Limited Editions and keeping track of how many have been made so far.
amountMinted?: number;
/// Each safety deposit box needs to be validated via endpoint before auction manager will agree to let auction begin.
validated?: boolean;
/// Ticked to true when a prize is claimed
claimed?: boolean;
}
export class AuctionManagerState {
status?: AuctionManagerStatus;
/// When all configs are validated the auction is started and auction manager moves to Running
winningConfigsValidated?: number;
/// Each master edition used as a template has to grant it's authority to the auction manager.
/// This counter is incremented by one each time this is done. At the end of the auction; this is decremented
/// each time authority is delegated back to the owner or the new owner and when it hits 0 another condition
/// is met for going to Finished state.
masterEditionsWithAuthoritiesRemainingToReturn?: number;
winningConfigStates?: WinningConfigState[];
}
export enum AuctionManagerStatus {
Initialized,
Validated,
Running,
Disbursing,
Finished,
}
export class BidRedemptionTicket {
openEditionRedeemed?: boolean;
bidRedeemed?: boolean;
}
export const SCHEMA = new Map<any, any>([
[
AuctionManager,
{
kind: 'struct',
fields: [
['key', 'u8'],
['authority', 'u32'],
['auction', 'u32'],
['vault', 'u32'],
['auctionProgram', 'u32'],
['tokenVaultProgram', 'u32'],
['tokenMetadataProgram', 'u32'],
['tokenProgram', 'u32'],
['state', AuctionManagerState],
['settings', AuctionManagerSettings],
],
},
],
[
AuctionManagerSettings,
{
kind: 'struct',
fields: [
['openEditionWinnerConstraint', { kind: 'enum' }], // TODO:
['openEditionNonWinningConstraint', { kind: 'enum' }], // TODO:
['winningConfigs', [WinningConfig]], // TODO: check
['openEditionConfig', { kind: 'option', type: 'u8' }],
['openEditionFixedPrice', { kind: 'option', type: 'u8' }],
],
},
],
[
WinningConfig,
{
kind: 'struct',
fields: [
['safetyDepositBoxIndex', 'u8'],
['amount', 'u8'],
['hasAuthority', 'u8'], // bool
['editionType', { kind: 'enum' }], // TODO:
],
},
],
[
WinningConfigState,
{
kind: 'struct',
fields: [
['amountMinted', 'u8'],
['validated', 'u8'], // bool
['claimed', 'u8'], // bool
],
},
],
[
AuctionManagerState,
{
kind: 'struct',
fields: [
// TODO: fix enum
['status', { kind: 'enum' }],
['winningConfigsValidated', 'u8'],
['masterEditionsWithAuthoritiesRemainingToReturn', 'u8'],
['winningConfigStates', [WinningConfigState]],
],
},
],
[
BidRedemptionTicket,
{
kind: 'struct',
fields: [
['openEditionRedeemed', 'u8'], // bool
['bidRedeemed', 'u8'], // bool
],
},
],
]);

View File

@ -0,0 +1 @@
export const initAuctionManager = () => {};

View File

@ -0,0 +1 @@
export const redeemBid = () => {};

View File

@ -0,0 +1 @@
export const redeemLimitedEditionBid = () => {};

View File

@ -0,0 +1 @@
export const redeemMasterEditionBid = () => {};

View File

@ -0,0 +1 @@
export const redeemOpenEditionBid = () => {};

View File

@ -0,0 +1 @@
export const startAuction = () => {};

View File

@ -0,0 +1 @@
export const validateSafetyDepositBox = () => {};

View File

@ -25,11 +25,6 @@ export function Routes() {
<MetaProvider>
<AppLayout>
<Switch>
<Route
exact
path="/"
component={() => <HomeView />}
/>
<Route
exact
path="/art/create/:step_param?"
@ -65,6 +60,10 @@ export function Routes() {
path="/auction/:id"
component={() => <AuctionView />}
/>
<Route
path="/"
component={() => <HomeView />}
/>
</Switch>
</AppLayout>
</MetaProvider>

View File

@ -1,9 +1,18 @@
import { PublicKey } from '@solana/web3.js';
export const WORMHOLE_PROGRAM_ID = new PublicKey(
'WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC',
export const AUCTION_PROGRAM_ID = new PublicKey(
'aucPpK6yb5MpEHDwyp3Kg79McUoKVqTMeum7iJ9syeH',
);
export const VAULT_PROGRAM_ID = new PublicKey(
'vauLTA73sFPqA8whreUbBsbn3SLJH2vhrW9fP5dmfdC',
);
export const METAPLEX_PROGRAM_ID = new PublicKey(
'HvwC9QSAzvGXhhVrgPmauVwFWcYZhne3hVot9EbHuFTm',
);
// TODO: generate key ---
export const AR_SOL_HOLDER_ID = new PublicKey(
'HvwC9QSAzvGXhhVrgPmauVwFWcYZhne3hVot9EbHuFTm',
);

View File

@ -17,7 +17,7 @@ import { ArtCard } from './../../components/ArtCard';
import { UserSearch, UserValue } from './../../components/UserSearch';
import { Confetti } from './../../components/Confetti';
import './../styles.less';
import { mintNFT } from '../../models';
import { mintNFT } from '../../actions';
import {
MAX_METADATA_LEN,
MAX_OWNER_LEN,
@ -357,13 +357,13 @@ const InfoStep = (props: {
setAttributes: (attr: IMetadataExtension) => void;
confirm: () => void;
}) => {
const [creators, setCreators] = useState<Array<UserValue>>([]);
const [creators, setCreators] = useState<Array<UserValue>>([])
const [royalties, setRoyalties] = useState<Array<Royalty>>([])
useEffect(() => {
setRoyalties(creators.map(creator => ({
creator_key: creator.key,
amount: 100 / creators.length,
amount: Math.trunc(100 / creators.length),
})))
}, [creators])
@ -463,6 +463,10 @@ const InfoStep = (props: {
);
};
const shuffle = (array: Array<any>) => {
array.sort(() => Math.random() - 0.5);
}
const RoyaltiesSplitter = (props: {
creators: Array<UserValue>,
royalties: Array<Royalty>,
@ -476,15 +480,31 @@ const RoyaltiesSplitter = (props: {
const amt = royalty.amount
const handleSlide = (newAmt: number) => {
const diff = newAmt - amt
let n = props.royalties.length - 1
const othersRoyalties = props.royalties.filter(_royalty => _royalty.creator_key != royalty.creator_key)
if (othersRoyalties.length < 1) return
shuffle(othersRoyalties)
const others_n = props.royalties.length - 1
const sign = Math.sign(newAmt - amt)
let remaining = Math.abs(newAmt - amt)
let count = 0
while (remaining > 0 && count < 100) {
const idx = count % others_n
const _royalty = othersRoyalties[idx]
if (
(0 < _royalty.amount && _royalty.amount < 100) // Normal
|| (_royalty.amount == 0 && sign < 0) // Low limit
|| (_royalty.amount == 100 && sign > 0) // High limit
) {
_royalty.amount -= sign
remaining -= 1
}
count += 1
}
props.setRoyalties(props.royalties.map(_royalty => {
let computed_amount = _royalty.amount - diff / n
if (computed_amount <= 0) {
computed_amount = 0
// n -= 1
}
const computed_amount = othersRoyalties.find(newRoyalty =>
newRoyalty.creator_key == _royalty.creator_key
)?.amount
return {
..._royalty,
amount: _royalty.creator_key == royalty.creator_key ? newAmt : computed_amount,
@ -494,7 +514,7 @@ const RoyaltiesSplitter = (props: {
return (
<Row key={idx} style={{ margin: '5px auto' }}>
<Col span={11} className="slider-elem">{creator.label}</Col>
<Col span={8} className="slider-elem">{amt.toFixed(0)}%</Col>
<Col span={8} className="slider-elem">{amt}%</Col>
<Col span={4}><Slider value={amt} onChange={handleSlide} /></Col>
</Row>
)

View File

@ -1,20 +1,12 @@
import { ParsedAccount, TokenAccount, useUserAccounts } from '@oyster/common';
import React from 'react';
import { ArtCard } from '../../components/ArtCard';
import { useMeta } from '../../contexts';
import { Row, Col } from 'antd';
import Masonry from 'react-masonry-css'
import { Link } from 'react-router-dom';
import { useUserArts } from '../../hooks';
export const ArtworksView = () => {
const { metadata } = useMeta();
const { userAccounts } = useUserAccounts();
const accountByMint = userAccounts.reduce((prev, acc) => {
prev.set(acc.info.mint.toBase58(), acc);
return prev;
}, new Map<string, TokenAccount>());
const ownedMetadata = metadata.filter(m => accountByMint.has(m.info.mint.toBase58()));
const ownedMetadata = useUserArts();
const breakpointColumnsObj = {
default: 4,
1100: 3,

View File

@ -0,0 +1,103 @@
import React, { useEffect, useState } from 'react';
import {
Row,
Button,
Modal,
ButtonProps,
} from 'antd';
import { ArtCard } from './../../components/ArtCard';
import './../styles.less';
import {
Metadata,
ParsedAccount,
} from '@oyster/common';
import { useUserArts } from '../../hooks';
import Masonry from 'react-masonry-css';
export interface ArtSelectorProps extends ButtonProps {
selected: ParsedAccount<Metadata>[];
setSelected: (selected: ParsedAccount<Metadata>[]) => void;
allowMultiple: boolean;
}
export const ArtSelector = (props: ArtSelectorProps) => {
const { selected, setSelected, allowMultiple, ...rest } = props;
const items = useUserArts();
const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set(props.selected.map(item => item.pubkey.toBase58())));
const [visible, setVisible] = useState(false);
const showModal = () => {
setVisible(true);
};
useEffect(() => {
props.setSelected(items.filter(item => selectedItems.has(item.pubkey.toBase58())));
}, [selectedItems]);
const breakpointColumnsObj = {
default: 4,
1100: 3,
700: 2,
500: 1
};
return (
<>
<Button {...rest} onClick={() => showModal} />
<Modal visible={visible}>
<Row className="call-to-action" style={{ marginBottom: 0 }}>
<h2>Select the NFT you want to sell</h2>
<p style={{ fontSize: '1.2rem' }}>
Select the NFT that you want to sell copy/copies of.
</p>
</Row>
<Row className="content-action">
<Masonry
breakpointCols={breakpointColumnsObj}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{items.map(m => {
const id = m.pubkey.toBase58();
const isSelected = selectedItems.has(id);
const onSelect = () => {
let list = [...selectedItems.keys()];
if (props.allowMultiple) {
list = [];
}
isSelected ?
setSelectedItems(new Set(list.filter(item => item !== id))) :
setSelectedItems(new Set([...list, id]));
};
return <ArtCard key={id}
image={m.info.extended?.image}
category={m.info.extended?.category}
name={m.info?.name}
symbol={m.info.symbol}
preview={false}
onClick={onSelect}
className={isSelected ? 'selected-card' : 'not-selected-card'}
/>;
})}
</Masonry>
</Row>
<Row>
<Button
type="primary"
size="large"
onClick={() => {
// TODO;
}}
className="action-btn"
>
Confirm
</Button>
</Row>
</Modal>
</>
);
};

File diff suppressed because it is too large Load Diff

View File

@ -61,7 +61,7 @@
.type-btn {
height: 80px;
width: 280px;
width: 450px;
border-radius: 8px;
background-color: #1d1d1d;
border-width: 0px;
@ -166,6 +166,7 @@
.action-field {
display: flex;
flex-direction: column;
text-align: left;
.field-title {
text-align: left;
@ -185,9 +186,34 @@
margin: 12px 0px;
}
.field-info {
text-align: left;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 12px;
}
margin-bottom: 30px;
}
.field-date {
background: #282828;
border-radius: 8px;
padding: 10px;
border-width: 0px;
margin-bottom: 10px;
}
.selected-card {
border-width: 3px;
border-color: #5870EE !important;
border-style: solid;
}
.not-selected-card {
}
.royalties-input {
width: 100%;
height: 50px;
@ -266,3 +292,15 @@
height: 24px;
margin-top: -7px;
}
.radio-field {
display: block;
height: 30px;
line-height: 30px;
}
.radio-subtitle {
color: dimgray;
font-size: 0.8rem;
margin-bottom: 10px;
}

View File

@ -3345,7 +3345,7 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@toruslabs/eccrypto@^1.1.5":
"@toruslabs/eccrypto@^1.1.5", "@toruslabs/eccrypto@^1.1.6":
version "1.1.6"
resolved "https://registry.yarnpkg.com/@toruslabs/eccrypto/-/eccrypto-1.1.6.tgz#ce877cf00d6f9cf7ab3daa6ac4d6d540110b813b"
integrity sha512-L3TAsdEARouyzTbSKE0PqcYXmHQiFh95FB2YnsRbWERCAF0VWg3kY/YA//M/HBZqCJoRwa5WRA61lWbM7zAX5Q==
@ -3372,6 +3372,44 @@
dependencies:
deepmerge "^4.2.2"
"@toruslabs/openlogin-ed25519@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@toruslabs/openlogin-ed25519/-/openlogin-ed25519-0.7.0.tgz#836bb22b2daf0034e73d3666ad0c98227d342f36"
integrity sha512-+JjCadlbBI1sBie+Z/CS5EHVjjCThxq0imj0VNEcNDJUqdF91uJJghyWx46GllSHoJhWh7BsZUpVU0WZZMBPmw==
dependencies:
"@toruslabs/tweetnacl-js" "^1.0.3"
"@toruslabs/openlogin-jrpc@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@toruslabs/openlogin-jrpc/-/openlogin-jrpc-0.7.0.tgz#72fee3a94e9488ae133b0595bdf4d272ca01010d"
integrity sha512-BO6QnMQoZDp6O/JPVRj216es2m+10JcvtvudNhtfeCBPEXw/uc/OBRBBM0CC002zAHDgivSlPCc+8KOvNRXl1w==
dependencies:
"@toruslabs/openlogin-utils" "^0.7.0"
end-of-stream "^1.4.4"
fast-safe-stringify "^2.0.7"
once "^1.4.0"
pump "^3.0.0"
"@toruslabs/openlogin-utils@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@toruslabs/openlogin-utils/-/openlogin-utils-0.7.0.tgz#d612aacd0daaaf48734c7512335777569113902b"
integrity sha512-6caBBLGKjcDulU6oD0p7obtg0dTQlCLTAkDStkwSK9VObMOA/eR92jn3ivjE+r+epiU0LH7kFLAE8/ZFbtpNCw==
dependencies:
base64url "^3.0.1"
keccak "^3.0.1"
randombytes "^2.1.0"
"@toruslabs/openlogin@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@toruslabs/openlogin/-/openlogin-0.7.0.tgz#0169a38b0ab634f13354ded010565ff8ba179db1"
integrity sha512-p3s1i3VaHOLBdSjgYtUhW59FdYuAp3FkTxoQHmVr2Yr8NoIhAtRBd9JWKuHm5kGtjmb6+eI7pQx1CVoIph5yWw==
dependencies:
"@toruslabs/eccrypto" "^1.1.6"
"@toruslabs/openlogin-jrpc" "^0.7.0"
"@toruslabs/openlogin-utils" "^0.7.0"
lodash.merge "^4.6.2"
pump "^3.0.0"
"@toruslabs/torus-embed@^1.8.0", "@toruslabs/torus-embed@^1.9.10":
version "1.9.14"
resolved "https://registry.yarnpkg.com/@toruslabs/torus-embed/-/torus-embed-1.9.14.tgz#6d487a735ec6e570d62a044069581dc372ab193e"
@ -3409,6 +3447,11 @@
memory-cache "^0.2.0"
web3-utils "^1.3.3"
"@toruslabs/tweetnacl-js@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@toruslabs/tweetnacl-js/-/tweetnacl-js-1.0.3.tgz#52abbcd2a6b77959ef6a98afedce77764d87226d"
integrity sha512-WQJYMTR/bkqvpk3DWOqRt5e24RhwJp9PXUoSj4zSthd3+fDhKYCI56YVMPNDKah1fCffOe9F3m8iZ5SgDZ+Csw==
"@typechain/ethers-v4@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@typechain/ethers-v4/-/ethers-v4-1.0.1.tgz#268b619c2660bf7689cda86e30798bd30753ae94"
@ -5977,6 +6020,11 @@ base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
base64url@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
base@^0.11.1:
version "0.11.2"
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
@ -8835,7 +8883,7 @@ encoding@^0.1.11:
dependencies:
iconv-lite "^0.6.2"
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1:
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@ -10018,7 +10066,7 @@ fast-memoize@^2.5.2:
resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e"
integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==
fast-safe-stringify@^2.0.6:
fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
@ -13524,7 +13572,7 @@ lodash.memoize@4.x, lodash.memoize@^4.1.2:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.merge@^4.4.0:
lodash.merge@^4.4.0, lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==