├── src ├── pages │ ├── root.comp.js │ ├── topshot.comp.js │ ├── nav.comp.js │ ├── plays.comp.js │ ├── set.comp.js │ └── account.comp.js ├── util │ ├── address.util.js │ └── fetchPlays.js ├── config │ ├── local-config.comp.js │ ├── mainnet-config.comp.js │ └── testnet-config.comp.js └── index.js ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── .prettierrc ├── README.md ├── .gitignore ├── license.txt ├── package.json └── .github └── workflows └── main.yml /src/pages/root.comp.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrrkren/topshot-explorer/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrrkren/topshot-explorer/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrrkren/topshot-explorer/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "es5", 4 | "bracketSpacing": false, 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /src/util/address.util.js: -------------------------------------------------------------------------------- 1 | export function sansPrefix(address) { 2 | if (address == null) return null 3 | return address.replace(/^0x/, "") 4 | } 5 | 6 | export function withPrefix(address) { 7 | if (address == null) return null 8 | return "0x" + sansPrefix(address) 9 | } 10 | -------------------------------------------------------------------------------- /src/config/local-config.comp.js: -------------------------------------------------------------------------------- 1 | import {useEffect} from "react" 2 | import {config} from "@onflow/config" 3 | 4 | export function LocalConfig() { 5 | window.topshotAddress = "" 6 | window.topshotMarketAddress = "" 7 | useEffect(() => { 8 | config().put("accessNode.api", "http://localhost:8080").put("fcl.eventsPollRate", 1000) 9 | }, []) 10 | return null 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Topshot explorer 2 | https://topshotexplorer.com 3 | 4 | project template ripped from https://github.com/orodio/flow-view-source 5 | 6 | to run: 7 | `npm install` 8 | `npm start` 9 | 10 | 11 | for more information on topshot contracts: 12 | https://flow-view-source.com/mainnet/account/0x0b2a3299cc857e29 13 | https://flow-view-source.com/mainnet/account/0xc1e4f4f4c4257510 14 | -------------------------------------------------------------------------------- /src/config/mainnet-config.comp.js: -------------------------------------------------------------------------------- 1 | import {useEffect} from "react" 2 | import {config} from "@onflow/config" 3 | 4 | export function MainnetConfig() { 5 | window.topshotAddress = "0b2a3299cc857e29" 6 | window.topshotMarketAddress = "c1e4f4f4c4257510" 7 | useEffect(() => { 8 | config().put("accessNode.api", "https://rest-mainnet.onflow.org") 9 | }, []) 10 | return null 11 | } 12 | -------------------------------------------------------------------------------- /src/config/testnet-config.comp.js: -------------------------------------------------------------------------------- 1 | import {useEffect} from "react" 2 | import {config} from "@onflow/config" 3 | 4 | export function TestnetConfig() { 5 | window.topshotAddress = "877931736ee77cff" 6 | window.topshotMarketAddress = "547f177b243b4d80" 7 | useEffect(() => { 8 | config().put("accessNode.api", "https://rest-testnet.onflow.org") 9 | }, []) 10 | return null 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Eric Ren 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "topshot-explorer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ashvin27/react-datatable": "^1.5.3", 7 | "@onflow/config": "0.0.2", 8 | "@onflow/fcl": "^0.0.78", 9 | "@onflow/types": "0.0.3", 10 | "@testing-library/jest-dom": "^4.2.4", 11 | "@testing-library/react": "^9.3.2", 12 | "@testing-library/user-event": "^7.1.2", 13 | "bootstrap": "^4.5.2", 14 | "history": "^5.0.0", 15 | "prismjs": "^1.21.0", 16 | "react": "^16.13.1", 17 | "react-bootstrap": "^1.3.0", 18 | "react-dom": "^16.13.1", 19 | "react-ga": "^3.1.2", 20 | "react-paginate": "^6.5.0", 21 | "react-router-dom": "^5.2.0", 22 | "react-scripts": "3.4.3", 23 | "styled-components": "^5.1.1" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": "react-app" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | build-and-deploy: 17 | runs-on: ubuntu-latest 18 | steps: 19 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 20 | - uses: actions/checkout@v2 21 | - uses: borales/actions-yarn@v2.0.0 22 | with: 23 | cmd: install # will run `yarn install` command 24 | - uses: borales/actions-yarn@v2.0.0 25 | with: 26 | cmd: build # will run `yarn build` command 27 | - uses: jakejarvis/s3-sync-action@master 28 | with: 29 | args: --acl public-read --follow-symlinks --delete 30 | env: 31 | AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} 32 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 33 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 34 | AWS_REGION: 'us-east-1' # optional: defaults to us-east-1 35 | SOURCE_DIR: 'build' # optional: defaults to entire repository 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import * as fcl from "@onflow/fcl" 4 | import * as t from "@onflow/types" 5 | import {MainnetConfig} from "./config/mainnet-config.comp" 6 | // import {TestnetConfig} from "./config/testnet-config.comp" 7 | 8 | import {BrowserRouter as Router, Route, Switch} from "react-router-dom" 9 | 10 | import {Account} from "./pages/account.comp" 11 | import {TopShot} from "./pages/topshot.comp" 12 | import {TopShotNav} from "./pages/nav.comp" 13 | import {TopshotSet} from "./pages/set.comp" 14 | import {TopshotPlays} from "./pages/plays.comp" 15 | 16 | import "bootstrap/dist/css/bootstrap.css" 17 | 18 | window.fcl = fcl 19 | window.t = t 20 | window.topshotAddress = "" 21 | window.topshotMarketAddress = "" 22 | 23 | const NoMatch = () =>
route not found
24 | 25 | ReactDOM.render( 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | , 39 | document.getElementById("root") 40 | ) 41 | -------------------------------------------------------------------------------- /src/util/fetchPlays.js: -------------------------------------------------------------------------------- 1 | import * as fcl from "@onflow/fcl" 2 | 3 | const getTopShotPlays = async () => { 4 | var start = 1 5 | let limit = 3000 6 | 7 | var lastPlayFetched = false 8 | var res = {} 9 | while (!lastPlayFetched) { 10 | const resp = await fcl.send([ 11 | fcl.script` 12 | import TopShot from 0x${window.topshotAddress} 13 | access(all) struct MyPlay { 14 | access(all) let playID: UInt32 15 | access(all) let metadata: {String:String} 16 | 17 | init(playID: UInt32, metadata: {String:String}) { 18 | self.playID = playID 19 | self.metadata = metadata 20 | } 21 | } 22 | access(all) struct TopShotData { 23 | access(all) let totalSupply: UInt64 24 | access(all) let plays: [MyPlay] 25 | access(all) let currentSeries: UInt32 26 | access(all) var lastPlayFetched: Bool 27 | access(all) let nextPlayID: UInt32 28 | init() { 29 | self.totalSupply = TopShot.totalSupply 30 | self.currentSeries = TopShot.currentSeries 31 | self.plays = [] 32 | self.lastPlayFetched= false 33 | self.nextPlayID = TopShot.nextPlayID 34 | } 35 | access(all) fun addPlay(p: MyPlay) { 36 | self.plays.append(p) 37 | if TopShot.nextPlayID-1 == p.playID { 38 | self.lastPlayFetched = true 39 | } 40 | } 41 | } 42 | access(all) fun main(start: UInt32, end: UInt32): TopShotData { 43 | let ts = TopShotData() 44 | var i = start 45 | while i < end { 46 | let pm = TopShot.getPlayMetaData(playID: i) 47 | if pm == nil { 48 | break 49 | } 50 | ts.addPlay(p: MyPlay(playID: i, metadata: pm!)) 51 | i = i + 1 52 | } 53 | return ts 54 | }`, 55 | fcl.args([fcl.arg(start, fcl.t.UInt32), fcl.arg(start+limit, fcl.t.UInt32)]), 56 | ]) 57 | let newRes = await fcl.decode(resp) 58 | 59 | lastPlayFetched = newRes.lastPlayFetched 60 | 61 | res = { 62 | ...res, 63 | ...newRes, 64 | plays : [...res.plays || [], ...newRes.plays] 65 | } 66 | 67 | start = start+limit 68 | 69 | } 70 | return res 71 | } 72 | 73 | export {getTopShotPlays}; -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | Topshot Explorer 25 | 30 | 31 | 32 | 39 | 40 | 41 | 42 | 43 |
44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/pages/topshot.comp.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from "react" 2 | import * as fcl from "@onflow/fcl" 3 | import styled from "styled-components" 4 | import Prism from "prismjs" 5 | import {getTopShotPlays} from "../util/fetchPlays"; 6 | 7 | const getTopShotSets = async () => { 8 | const resp = await fcl.send([ 9 | fcl.script` 10 | import TopShot from 0x${window.topshotAddress} 11 | access(all) struct Set { 12 | access(all) let id: UInt32 13 | access(all) let setName: String 14 | access(all) let playIDs: [UInt32] 15 | access(all) var locked: Bool 16 | init(id: UInt32, setName: String) { 17 | self.id = id 18 | self.setName = setName 19 | self.playIDs = TopShot.getPlaysInSet(setID: id)! 20 | self.locked = TopShot.isSetLocked(setID: id)! 21 | } 22 | } 23 | access(all) struct TopShotData { 24 | access(all) var sets: [Set] 25 | init() { 26 | var sets: [Set] = [] 27 | self.sets = sets 28 | 29 | var setID = UInt32(1) 30 | 31 | while setID < TopShot.nextSetID { 32 | var setName = TopShot.getSetName(setID: setID) 33 | if setName == nil { 34 | setID = setID + UInt32(1) 35 | continue 36 | } 37 | sets.append(Set(id: setID, setName: setName!)) 38 | setID = setID + UInt32(1) 39 | } 40 | self.sets = sets 41 | } 42 | } 43 | access(all) fun main(): TopShotData { 44 | return TopShotData() 45 | } `, 46 | ]) 47 | return fcl.decode(resp) 48 | 49 | } 50 | 51 | const getTopShot = async () => { 52 | const plays = await getTopShotPlays(); 53 | const sets = await getTopShotSets(); 54 | return {...plays, ...sets} 55 | } 56 | 57 | const Root = styled.div` 58 | // font-family: monospace; 59 | // color: #233445; 60 | font-size: 13px; 61 | padding: 21px; 62 | ` 63 | 64 | const Muted = styled.span` 65 | color: #78899a; 66 | ` 67 | 68 | export function TopShot() { 69 | const [error, setError] = useState(null) 70 | const [topshotData, setTopShotData] = useState(null) 71 | useEffect(() => { 72 | getTopShot() 73 | .then((d) => { 74 | setTopShotData(d) 75 | }) 76 | .catch((e) => setError(JSON.stringify(e))) 77 | }, []) 78 | 79 | const codeChange = (topshotData || {}).code || new Uint8Array() 80 | useEffect(() => Prism.highlightAll(), [codeChange]) 81 | 82 | if (error != null) 83 | return ( 84 | 85 | 86 |

87 | Error Fetching TopShot Info: {error} 88 |

89 |
90 |
91 | ) 92 | if (topshotData == null) 93 | return ( 94 | 95 |

96 | Fetching info for TopShot 97 |

98 |
99 | ) 100 | 101 | return ( 102 | 103 |

104 | TopShot Contract: 105 | 0x0b2a3299cc857e29 106 |

107 |

108 | TopShot Market Contract: 109 | 0xc1e4f4f4c4257510 110 |

111 |
112 | {topshotData && ( 113 |
114 |

115 | Total Supply: 116 | {topshotData.totalSupply} 117 |

118 |

119 | Current Series: 120 | S{topshotData.currentSeries} 121 |

122 |
123 | )} 124 |
125 | 126 |

127 | Built with{" "} 128 | 129 | ❤️ 130 | {" "} 131 | on flow 132 |
133 | open sourced here, PRs welcome! 134 |

135 |
136 | ) 137 | } 138 | -------------------------------------------------------------------------------- /src/pages/nav.comp.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from "react" 2 | import * as fcl from "@onflow/fcl" 3 | import styled from "styled-components" 4 | 5 | import {Navbar, Nav, NavDropdown, Form, FormControl, Button} from "react-bootstrap" 6 | const Red = styled.span` 7 | color: red; 8 | ` 9 | const Green = styled.span` 10 | color: green; 11 | ` 12 | const splitSets = (sets) => { 13 | return sets.reduce((acc, cur) => {(acc[cur.series] = acc[cur.series] || []).push(cur); return acc}, {}) 14 | } 15 | 16 | const getTopshotOverview = async () => { 17 | const resp = await fcl.send([ 18 | fcl.script` 19 | import TopShot from 0x${window.topshotAddress} 20 | access(all) struct Set { 21 | access(all) let id: UInt32 22 | access(all) let setName: String 23 | access(all) var locked: Bool 24 | access(all) let series: UInt32 25 | init(id: UInt32, setName: String, series: UInt32) { 26 | self.id = id 27 | self.setName = setName 28 | self.series = series 29 | self.locked = TopShot.isSetLocked(setID: id)! 30 | } 31 | } 32 | access(all) struct TopshotOverview { 33 | access(all) let totalSupply: UInt64 34 | access(all) var sets: [Set] 35 | init() { 36 | self.totalSupply = TopShot.totalSupply 37 | var setID = UInt32(1) 38 | var sets: [Set] = [] 39 | self.sets = sets 40 | 41 | while setID < TopShot.nextSetID { 42 | var setName = TopShot.getSetName(setID: setID) 43 | if setName == nil { 44 | setID = setID + UInt32(1) 45 | continue 46 | } 47 | var series = TopShot.getSetSeries(setID: setID) 48 | sets.append(Set(id: setID, setName: setName!, series: series!)) 49 | setID = setID + UInt32(1) 50 | } 51 | self.sets = sets 52 | } 53 | } 54 | access(all) fun main(): TopshotOverview { 55 | return TopshotOverview() 56 | } `, 57 | ]) 58 | return fcl.decode(resp) 59 | } 60 | 61 | export function TopShotNav() { 62 | const [error, setError] = useState(null) 63 | const [accountAddress, setAccountAddress] = useState("") 64 | const [seriesSets, setSeriesSets] = useState(null) 65 | useEffect(() => { 66 | getTopshotOverview() 67 | .then((d) => { 68 | console.log(d) 69 | setSeriesSets(splitSets(d.sets)) 70 | }) 71 | .catch(setError) 72 | }, []) 73 | return ( 74 | 75 | 76 | {error ? Topshot Explorer : "Topshot Explorer"} 77 | 78 | 79 | 80 | 109 |
110 | { 114 | setAccountAddress(event.target.value) 115 | }} 116 | className="mr-sm-2" 117 | /> 118 | 121 | 122 |
123 |
124 | ) 125 | } 126 | -------------------------------------------------------------------------------- /src/pages/plays.comp.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from "react" 2 | import styled from "styled-components" 3 | import ReactDatatable from '@ashvin27/react-datatable' 4 | import {getTopShotPlays} from "../util/fetchPlays"; 5 | 6 | const Root = styled.div` 7 | font-size: 13px; 8 | padding: 21px; 9 | ` 10 | 11 | const Muted = styled.span` 12 | color: #78899a; 13 | ` 14 | 15 | const H1 = styled.h1`` 16 | 17 | const Button = styled.button` 18 | margin-left: 20px; 19 | height: 30px; 20 | font-size: 18px; 21 | border-radius: 15px; 22 | border-color: grey; 23 | border-width: 1px; 24 | ` 25 | 26 | const config = { 27 | page_size: 100, 28 | length_menu: [ 100, 500, 1000 ], 29 | no_data_text: 'No data available!', 30 | sort: { column: "playID", order: "desc" }, 31 | key_column: "playID" 32 | } 33 | 34 | export function TopshotPlays() { 35 | const [error, setError] = useState(null) 36 | 37 | // used to chek the reload, so another reload is not triggered while the previous is still running 38 | // const [done, setDone] = useState(false) 39 | 40 | const [manualReloadDone, setManualReloadDone] = useState(true) 41 | 42 | const [topshotPlays, setTopshotPlays] = useState(null) 43 | useEffect(() => { 44 | load() 45 | .catch(() => setError(true)) 46 | }, []) 47 | 48 | // for reloading 49 | // disable auto refresh for now 50 | // useEffect(() => { 51 | // if(done){ 52 | // // set some delay 53 | // const timer = setTimeout(()=>{ 54 | // load() 55 | // .catch((e)=>{ 56 | // setDone(true) // enable reloading again for failed reload attempts 57 | // }) 58 | // }, 5000) 59 | // return () => clearTimeout(timer); 60 | // } 61 | // }, [done]); 62 | 63 | const load = () => { 64 | // setDone(false) 65 | return getTopShotPlays() 66 | .then((d) => { 67 | console.log(d) 68 | setTopshotPlays(d) 69 | // setDone(true) 70 | }) 71 | } 72 | 73 | const handleManualReload = () => { 74 | setManualReloadDone(false) 75 | load() 76 | .then(()=>{ 77 | setManualReloadDone(true) 78 | }) 79 | .catch((err)=>{ 80 | setManualReloadDone(true) 81 | // Do we need to show them the error on manual reloadf? 82 | console.log(`An error occured while reloading err: ${err}`); 83 | }) 84 | 85 | } 86 | 87 | if (error != null) 88 | return ( 89 | 90 | 91 |

92 | TopShot: 93 | 0x0b2a3299cc857e29 94 |

95 |

96 | Error Fetching TopShot Info: {error} 97 |

98 |
99 |
100 | ) 101 | if (topshotPlays == null) 102 | return ( 103 | 104 |

105 | Fetching Plays... 106 |

107 |
108 | ) 109 | 110 | var columns_found = { 'playID': true }; // add playID to columns found since it's in metadata 111 | topshotPlays.plays.forEach(play => { // get all possible column keys (names) in metadata 112 | for (var key in play.metadata) { 113 | columns_found[key] = true; 114 | } 115 | }); 116 | columns_found = Object.keys(columns_found); // convert object to array, save only keys 117 | 118 | // preferred column order 119 | const columns_order = [ 120 | 'playID', 121 | 'FullName', 122 | 'DateOfMomentLocal', 123 | 'PlayType', 124 | 'PlayCategory', 125 | 'TeamAtMoment', 126 | 'TeamAtMomentNBAID', 127 | 'HomeTeamName', // game details 128 | 'HomeTeamScore', 129 | 'AwayTeamName', 130 | 'AwayTeamScore', 131 | 'Outcome', 132 | 'NbaSeason', 133 | 'TotalYearsExperience', 134 | 'PrimaryPosition', // play (info specific to play or point in time) 135 | 'PlayerPosition', 136 | 'JerseyNumber', 137 | 'DraftYear', 138 | 'DraftRound', 139 | 'DraftSelection', 140 | 'DraftTeam', 141 | 'Birthdate', 142 | 'Birthplace', 143 | 'Height', 144 | 'Weight', 145 | 'CurrentTeam', // optional and infrequent 146 | 'CurrentTeamID', 147 | ]; 148 | 149 | const columns_extra = columns_found.filter(value => !columns_order.includes(value)); 150 | var columns_ordered = columns_order.concat(columns_extra); // append additional columns not initially found in columns_order 151 | 152 | var columns_exclude = ['LastName', 'FirstName', 'DateOfMoment', 'Tagline']; // columns to exclude 153 | columns_ordered = columns_ordered.filter(value => !columns_exclude.includes(value)); 154 | 155 | console.log(columns_ordered); 156 | 157 | var columns = []; 158 | columns_ordered.forEach(column_key => { 159 | columns.push({ 160 | key: column_key, 161 | text: column_key, 162 | align: "left", 163 | sortable: true 164 | }); 165 | }); 166 | 167 | var data = []; 168 | topshotPlays.plays.forEach(play => { 169 | var metadata = play.metadata; 170 | for (let key in metadata) { 171 | if (metadata[key] === '' || metadata[key] === 'N/A') { // remove invalid on-chain values OR set N/A to empty for format consistency 172 | metadata[key] = ''; 173 | } 174 | } 175 | 176 | const fix_keys = ['HomeTeamScore', 'AwayTeamScore', 'DraftYear', 'Weight'] // fix the associated value if equal to 0 177 | fix_keys.forEach(key => { 178 | if (parseInt(metadata[key]) === 0) { 179 | metadata[key] = ''; 180 | } 181 | }); 182 | 183 | if (metadata.Birthplace) { // fix inconsistent formatting of Birthplace 184 | metadata.Birthplace = metadata.Birthplace.split(',').map(item => item.trim()).filter(item => item).join(', '); 185 | } 186 | 187 | metadata.playID = play.playID; 188 | 189 | let date_options = { 190 | year: 'numeric', 191 | month: '2-digit', 192 | day: '2-digit', 193 | hour: '2-digit', 194 | minute: '2-digit', 195 | second: '2-digit' 196 | }; 197 | 198 | if (metadata.DateOfMoment) { 199 | metadata.DateOfMomentLocal = new Date(metadata.DateOfMoment).toLocaleString(undefined, date_options); 200 | } 201 | 202 | metadata.Outcome = (metadata.HomeTeamName === metadata.TeamAtMoment) 203 | ? (parseInt(metadata.HomeTeamScore) > parseInt(metadata.AwayTeamScore) ? 'Home Win' : 'Home Loss') 204 | : (parseInt(metadata.HomeTeamScore) > parseInt(metadata.AwayTeamScore) ? 'Away Loss' : 'Away Win'); 205 | 206 | data.push(metadata); 207 | }); 208 | 209 | return ( 210 | 211 |

