Add attributes when creating NFT and display (#219)

* attempt to set attributes

* add support for attributes, creation, auction, art view

* validate on Continue to royalties, support for number value, trait_type is optional

* update antd no effect, remove label wrapping which breaks form

Co-authored-by: Arrowana <8245419+Arrowana@users.noreply.github.com>
This commit is contained in:
Pierre 2021-08-25 02:05:36 +10:00 committed by GitHub
parent 2b5ed5feae
commit 71f674af4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 198 additions and 43 deletions

View File

@ -43,7 +43,7 @@
"@types/echarts": "^4.9.0",
"@types/react-router-dom": "^5.1.6",
"@welldone-software/why-did-you-render": "^6.0.5",
"antd": "^4.6.6",
"antd": "^4.16.12",
"bignumber.js": "^9.0.1",
"bn.js": "^5.1.3",
"borsh": "^0.4.0",

View File

@ -60,6 +60,8 @@ export type MetadataFile = {
export type FileOrString = MetadataFile | string;
export type Attribute = {trait_type?: string, display_type?: string, value: string | number};
export interface IMetadataExtension {
name: string;
symbol: string;
@ -70,6 +72,8 @@ export interface IMetadataExtension {
image: string;
animation_url?: string;
attributes?: Attribute[];
// stores link to item on meta
external_url: string;

View File

@ -13,6 +13,7 @@ import {
findProgramAddress,
StringPublicKey,
toPublicKey,
Attribute,
} from '@oyster/common';
import React from 'react';
import { MintLayout, Token } from '@solana/spl-token';
@ -50,6 +51,7 @@ export const mintNFT = async (
description: string;
image: string | undefined;
animation_url: string | undefined;
attributes: Attribute[] | undefined;
external_url: string;
properties: any;
creators: Creator[] | null;
@ -70,6 +72,7 @@ export const mintNFT = async (
seller_fee_basis_points: metadata.sellerFeeBasisPoints,
image: metadata.image,
animation_url: metadata.animation_url,
attributes: metadata.attributes,
external_url: metadata.external_url,
properties: {
...metadata.properties,

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Row, Col, Divider, Layout, Tag, Button, Skeleton } from 'antd';
import { Row, Col, Divider, Layout, Tag, Button, Skeleton, List, Card } from 'antd';
import { useParams } from 'react-router-dom';
import { useArt, useExtendedArt } from './../../hooks';
@ -36,6 +36,7 @@ export const ArtView = () => {
// }, new Map<string, TokenAccount>());
const description = data?.description;
const attributes = data?.attributes;
const pubkey = wallet?.publicKey?.toBase58() || '';
@ -180,7 +181,7 @@ export const ArtView = () => {
Mark as Sold
</Button> */}
</Col>
<Col span="24">
<Col span="12">
<Divider />
{art.creators?.find(c => !c.verified) && unverified}
<br />
@ -194,6 +195,25 @@ export const ArtView = () => {
<div className="info-header">ABOUT THE CREATOR</div>
<div className="info-content">{art.about}</div> */}
</Col>
<Col span="12">
{attributes &&
<>
<Divider />
<br />
<div className="info-header">Attributes</div>
<List
size="large"
grid={{ column: 4 }}
>
{attributes.map(attribute =>
<List.Item>
<Card title={attribute.trait_type}>{attribute.value}</Card>
</List.Item>
)}
</List>
</>
}
</Col>
</Row>
</Col>
</Content>

View File

@ -13,6 +13,7 @@ import {
InputNumber,
Form,
Typography,
Space,
} from 'antd';
import { ArtCard } from './../../components/ArtCard';
import { UserSearch, UserValue } from './../../components/UserSearch';
@ -23,6 +24,7 @@ import {
useConnection,
useWallet,
IMetadataExtension,
Attribute,
MetadataCategory,
useConnectionConfig,
Creator,
@ -39,6 +41,7 @@ import { useHistory, useParams } from 'react-router-dom';
import { cleanName, getLast } from '../../utils/utils';
import { AmountLabel } from '../../components/AmountLabel';
import useWindowDimensions from '../../utils/layout';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
const { Step } = Steps;
const { Dragger } = Upload;
@ -65,6 +68,7 @@ export const ArtCreateView = () => {
external_url: '',
image: '',
animation_url: undefined,
attributes: undefined,
seller_fee_basis_points: 0,
creators: [],
properties: {
@ -96,6 +100,7 @@ export const ArtCreateView = () => {
sellerFeeBasisPoints: attributes.seller_fee_basis_points,
image: attributes.image,
animation_url: attributes.animation_url,
attributes: attributes.attributes,
external_url: attributes.external_url,
properties: {
files: attributes.properties.files,
@ -547,6 +552,7 @@ const InfoStep = (props: {
props.files,
props.attributes,
);
const [form] = Form.useForm();
useEffect(() => {
setRoyalties(
@ -642,6 +648,54 @@ const InfoStep = (props: {
className="royalties-input"
/>
</label>
<label className="action-field">
<span className="field-title">Attributes</span>
</label>
<Form
name="dynamic_attributes"
form={form}
autoComplete="off"
>
<Form.List name="attributes">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey }) => (
<Space key={key} align="baseline">
<Form.Item
name={[name, 'trait_type']}
fieldKey={[fieldKey, 'trait_type']}
hasFeedback
>
<Input placeholder="trait_type (Optional)" />
</Form.Item>
<Form.Item
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
rules={[{ required: true, message: 'Missing value' }]}
hasFeedback
>
<Input placeholder="value" />
</Form.Item>
<Form.Item
name={[name, 'display_type']}
fieldKey={[fieldKey, 'display_type']}
hasFeedback
>
<Input placeholder="display_type (Optional)" />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
Add attribute
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form>
</Col>
</Row>
@ -650,11 +704,24 @@ const InfoStep = (props: {
type="primary"
size="large"
onClick={() => {
props.setAttributes({
...props.attributes,
});
form.validateFields()
.then(values => {
const nftAttributes = values.attributes;
// value is number if possible
for (const nftAttribute of nftAttributes || []) {
const newValue = Number(nftAttribute.value);
if (!isNaN(newValue)) {
nftAttribute.value = newValue;
}
}
console.log('Adding NFT attributes:', nftAttributes)
props.setAttributes({
...props.attributes,
attributes: nftAttributes,
});
props.confirm();
props.confirm();
})
}}
className="action-btn"
>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Row, Col, Button, Skeleton, Carousel } from 'antd';
import { Row, Col, Button, Skeleton, Carousel, List, Card } from 'antd';
import { AuctionCard } from '../../components/AuctionCard';
import { Connection } from '@solana/web3.js';
import {
@ -95,6 +95,7 @@ export const AuctionView = () => {
const hasDescription = data === undefined || data.description === undefined;
const description = data?.description;
const attributes = data?.attributes;
const items = [
...(auction?.items
@ -158,7 +159,22 @@ export const AuctionView = () => {
No description provided.
</div>
))}
</div>
{attributes &&
<>
<h6>Attributes</h6>
<List
grid={{ column: 4 }}
>
{attributes.map(attribute =>
<List.Item>
<Card title={attribute.trait_type}>{attribute.value}</Card>
</List.Item>
)}
</List>
</>}
{/* {auctionData[id] && (
<>
<h6>About this Auction</h6>

View File

@ -23,7 +23,7 @@
resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.1.0.tgz#480b025f4b20ef7fe8f47d4a4846e4fee84ea06c"
integrity sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ==
"@ant-design/icons@^4.4.0", "@ant-design/icons@^4.6.2":
"@ant-design/icons@^4.4.0":
version "4.6.2"
resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.6.2.tgz#290f2e8cde505ab081fda63e511e82d3c48be982"
integrity sha512-QsBG2BxBYU/rxr2eb8b2cZ4rPKAPBpzAR+0v6rrZLp/lnyvflLH3tw1vregK+M7aJauGWjIGNdFmUfpAOtw25A==
@ -34,6 +34,17 @@
classnames "^2.2.6"
rc-util "^5.9.4"
"@ant-design/icons@^4.6.3":
version "4.6.3"
resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.6.3.tgz#cdcccf4df24d51501acef9228444efb7ef8938b1"
integrity sha512-OO4JW3OE13FKahplPYhqEg3uEhMiMDxujVUUx/RJUCEkSgBtAEnpKnq8oz2sBKqXeEhkr9/GE2tAHO1gyc70Uw==
dependencies:
"@ant-design/colors" "^6.0.0"
"@ant-design/icons-svg" "^4.0.0"
"@babel/runtime" "^7.11.2"
classnames "^2.2.6"
rc-util "^5.9.4"
"@ant-design/react-slick@~0.28.1":
version "0.28.3"
resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-0.28.3.tgz#ad5cf1cf50363c1a3842874d69d0ce1f26696e71"
@ -3166,13 +3177,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
antd@^4.6.6:
version "4.16.1"
resolved "https://registry.yarnpkg.com/antd/-/antd-4.16.1.tgz#7e6bcd24a8b7afb0e0c96239bc3a986ef3958167"
integrity sha512-v7KfUYvEAiqfTECKC4/VkpRPB/4RRxZLR3b2kKCYEtcj4nEHvsOKfO5CDbWVtSUmCehxOXNR2lV+UNy06KHBnA==
antd@^4.16.12:
version "4.16.12"
resolved "https://registry.yarnpkg.com/antd/-/antd-4.16.12.tgz#8d32a1ad6a80fd2ea61e6f1c432b31782fd2cd50"
integrity sha512-vFptOyOo0EubF6sgdJdH8GwnphcZcxV2QG+znSUj4hMOzRI8a0p3XS2mvKpsS92bu4PBuvsc9wmNQNnOfh1GrA==
dependencies:
"@ant-design/colors" "^6.0.0"
"@ant-design/icons" "^4.6.2"
"@ant-design/icons" "^4.6.3"
"@ant-design/react-slick" "~0.28.1"
"@babel/runtime" "^7.12.5"
array-tree-filter "^2.1.0"
@ -3183,17 +3194,17 @@ antd@^4.6.6:
rc-cascader "~1.4.0"
rc-checkbox "~2.3.0"
rc-collapse "~3.1.0"
rc-dialog "~8.5.1"
rc-dialog "~8.6.0"
rc-drawer "~4.3.0"
rc-dropdown "~3.2.0"
rc-field-form "~1.20.0"
rc-image "~5.2.4"
rc-image "~5.2.5"
rc-input-number "~7.1.0"
rc-mentions "~1.6.1"
rc-menu "~9.0.9"
rc-menu "~9.0.12"
rc-motion "^2.4.0"
rc-notification "~4.5.7"
rc-pagination "~3.1.6"
rc-pagination "~3.1.9"
rc-picker "~2.5.10"
rc-progress "~3.1.0"
rc-rate "~2.9.0"
@ -3203,16 +3214,15 @@ antd@^4.6.6:
rc-steps "~4.1.0"
rc-switch "~3.2.0"
rc-table "~7.15.1"
rc-tabs "~11.9.1"
rc-tabs "~11.10.0"
rc-textarea "~0.3.0"
rc-tooltip "~5.1.1"
rc-tree "~4.1.0"
rc-tree "~4.2.1"
rc-tree-select "~4.3.0"
rc-trigger "^5.2.1"
rc-trigger "^5.2.10"
rc-upload "~4.3.0"
rc-util "^5.13.1"
scroll-into-view-if-needed "^2.2.25"
warning "^4.0.3"
any-promise@^1.0.0:
version "1.3.0"
@ -12162,10 +12172,10 @@ rc-collapse@~3.1.0:
rc-util "^5.2.1"
shallowequal "^1.1.0"
rc-dialog@~8.5.0, rc-dialog@~8.5.1:
version "8.5.2"
resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-8.5.2.tgz#530e289c25a31c15c85a0e8a4ba3f33414bff418"
integrity sha512-3n4taFcjqhTE9uNuzjB+nPDeqgRBTEGBfe46mb1e7r88DgDo0lL4NnxY/PZ6PJKd2tsCt+RrgF/+YeTvJ/Thsw==
rc-dialog@~8.6.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-8.6.0.tgz#3b228dac085de5eed8c6237f31162104687442e7"
integrity sha512-GSbkfqjqxpZC5/zc+8H332+q5l/DKUhpQr0vdX2uDsxo5K0PhvaMEVjyoJUTkZ3+JstEADQji1PVLVb/2bJeOQ==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.6"
@ -12199,14 +12209,14 @@ rc-field-form@~1.20.0:
async-validator "^3.0.3"
rc-util "^5.8.0"
rc-image@~5.2.4:
version "5.2.4"
resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-5.2.4.tgz#ff1059f937bde6ca918c6f1beb316beba911f255"
integrity sha512-kWOjhZC1OoGKfvWqtDoO9r8WUNswBwnjcstI6rf7HMudz0usmbGvewcWqsOhyaBRJL9+I4eeG+xiAoxV1xi75Q==
rc-image@~5.2.5:
version "5.2.5"
resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-5.2.5.tgz#44e6ffc842626827960e7ab72e1c0d6f3a8ce440"
integrity sha512-qUfZjYIODxO0c8a8P5GeuclYXZjzW4hV/5hyo27XqSFo1DmTCs2HkVeQObkcIk5kNsJtgsj1KoPThVsSc/PXOw==
dependencies:
"@babel/runtime" "^7.11.2"
classnames "^2.2.6"
rc-dialog "~8.5.0"
rc-dialog "~8.6.0"
rc-util "^5.0.6"
rc-input-number@~7.1.0:
@ -12230,7 +12240,7 @@ rc-mentions@~1.6.1:
rc-trigger "^5.0.4"
rc-util "^5.0.1"
rc-menu@^9.0.0, rc-menu@~9.0.9:
rc-menu@^9.0.0:
version "9.0.10"
resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-9.0.10.tgz#59fde6442c138dc60693fbe02ea50c33c8164f38"
integrity sha512-wb7fZZ3f5KBqr7v3q8U1DB5K4SEm31KLPe/aANyrHajVJjQpiiGTMLF7ZB7vyqjC4QJq0SJewB4FkumT2U86fw==
@ -12243,6 +12253,19 @@ rc-menu@^9.0.0, rc-menu@~9.0.9:
rc-util "^5.12.0"
shallowequal "^1.1.0"
rc-menu@~9.0.12:
version "9.0.12"
resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-9.0.12.tgz#492c4bb07a596e2ce07587c669b27ee28c3810c5"
integrity sha512-8uy47DL36iDEwVZdUO/fjhhW5+4j0tYlrCsOzw6iy8MJqKL7/HC2pj7sL/S9ayp2+hk9fYQYB9Tu+UN+N2OOOQ==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "2.x"
rc-motion "^2.4.3"
rc-overflow "^1.2.0"
rc-trigger "^5.1.2"
rc-util "^5.12.0"
shallowequal "^1.1.0"
rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.2.0, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.0, rc-motion@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.4.3.tgz#2afd129da8764ee0372ba83442949d8ecb1c7ad2"
@ -12272,10 +12295,10 @@ rc-overflow@^1.0.0, rc-overflow@^1.2.0:
rc-resize-observer "^1.0.0"
rc-util "^5.5.1"
rc-pagination@~3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.6.tgz#db3c06e50270b52fe272ac527c1fdc2c8d28af1f"
integrity sha512-Pb2zJEt8uxXzYCWx/2qwsYZ3vSS9Eqdw0cJBli6C58/iYhmvutSBqrBJh51Z5UzYc5ZcW5CMeP5LbbKE1J3rpw==
rc-pagination@~3.1.9:
version "3.1.9"
resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.9.tgz#797ad75d85b1ef7a82801207ead410110337fdd6"
integrity sha512-IKBKaJ4icVPeEk9qRHrFBJmHxBUrCp3+nENBYob4Ofqsu3RXjBOy4N36zONO7oubgLyiG3PxVmyAuVlTkoc7Jg==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.1"
@ -12373,10 +12396,10 @@ rc-table@~7.15.1:
rc-util "^5.13.0"
shallowequal "^1.1.0"
rc-tabs@~11.9.1:
version "11.9.1"
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-11.9.1.tgz#5b2e74da9a276978c2172ef9a05ae8af14da74cb"
integrity sha512-CLNx3qaWnO8KBWPd+7r52Pfk0MoPyKtlr+2ltWq2I9iqAjd1nZu6iBpQP7wbWBwIomyeFNw/WjHdRN7VcX5Qtw==
rc-tabs@~11.10.0:
version "11.10.1"
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-11.10.1.tgz#7b112f78bac998480c777ae160adc425e3fdb7cb"
integrity sha512-ey1i2uMyfnRNYbViLcUYGH+Y7hueJbdCVSLaXnXki9hxBcGqxJMPy9t5xR0n/3QFQspj7Tf6+2VTXVtmO7Yaug==
dependencies:
"@babel/runtime" "^7.11.2"
classnames "2.x"
@ -12414,7 +12437,7 @@ rc-tree-select@~4.3.0:
rc-tree "^4.0.0"
rc-util "^5.0.5"
rc-tree@^4.0.0, rc-tree@~4.1.0:
rc-tree@^4.0.0:
version "4.1.5"
resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-4.1.5.tgz#734ab1bfe835e78791be41442ca0e571147ab6fa"
integrity sha512-q2vjcmnBDylGZ9/ZW4F9oZMKMJdbFWC7um+DAQhZG1nqyg1iwoowbBggUDUaUOEryJP+08bpliEAYnzJXbI5xQ==
@ -12425,7 +12448,18 @@ rc-tree@^4.0.0, rc-tree@~4.1.0:
rc-util "^5.0.0"
rc-virtual-list "^3.0.1"
rc-trigger@^5.0.0, rc-trigger@^5.0.4, rc-trigger@^5.1.2, rc-trigger@^5.2.1:
rc-tree@~4.2.1:
version "4.2.2"
resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-4.2.2.tgz#4429187cbbfbecbe989714a607e3de8b3ab7763f"
integrity sha512-V1hkJt092VrOVjNyfj5IYbZKRMHxWihZarvA5hPL/eqm7o2+0SNkeidFYm7LVVBrAKBpOpa0l8xt04uiqOd+6w==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "2.x"
rc-motion "^2.0.1"
rc-util "^5.0.0"
rc-virtual-list "^3.0.1"
rc-trigger@^5.0.0, rc-trigger@^5.0.4, rc-trigger@^5.1.2:
version "5.2.8"
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.2.8.tgz#27c8291c24518b8f11d76c848f5424e0c429e94a"
integrity sha512-Tn84oGmvNBLXI+ptpzxyJx4ArKTduuB6l74ShDLhDaJaF9f5JAMizfx31L30ELVIzRr3Ze4sekG7rzwPGwVOdw==
@ -12436,6 +12470,17 @@ rc-trigger@^5.0.0, rc-trigger@^5.0.4, rc-trigger@^5.1.2, rc-trigger@^5.2.1:
rc-motion "^2.0.0"
rc-util "^5.5.0"
rc-trigger@^5.2.10:
version "5.2.10"
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.2.10.tgz#8a0057a940b1b9027eaa33beec8a6ecd85cce2b1"
integrity sha512-FkUf4H9BOFDaIwu42fvRycXMAvkttph9AlbCZXssZDVzz2L+QZ0ERvfB/4nX3ZFPh1Zd+uVGr1DEDeXxq4J1TA==
dependencies:
"@babel/runtime" "^7.11.2"
classnames "^2.2.6"
rc-align "^4.0.0"
rc-motion "^2.0.0"
rc-util "^5.5.0"
rc-upload@~4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-4.3.0.tgz#2fbbd11242f730802b900c09b469f28437bac0ff"
@ -15045,7 +15090,7 @@ walletlink@^2.1.0:
preact "^10.5.9"
rxjs "^6.6.3"
warning@^4.0.1, warning@^4.0.3:
warning@^4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==