├── 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 |
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 |
339 | - This probably means it doesn't exist
340 |
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 | | moment id |
383 | set name |
384 | player full name |
385 | serial number |
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 | | {moment.id} |
397 | {moment.setName} |
398 | {moment.play.FullName} |
399 | {moment.serialNumber} |
400 |
401 | )
402 | })}
403 |
404 |
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 | | moment id |
446 | set name |
447 | player full name |
448 | serial number |
449 | price |
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 | | {listing.id} |
461 | {listing.setName} |
462 | {listing.play.FullName} |
463 | {listing.serialNumber} |
464 | {listing.price} |
465 |
466 | )
467 | })}
468 |
469 |
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 | }
--------------------------------------------------------------------------------