diff --git a/README.md b/README.md index 5e5b1cef..0e7fbfa7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![OpenMinter header](/docs/assets/minterhead.png) -[![](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE) [![](https://img.shields.io/badge/version-v0.4.2-orange)](https://github.com/tqtezos/minter) +[![](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE) [![](https://img.shields.io/github/v/release/tqtezos/minter)](https://github.com/tqtezos/minter) ## OpenMinter @@ -12,7 +12,7 @@ capabilities to trade them. ### Notice -This software is in beta. At the moment, the smart contracts +This software is in beta. At the moment, the [smart contracts](https://github.com/tqtezos/minter-sdk) that OpenMinter uses have **not** been formally audited. Please use this software at your own risk. @@ -40,9 +40,9 @@ OpenMinter supports the following networks and software components: #### 🌐 Mainnet and Edonet #### 📦 Sandboxed development via [Flextesa][flextesa] #### 🎨 Multimedia NFTs powered by [TZIP-21](https://tzip.tezosagora.org/proposal/tzip-21/) -#### 👛 Various wallets via [Beacon](https://www.walletbeacon.io/) +#### ⚙️ Smart contracts based on [minter-sdk](https://github.com/tqtezos/minter-sdk) +#### 👛 Wallets compatible with [Beacon](https://www.walletbeacon.io/) #### 📖 Indexing via [Better Call Dev][bcdhub] -#### ⚙️ The latest [FA2](https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-12/tzip-12.md) specification #### 🚀 [IPFS](https://ipfs.io/) via a local node or [Pinata](https://pinata.cloud/) The following dependencies are required to run OpenMinter. diff --git a/docker-compose.yml b/docker-compose.yml index ccbbd1a9..b566d21e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: - mq ports: - 42000:14000 + restart: on-failure logging: options: max-size: 10m diff --git a/docs/20200716_InitialOutline.md b/docs/20200716_InitialOutline.md index 7108075c..eeaf83da 100644 --- a/docs/20200716_InitialOutline.md +++ b/docs/20200716_InitialOutline.md @@ -61,7 +61,7 @@ will be an element to also explore in future iterations. - Simple name - Description - Symbol - - General key-value pairs + - Attributes (General key-value pairs) - URL (IPFS image) - Contract with standard FA2 entry points - Backend diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..b87b8d3d --- /dev/null +++ b/netlify.toml @@ -0,0 +1,4 @@ +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 diff --git a/package.json b/package.json index 396e1b59..3d4078dc 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,14 @@ "@reduxjs/toolkit": "1.5.0", "@taquito/beacon-wallet": "8.0.6-beta.0", "@taquito/signer": "8.0.6-beta.0", - "@taquito/tzip16": "8.0.6-beta.0", "@taquito/taquito": "8.0.6-beta.0", + "@taquito/tzip16": "8.0.6-beta.0", + "@taquito/tzip12": "8.0.6-beta.0", "@types/lodash": "4.14.165", "@types/react": "16.9.12", "@types/react-dom": "16.9.0", "@types/react-dropzone": "5.1.0", + "@types/react-icons": "3.0.0", "@types/react-redux": "7.1.16", "axios-retry": "3.1.9", "buffer": "6.0.3", @@ -26,18 +28,19 @@ "react-dom": "16.13.1", "react-dropzone": "11.2.4", "react-feather": "2.0.9", + "react-icons": "4.2.0", "react-redux": "7.2.2", "typescript": "4.1.3", "wouter": "2.5.1" }, "devDependencies": { - "@types/async-retry": "1.4.2", - "@types/configstore": "4.0.0", - "@types/jest": "24.0.0", "@testing-library/jest-dom": "4.2.4", "@testing-library/react": "9.3.2", "@testing-library/user-event": "7.1.2", "@tsed/logger": "5.5.2", + "@types/async-retry": "1.4.2", + "@types/configstore": "4.0.0", + "@types/jest": "24.0.0", "async-retry": "1.3.1", "axios": "0.21.1", "configstore": "5.0.1", diff --git a/public/favicon.ico b/public/favicon.ico index bcd5dfd6..56e08125 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/scripts/bootstrap-contracts-config.ts b/scripts/bootstrap-contracts-config.ts index d8a5e65b..517ea8e2 100644 --- a/scripts/bootstrap-contracts-config.ts +++ b/scripts/bootstrap-contracts-config.ts @@ -14,6 +14,7 @@ interface BoostrapStorageCallback { interface BootstrapContractParams { configKey: string; contractFilename: string; + contractGitHash: string; contractAlias: string; initStorage: BoostrapStorageCallback; } @@ -63,11 +64,11 @@ async function getContractAddress( } async function fetchContractCode( - contractFilename: string + contractFilename: string, + contractGitHash: string ): Promise { const rawRepoUrl = 'https://raw.githubusercontent.com/tqtezos/minter-sdk'; - const gitHash = '8f67bb8c2abc12b8e6f8e529e1412262972deab3'; - const contractCodeUrl = `${rawRepoUrl}/${gitHash}/contracts/bin/${contractFilename}`; + const contractCodeUrl = `${rawRepoUrl}/${contractGitHash}/contracts/bin/${contractFilename}`; const response = await axios.get(contractCodeUrl); return { code: response.data, url: contractCodeUrl }; } @@ -130,7 +131,8 @@ async function bootstrapContract( let contract; try { const { code, url: contractCodeUrl } = await fetchContractCode( - params.contractFilename + params.contractFilename, + params.contractGitHash ); $log.info( @@ -175,6 +177,7 @@ async function bootstrap(env: string) { configKey: 'contracts.nftFaucet', contractAlias: 'nftFaucet', contractFilename: 'fa2_multi_nft_faucet.tz', + contractGitHash: 'aec441412d53653fa0048fee7c12c1eb1365909b', initStorage: initStorageNftFaucet }); @@ -183,6 +186,7 @@ async function bootstrap(env: string) { configKey: 'contracts.marketplace.fixedPrice.tez', contractAlias: 'fixedPriceMarketTez', contractFilename: 'fixed_price_sale_market_tez.tz', + contractGitHash: '8f67bb8c2abc12b8e6f8e529e1412262972deab3', initStorage: () => new MichelsonMap() }); } diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index ec18c357..aeef2417 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -4,6 +4,7 @@ import SplashPage from '../SplashPage'; import CreateNonFungiblePage from '../CreateNonFungiblePage'; import CollectionsCatalog from '../Collections/Catalog'; import CollectionsTokenDetail from '../Collections/TokenDetail'; +import MarketplaceCatalog from '../Marketplace/Catalog'; import Header from '../common/Header'; import { Flex } from '@chakra-ui/react'; import Notifications from '../common/Notifications'; @@ -40,6 +41,9 @@ export default function App() { + + + {({ contractAddress, tokenId }) => ( s.collections); + const dispatch = useDispatch(); + + return ( + + + + + + + Collections + + + + + + Featured + + dispatch(selectCollection(state.globalCollection))} + > + {state.collections[state.globalCollection].metadata.name} + + + Your Collections + + {Object.keys(state.collections) + .filter(address => address !== state.globalCollection) + .map(address => ( + dispatch(selectCollection(address))} + > + {state.collections[address].metadata.name} + + ))} + + + + ); +} diff --git a/src/components/Collections/Catalog/TokenGrid.tsx b/src/components/Collections/Catalog/TokenGrid.tsx index 3a650a6b..f11a131a 100644 --- a/src/components/Collections/Catalog/TokenGrid.tsx +++ b/src/components/Collections/Catalog/TokenGrid.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useLocation } from 'wouter'; -import { AspectRatio, Box, Flex, Grid, Image, Text } from '@chakra-ui/react'; +import { AspectRatio, Box, Flex, SimpleGrid, Image, Text } from '@chakra-ui/react'; import { Wind, HelpCircle } from 'react-feather'; import { Token, CollectionsState } from '../../../reducer/slices/collections'; import { ipfsUriToGatewayUrl } from '../../../lib/util/ipfs'; @@ -169,7 +169,7 @@ export default function TokenGrid({ state, walletAddress }: TokenGridProps) { } return ( - + {tokens.map(token => { return ( ); })} - + ); } diff --git a/src/components/Collections/Catalog/index.tsx b/src/components/Collections/Catalog/index.tsx index b0e1b4b7..bbe8c1fd 100644 --- a/src/components/Collections/Catalog/index.tsx +++ b/src/components/Collections/Catalog/index.tsx @@ -1,10 +1,11 @@ import React, { useEffect } from 'react'; -import { Box, Flex, Heading, Text } from '@chakra-ui/react'; +import { Box, Flex, Heading, Text, Link, Spinner } from '@chakra-ui/react'; import { useLocation } from 'wouter'; -import { RefreshCw } from 'react-feather'; +import { RefreshCw, ExternalLink } from 'react-feather'; import { MinterButton } from '../../common'; import Sidebar from './Sidebar'; import TokenGrid from './TokenGrid'; +import CollectionsDropdown from './CollectionsDropdown'; import { useSelector, useDispatch } from '../../../reducer'; import { @@ -48,8 +49,25 @@ export default function Catalog() { const collection = state.collections[selectedCollection]; return ( - - + + - - - {collection.metadata.name || ''} - - {collection.address} - + + + + {collection.metadata.name || ''} + + + + + + + {collection.address} + + + + + @@ -87,7 +136,16 @@ export default function Catalog() { Refresh - + {!collection.loaded ? ( + + + + Loading... + + + ) : ( + + )} ); diff --git a/src/components/Collections/TokenDetail/index.tsx b/src/components/Collections/TokenDetail/index.tsx index d6d7e758..4f31c4c4 100644 --- a/src/components/Collections/TokenDetail/index.tsx +++ b/src/components/Collections/TokenDetail/index.tsx @@ -1,13 +1,28 @@ import React, { useEffect, useState } from 'react'; import { useLocation } from 'wouter'; import { + Accordion, + AccordionButton, + AccordionIcon, + AccordionItem, + AccordionPanel, AspectRatio, Box, + Button, Flex, Heading, Image, Menu, MenuList, + Modal, + ModalCloseButton, + ModalContent, + ModalOverlay, + ResponsiveValue, + Slider, + SliderFilledTrack, + SliderThumb, + SliderTrack, Text, useDisclosure } from '@chakra-ui/react'; @@ -16,13 +31,15 @@ import { MinterButton, MinterMenuButton, MinterMenuItem } from '../../common'; import { TransferTokenModal } from '../../common/TransferToken'; import { SellTokenButton, CancelTokenSaleButton } from '../../common/SellToken'; import { BuyTokenButton } from '../../common/BuyToken'; -import { ipfsUriToGatewayUrl, uriToCid } from '../../../lib/util/ipfs'; +import { ipfsUriToGatewayUrl } from '../../../lib/util/ipfs'; import { useSelector, useDispatch } from '../../../reducer'; import { getContractNftsQuery, getNftAssetContractQuery } from '../../../reducer/async/queries'; +import { Maximize2 } from 'react-feather'; + function NotFound() { return ( @@ -72,7 +89,16 @@ function MediaNotFound() { ); } -function TokenImage(props: { src: string }) { +function TokenImage(props: { + id?: string; + src: string; + width?: string; + maxWidth?: string; + height?: string; + objectFit?: ResponsiveValue; + onLoad?: (event: React.SyntheticEvent) => void; + onFetch?: (type: string) => void; +}) { const [errored, setErrored] = useState(false); const [obj, setObj] = useState<{ url: string; type: string } | null>(null); useEffect(() => { @@ -87,8 +113,9 @@ function TokenImage(props: { src: string }) { url: URL.createObjectURL(blob), type: blob.type }); + props.onFetch?.(blob.type); })(); - }, [props.src]); + }, [props, props.onFetch, props.src]); if (errored) { return ; @@ -98,17 +125,20 @@ function TokenImage(props: { src: string }) { if (/^image\/.*/.test(obj.type)) { return ( setErrored(true)} + height={props.height} + onLoad={props.onLoad} /> ); } if (/^video\/.*/.test(obj.type)) { return ( -