212 | Plays 213 | 214 |

215 |
216 | {topshotPlays && ( 217 |
218 | 224 |
225 | )} 226 |
227 |
228 | ) 229 | } 230 | -------------------------------------------------------------------------------- /src/pages/set.comp.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect, useCallback} from "react" 2 | import {useParams} from "react-router-dom" 3 | import styled from "styled-components" 4 | 5 | import ReactDatatable from '@ashvin27/react-datatable' 6 | 7 | import * as fcl from "@onflow/fcl" 8 | const Red = styled.span` 9 | color: red; 10 | ` 11 | const Green = styled.span` 12 | color: green; 13 | ` 14 | const Muted = styled.span` 15 | color: #78899a; 16 | ` 17 | 18 | const Button = styled.button` 19 | margin-left: 20px; 20 | height: 30px; 21 | font-size: 18px; 22 | border-radius: 15px; 23 | border-color: grey; 24 | border-width: 1px; 25 | ` 26 | 27 | const getTopshotSet = async (setID) => { 28 | const resp = await fcl.send([ 29 | fcl.script` 30 | import TopShot from 0x${window.topshotAddress} 31 | access(all) struct Edition { 32 | access(all) let playID: UInt32 33 | access(all) let retired: Bool 34 | access(all) let momentCount: UInt32 35 | access(all) let playOrder: UInt32 36 | init(playID: UInt32, retired: Bool, momentCount: UInt32, playOrder: UInt32) { 37 | self.playID = playID 38 | self.retired = retired 39 | self.momentCount = momentCount 40 | self.playOrder = playOrder 41 | } 42 | } 43 | access(all) struct Set { 44 | access(all) let id: UInt32 45 | access(all) let setName: String 46 | access(all) let playIDs: [UInt32] 47 | access(all) let editions: [Edition] 48 | access(all) let locked: Bool 49 | access(all) let series: UInt32 50 | init(id: UInt32, setName: String) { 51 | self.id = id 52 | self.setName = setName 53 | var setData = TopShot.getSetData(setID: id)! 54 | self.playIDs = setData.getPlays() 55 | self.locked = setData.locked 56 | self.series = setData.series 57 | var editions: [Edition] = [] 58 | var playOrder = UInt32(1) 59 | 60 | for playID in self.playIDs { 61 | var retired = false 62 | var retiredEditions = setData.getRetired() 63 | retired = retiredEditions[playID]! 64 | var momentCount = UInt32(0) 65 | var numberMintedPerPlay = setData.getNumberMintedPerPlay() 66 | momentCount = numberMintedPerPlay[playID]! 67 | editions.append(Edition(playID: playID, retired: retired, momentCount: momentCount, playOrder: playOrder)) 68 | playOrder = playOrder + UInt32(1) 69 | } 70 | self.editions = editions 71 | } 72 | } 73 | access(all) struct MyPlay { 74 | access(all) let playID: UInt32 75 | access(all) let metadata: {String:String} 76 | 77 | init(playID: UInt32, metadata: {String:String}) { 78 | self.playID = playID 79 | self.metadata = metadata 80 | } 81 | } 82 | 83 | access(all) struct TopshotSet { 84 | access(all) let set: Set 85 | access(all) let plays: [MyPlay] 86 | 87 | init() { 88 | var setName = TopShot.getSetName(setID: ${setID}) 89 | self.set = Set(id: ${setID}, setName: setName!) 90 | let sd = TopShot.QuerySetData(setID: ${setID})! 91 | self.plays = [] 92 | for playID in sd.getPlays() { 93 | let md = TopShot.getPlayMetaData(playID: playID)! 94 | self.plays.append(MyPlay(playID: playID, metadata: md)) 95 | } 96 | } 97 | } 98 | access(all) fun main(): TopshotSet { 99 | return TopshotSet() 100 | } `, 101 | ]) 102 | return fcl.decode(resp) 103 | } 104 | const Root = styled.div` 105 | font-size: 13px; 106 | padding: 21px; 107 | ` 108 | 109 | const columns = [ 110 | { 111 | key: "playOrder", 112 | text: "Creation Order", 113 | align: "left", 114 | sortable: true, 115 | }, 116 | { 117 | key: "playID", 118 | text: "Play ID", 119 | align: "left", 120 | sortable: true, 121 | }, 122 | { 123 | key: "retired", 124 | text: "Retired", 125 | align: "left", 126 | sortable: true, 127 | }, 128 | { 129 | key: "fullName", 130 | text: "Full Name", 131 | align: "left", 132 | sortable: true 133 | }, 134 | { 135 | key: "playType", 136 | text: "Play Type", 137 | sortable: true 138 | }, 139 | { 140 | key: "playCategory", 141 | text: "Play Category", 142 | sortable: true 143 | }, 144 | { 145 | key: "totalMinted", 146 | text: "Total Minted", 147 | align: "left", 148 | sortable: true 149 | }, 150 | ]; 151 | 152 | const config = { 153 | page_size: 10, 154 | length_menu: [ 10, 20, 50 ], 155 | no_data_text: 'No data available!', 156 | sort: { column: "playOrder", order: "desc" }, 157 | key_column: "playID" 158 | } 159 | 160 | export function TopshotSet() { 161 | const [error, setError] = useState(null) 162 | const {setID} = useParams() 163 | const [TopshotSet, setTopshotSet] = useState(null) 164 | 165 | // used to chek the reload, so another reload is not triggered while the previous is still running 166 | const [done, setDone] = useState(false) 167 | 168 | const [manualReloadDone, setManualReloadDone] = useState(true) 169 | 170 | const load = useCallback(() => { 171 | setDone(false) 172 | return getTopshotSet(setID).then( 173 | (topshotSet) => { 174 | console.log(topshotSet) 175 | setTopshotSet(topshotSet) 176 | setDone(true) 177 | }) 178 | }, [setID]) 179 | 180 | useEffect(() => { 181 | load() 182 | .catch(setError) 183 | }, [setID, load]); 184 | 185 | // for reloading 186 | useEffect(() => { 187 | if(done){ 188 | // set some delay 189 | const timer = setTimeout(()=>{ 190 | load() 191 | .catch((e)=>{ 192 | setDone(true) // enable reloading again for failed reload attempts 193 | }) 194 | }, 5000) 195 | return () => clearTimeout(timer); 196 | } 197 | }, [done, load]); 198 | 199 | const handleManualReload = () => { 200 | setManualReloadDone(false) 201 | load() 202 | .then(()=>{ 203 | setManualReloadDone(true) 204 | }) 205 | .catch((err)=>{ 206 | setManualReloadDone(true) 207 | // Do we need to show them the error on manual reloadf? 208 | console.log(`An error occured while reloading err: ${err}`); 209 | }) 210 | 211 | } 212 | 213 | const getPlay = (playID) => { 214 | return ( 215 | TopshotSet && 216 | TopshotSet.plays.filter((play) => { 217 | return play.playID === playID 218 | }) 219 | ) 220 | } 221 | if (error != null) 222 | return ( 223 | 224 |

225 | Could NOT fetch info for: {setID} 226 |

227 |
228 | ) 229 | 230 | if (TopshotSet == null) 231 | return ( 232 | 233 |

234 | Fetching Set: {setID} 235 |

236 |
237 | ) 238 | 239 | const data = TopshotSet.set.editions?.map((edition) => { 240 | var play = getPlay(edition.playID)[0] 241 | return {playID: play.playID, retired: edition.retired ? retired : open, fullName: play.metadata.FullName, 242 | playType: play.metadata.PlayType, playCategory: play.metadata.PlayCategory, totalMinted: edition.momentCount, playOrder: edition.playOrder} 243 | }) 244 | return ( 245 | 246 |

247 | {TopshotSet.set.setName} S{TopshotSet.set.series}: 248 | {TopshotSet.set.locked ? locked set : open set} 249 | 250 |

251 | 257 |
258 | ) 259 | } 260 | -------------------------------------------------------------------------------- /src/pages/account.comp.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect, useCallback} from "react" 2 | import {useParams} from "react-router-dom" 3 | import {withPrefix, sansPrefix} from "../util/address.util" 4 | import * as fcl from "@onflow/fcl" 5 | import styled from "styled-components" 6 | import {Table} from "react-bootstrap" 7 | import ReactPaginate from "react-paginate" 8 | import * as t from "@onflow/types" 9 | 10 | const getAccount = async (address) => { 11 | const resp = await fcl.send([fcl.getAccount(sansPrefix(address))]) 12 | return fcl.decode(resp) 13 | } 14 | 15 | const getTopshotAccount = async (address) => { 16 | const resp = await fcl.send([ 17 | fcl.script` 18 | import TopShot from 0x${window.topshotAddress} 19 | import Market from 0x${window.topshotMarketAddress} 20 | access(all) struct TopshotAccount { 21 | access(all) var momentIDs: [UInt64] 22 | access(all) var saleMomentIDs: [UInt64] 23 | access(all) var hasV3: Bool 24 | init(momentIDs: [UInt64], saleMomentIDs: [UInt64], hasV3: Bool) { 25 | self.momentIDs = momentIDs 26 | self.saleMomentIDs = saleMomentIDs 27 | self.hasV3 = hasV3! 28 | } 29 | } 30 | access(all) fun main(): TopshotAccount { 31 | let acct = getAccount(0x${address}) 32 | let collectionRef = acct.capabilities.borrow<&{TopShot.MomentCollectionPublic}>(/public/MomentCollection)! 33 | let momentIDs = collectionRef.getIDs() 34 | var saleMomentIDs: [UInt64] = [] 35 | var hasV3: Bool = false 36 | if let marketV3CollectionRef = acct.capabilities.borrow<&{Market.SalePublic}>(/public/topshotSalev3Collection) { 37 | saleMomentIDs = marketV3CollectionRef.getIDs() 38 | hasV3 = true 39 | } else { 40 | let saleCollectionRef = acct.capabilities.borrow<&{Market.SalePublic}>(/public/topshotSaleCollection) ?? panic("Could not borrow capability from public collection") 41 | saleMomentIDs = saleCollectionRef.getIDs() 42 | } 43 | 44 | return TopshotAccount(momentIDs: momentIDs, saleMomentIDs: saleMomentIDs, hasV3: hasV3) 45 | } `, 46 | ]) 47 | return fcl.decode(resp) 48 | } 49 | 50 | const getMoments = async (address, momentIDs) => { 51 | if (momentIDs && momentIDs.length === 0) { 52 | return [] 53 | } 54 | const resp = await fcl.send([ 55 | fcl.script` 56 | import TopShot from 0x${window.topshotAddress} 57 | access(all) struct Moment { 58 | access(all) var id: UInt64? 59 | access(all) var playId: UInt32? 60 | access(all) var meta: &TopShot.MomentData? 61 | access(all) var play: {String: String}? 62 | access(all) var setId: UInt32? 63 | access(all) var setName: String? 64 | access(all) var serialNumber: UInt32? 65 | init(_ moment: &TopShot.NFT?) { 66 | self.id = moment?.id 67 | self.meta = moment?.data 68 | self.playId = moment?.data?.playID 69 | self.play = nil 70 | self.play = TopShot.getPlayMetaData(playID: self.playId!) 71 | self.setId = moment?.data?.setID 72 | self.setName = nil 73 | self.setName = TopShot.getSetName(setID: self.setId!) 74 | self.serialNumber = nil 75 | self.serialNumber = moment?.data?.serialNumber 76 | } 77 | } 78 | access(all) fun main(momentIDs: [UInt64]): [Moment] { 79 | let acct = getAccount(0x${address}) 80 | var moments: [Moment] = [] 81 | if let collectionRef = acct.capabilities.borrow<&{TopShot.MomentCollectionPublic}>(/public/MomentCollection) { 82 | for momentID in momentIDs { 83 | moments.append(Moment(collectionRef.borrowMoment(id: momentID))) 84 | } 85 | } 86 | return moments 87 | } `, 88 | fcl.args([fcl.arg(momentIDs, t.Array(t.UInt64))]), 89 | ]) 90 | return fcl.decode(resp) 91 | } 92 | 93 | const getListings = async (address, saleMomentIDs, useV3) => { 94 | if (saleMomentIDs && saleMomentIDs.length === 0) { 95 | return [] 96 | } 97 | var collectionPath = "topshotSaleCollection" 98 | if (useV3) { 99 | collectionPath = "topshotSalev3Collection" 100 | } 101 | 102 | const resp = await fcl.send([ 103 | fcl.script` 104 | import TopShot from 0x${window.topshotAddress} 105 | import Market from 0x${window.topshotMarketAddress} 106 | access(all) struct SaleMoment { 107 | access(all) var id: UInt64? 108 | access(all) var playId: UInt32? 109 | access(all) var meta: &TopShot.MomentData? 110 | access(all) var play: {String: String}? 111 | access(all) var setId: UInt32? 112 | access(all) var setName: String? 113 | access(all) var serialNumber: UInt32? 114 | access(all) var price: UFix64 115 | init(moment: &TopShot.NFT?, price: UFix64) { 116 | self.id = moment?.id 117 | self.meta = moment?.data 118 | self.playId = moment?.data?.playID 119 | self.play = nil 120 | self.play = TopShot.getPlayMetaData(playID: self.playId!) 121 | self.setId = moment?.data?.setID 122 | self.setName = nil 123 | self.setName = TopShot.getSetName(setID: self.setId!) 124 | self.serialNumber = nil 125 | self.serialNumber = moment?.data?.serialNumber 126 | self.price = price 127 | } 128 | } 129 | 130 | access(all) fun main(momentIDs: [UInt64]): [SaleMoment] { 131 | let acct = getAccount(0x${address}) 132 | let collectionRef = acct.capabilities.borrow<&{Market.SalePublic}>(/public/${collectionPath}) ?? panic("Could not borrow capability from public collection") 133 | var saleMoments: [SaleMoment] = [] 134 | for momentID in momentIDs { 135 | saleMoments.append(SaleMoment(moment: collectionRef.borrowMoment(id: momentID),price: collectionRef.getPrice(tokenID: momentID)!)) 136 | } 137 | return saleMoments 138 | } 139 | `, 140 | fcl.args([fcl.arg(saleMomentIDs, t.Array(t.UInt64))]), 141 | ]) 142 | return fcl.decode(resp) 143 | } 144 | 145 | const Root = styled.div` 146 | font-family: monospace; 147 | color: #233445; 148 | font-size: 13px; 149 | padding: 21px; 150 | ` 151 | 152 | const Muted = styled.span` 153 | color: #78899a; 154 | ` 155 | 156 | const H1 = styled.h1`` 157 | 158 | const Button = styled.button` 159 | margin-left: 20px; 160 | height: 30px; 161 | font-size: 18px; 162 | border-radius: 15px; 163 | border-color: grey; 164 | border-width: 1px; 165 | ` 166 | 167 | const Input = styled.input` 168 | margin-left: 20px; 169 | height: 30px; 170 | font-size: 18px; 171 | border-radius: 15px; 172 | border-color: grey; 173 | border-width: 1px; 174 | padding-left: 5px; 175 | ` 176 | 177 | const Span = styled.span` 178 | margin-left: 20px; 179 | font-size: 18px; 180 | ` 181 | 182 | 183 | export function Account() { 184 | const {address} = useParams() 185 | const [acct, setAcct] = useState(null) 186 | const [error, setError] = useState(null) 187 | const [momentError, setMomentError] = useState(null) 188 | const [listingError, setListingError] = useState(null) 189 | const [topshotAccount, setTopShotAccount] = useState(null) 190 | const [hasV3, setHasV3] = useState(null) 191 | 192 | const [momentIDs, setMomentIDs] = useState([]) 193 | const [saleMomentIDs, setSaleMomentIDs] = useState([]) 194 | 195 | // used to chek the reload, so another reload is not triggered while the previous is still running 196 | const [done, setDone] = useState(false) 197 | 198 | const [manualReloadDone, setManualReloadDone] = useState(true) 199 | 200 | const load = useCallback(() => { 201 | setDone(false) 202 | return getTopshotAccount(address) 203 | .then((d) => { 204 | console.log(d) 205 | setTopShotAccount(d) 206 | setMomentIDs(d.momentIDs.slice(0, 20)) 207 | setSaleMomentIDs(d.saleMomentIDs.slice(0, 20)) 208 | setDone(true) 209 | setHasV3(d.hasV3) 210 | }) 211 | }, [address]) 212 | 213 | useEffect(() => { 214 | load() 215 | .catch(setError) 216 | }, [address, load]) 217 | 218 | useEffect(() => { 219 | getAccount(address).then((a) => { 220 | setAcct(a) 221 | }).catch(setError) 222 | }, [address]) 223 | 224 | const [moments, setMoments] = useState(null) 225 | useEffect(() => { 226 | getMoments(address, momentIDs) 227 | .then((m) => { 228 | setMoments(m) 229 | }) 230 | .catch(setMomentError) 231 | }, [address, momentIDs]) 232 | 233 | const [listings, setListings] = useState(null) 234 | useEffect(() => { 235 | getListings(address, saleMomentIDs, topshotAccount ? topshotAccount.hasV3:false) 236 | .then((l) => { 237 | setListings(l) 238 | }) 239 | .catch(setListingError) 240 | }, [address, saleMomentIDs, topshotAccount]) 241 | 242 | // for reloading 243 | useEffect(() => { 244 | if(done){ 245 | // set some delay 246 | const timer = setTimeout(()=>{ 247 | load() 248 | .catch((e)=>{ 249 | setDone(true) // enable reloading again for failed reload attempts 250 | }) 251 | }, 5000) 252 | return () => clearTimeout(timer); 253 | } 254 | }, [done, load]); 255 | 256 | const handleManualReload = () => { 257 | setManualReloadDone(false) 258 | load() 259 | .then(()=>{ 260 | setManualReloadDone(true) 261 | }) 262 | .catch((err)=>{ 263 | setManualReloadDone(true) 264 | // Do we need to show them the error on manual reloadf? 265 | console.log(`An error occured while reloading err: ${err}`); 266 | }) 267 | } 268 | 269 | const handlePageClick = function (data) { 270 | let mIDs = topshotAccount.momentIDs.slice(data.selected * 20, data.selected * 20 + 20) 271 | setMomentIDs(mIDs) 272 | } 273 | 274 | const handleSalePageClick = function (data) { 275 | let mIDs = topshotAccount.saleMomentIDs.slice(data.selected * 20, data.selected * 20 + 20) 276 | setSaleMomentIDs(mIDs) 277 | } 278 | 279 | // add search 280 | const [searchMomentID, setSearchMomentID] = useState(null) 281 | const [searchListingID, setSearchListingID] = useState(null) 282 | 283 | const handleSearchMomentChange = (e) => { 284 | setSearchMomentID(e.target.value) 285 | } 286 | 287 | const handleSearchListingChange = (e) => { 288 | setSearchListingID(e.target.value) 289 | } 290 | 291 | const handleSearchMoment = ()=>{ 292 | setMomentError(null) 293 | if(searchMomentID === null || searchMomentID === ""){ 294 | setMomentIDs(topshotAccount.momentIDs.slice(0, 20)) 295 | setDone(true) // continue real-time update 296 | return 297 | } 298 | setDone(false) // stop real-time update 299 | let value = parseInt(searchMomentID) 300 | //search the list of momentIDs 301 | if(!topshotAccount.momentIDs.includes(value)){ 302 | setMomentError("") 303 | setMoments([]) 304 | return 305 | } 306 | setMomentIDs([value]) 307 | } 308 | const handleSearchListing = ()=>{ 309 | setListingError(null) 310 | if(searchListingID === null || searchListingID === ""){ 311 | setSaleMomentIDs(topshotAccount.saleMomentIDs.slice(0, 20)) 312 | setDone(true) // continue real-time update 313 | return 314 | } 315 | setDone(false) // stop real-time update 316 | let value = parseInt(searchListingID) 317 | //search the list of saleMomentIDs 318 | if(!topshotAccount.saleMomentIDs.includes(value)){ 319 | setListingError("") 320 | setListings([]) 321 | return 322 | } 323 | setSaleMomentIDs([value]) 324 | } 325 | 326 | 327 | if (error != null) 328 | return ( 329 | 330 |

331 | Account: 332 | {withPrefix(address)} 333 |

334 |

335 | Could NOT fetch info for: 336 | {withPrefix(address)} 337 |

338 | 341 |
342 | ) 343 | if (acct == null) 344 | return ( 345 | 346 |

347 | Account: 348 | {withPrefix(address)} 349 |

350 |

351 | Fetching info for: 352 | {withPrefix(address)} 353 |

354 |
355 | ) 356 | return ( 357 | 358 |

359 | Account: 360 | {withPrefix(acct.address)} 361 | 362 |

363 |
364 |

365 | Moments 366 | {topshotAccount && topshotAccount.momentIDs.length} 367 | Search By ID: 368 | 369 | 370 |

371 | {/* */} 372 | { 373 | momentError != null &&
    374 |
  • MomentID doesn't exist for user
  • 375 |
376 | } 377 | {moments && !!moments.length && momentError == null && ( 378 |
379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | {moments 390 | .sort((a, b) => { 391 | return a.setName === b.setName ? 0 : +(a.setName > b.setName) || -1 392 | }) 393 | .map((moment) => { 394 | return ( 395 | 396 | 397 | 398 | 399 | 400 | 401 | ) 402 | })} 403 | 404 |
moment idset nameplayer full nameserial number
{moment.id}{moment.setName}{moment.play.FullName}{moment.serialNumber}
405 | "} 408 | breakLabel={...} 409 | breakClassName={"page-item"} 410 | pageClassName="page-item" 411 | previousClassName="page-item" 412 | nextClassName="page-item" 413 | pageLinkClassName="page-link" 414 | previousLinkClassName="page-link" 415 | nextLinkClassName="page-link" 416 | pageCount={momentIDs.length === 1 ? 1 : topshotAccount ? topshotAccount.momentIDs.length / 20 : 0} 417 | marginPagesDisplayed={2} 418 | pageRangeDisplayed={5} 419 | onPageChange={handlePageClick} 420 | containerClassName={"pagination"} 421 | subContainerClassName={"pages pagination"} 422 | activeClassName={"active"} 423 | /> 424 |
425 | )} 426 |
427 |
428 |

429 | Listings ({hasV3 ? "market v3" : "market v1"}) 430 | {topshotAccount && topshotAccount.saleMomentIDs.length} 431 | Search By ID: 432 | 433 | 434 |

435 | { 436 | listingError != null &&
    437 |
  • SaleMomentID doesn't exist for user
  • 438 |
439 | } 440 | {listings && !!listings.length && listingError == null && ( 441 |
442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | {listings 454 | .sort((a, b) => { 455 | return a.setName === b.setName ? 0 : +(a.setName > b.setName) || -1 456 | }) 457 | .map((listing) => { 458 | return ( 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | ) 467 | })} 468 | 469 |
moment idset nameplayer full nameserial numberprice
{listing.id}{listing.setName}{listing.play.FullName}{listing.serialNumber}{listing.price}
470 | "} 473 | breakLabel={...} 474 | breakClassName={"page-item"} 475 | pageClassName="page-item" 476 | previousClassName="page-item" 477 | nextClassName="page-item" 478 | pageLinkClassName="page-link" 479 | previousLinkClassName="page-link" 480 | nextLinkClassName="page-link" 481 | pageCount={saleMomentIDs.length === 1 ? 1 : topshotAccount ? topshotAccount.saleMomentIDs.length / 20 : 0} 482 | marginPagesDisplayed={2} 483 | pageRangeDisplayed={5} 484 | onPageChange={handleSalePageClick} 485 | containerClassName={"pagination"} 486 | subContainerClassName={"pages pagination"} 487 | activeClassName={"active"} 488 | /> 489 |
490 | )} 491 |
492 |
493 | ) 494 | } --------------------------------------------------------------------------------