Frontend prettified
@ -17,7 +17,7 @@
|
|||||||
"web3": "^1.6.0"
|
"web3": "^1.6.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "DISABLE_ESLINT_PLUGIN=true react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Container } from './components/generic'
|
import Container from './components/atoms/Container'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { HashRouter} from 'react-router-dom'
|
import { HashRouter} from 'react-router-dom'
|
||||||
import Router from './components/router'
|
import Router from './components/router'
|
||||||
@ -24,16 +24,11 @@ function App() {
|
|||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
// Rendering
|
// Rendering
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
if( error || loading ) return <Theme>
|
|
||||||
<Container>
|
|
||||||
<p>{ error || loading }</p>
|
|
||||||
</Container>
|
|
||||||
</Theme>
|
|
||||||
|
|
||||||
return <Theme>
|
return <Theme>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
|
|
||||||
<Router />
|
{ error || loading ? <Container> <p>{ error || loading }</p> </Container> : <Router /> }
|
||||||
|
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
</Theme>
|
</Theme>
|
||||||
|
1
minter/src/assets/account-circle-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 2c5.52 0 10 4.48 10 10s-4.48 10-10 10S2 17.52 2 12 6.48 2 12 2zM6.023 15.416C7.491 17.606 9.695 19 12.16 19c2.464 0 4.669-1.393 6.136-3.584A8.968 8.968 0 0 0 12.16 13a8.968 8.968 0 0 0-6.137 2.416zM12 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></svg>
|
After Width: | Height: | Size: 374 B |
1
minter/src/assets/account-circle-line.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-4.987-3.744A7.966 7.966 0 0 0 12 20c1.97 0 3.773-.712 5.167-1.892A6.979 6.979 0 0 0 12.16 16a6.981 6.981 0 0 0-5.147 2.256zM5.616 16.82A8.975 8.975 0 0 1 12.16 14a8.972 8.972 0 0 1 6.362 2.634 8 8 0 1 0-12.906.187zM12 13a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></svg>
|
After Width: | Height: | Size: 495 B |
10
minter/src/assets/discord-logo-black.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg width="71" height="55" viewBox="0 0 71 55" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0)">
|
||||||
|
<path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" fill="#23272A"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0">
|
||||||
|
<rect width="71" height="55" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
1
minter/src/assets/door-closed-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0H24V24H0z"/><path d="M3 21v-2h2V4c0-.552.448-1 1-1h12c.552 0 1 .448 1 1v15h2v2H3zm12-10h-2v2h2v-2z"/></svg>
|
After Width: | Height: | Size: 215 B |
1
minter/src/assets/hand-coin-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M9.33 11.5h2.17A4.5 4.5 0 0 1 16 16H8.999L9 17h8v-1a5.578 5.578 0 0 0-.886-3H19a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.761 0-5.1-.59-7-1.625L6 10.071A6.967 6.967 0 0 1 9.33 11.5zM5 19a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9zM18 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6zm-7-3a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"/></svg>
|
After Width: | Height: | Size: 471 B |
1
minter/src/assets/home-2-line.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M19 21H5a1 1 0 0 1-1-1v-9H1l10.327-9.388a1 1 0 0 1 1.346 0L23 11h-3v9a1 1 0 0 1-1 1zM6 19h12V9.157l-6-5.454-6 5.454V19z"/></svg>
|
After Width: | Height: | Size: 257 B |
1
minter/src/assets/pie-chart-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 2.05V13h10.95c-.501 5.053-4.765 9-9.95 9-5.523 0-10-4.477-10-10 0-5.185 3.947-9.449 9-9.95zm2 0A10.003 10.003 0 0 1 21.95 11H13V2.05z"/></svg>
|
After Width: | Height: | Size: 275 B |
1
minter/src/assets/rocket-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5.33 15.929A13.064 13.064 0 0 1 5 13c0-5.088 2.903-9.436 7-11.182C16.097 3.564 19 7.912 19 13c0 1.01-.114 1.991-.33 2.929l2.02 1.796a.5.5 0 0 1 .097.63l-2.458 4.096a.5.5 0 0 1-.782.096l-2.254-2.254a1 1 0 0 0-.707-.293H9.414a1 1 0 0 0-.707.293l-2.254 2.254a.5.5 0 0 1-.782-.096l-2.458-4.095a.5.5 0 0 1 .097-.631l2.02-1.796zM12 13a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></svg>
|
After Width: | Height: | Size: 496 B |
@ -1,18 +1,34 @@
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
const DynamicButton = ( { to, ...props } ) => to ? <link { ...props } to={ to } /> : <button { ...props } />
|
const DynamicButton = ( { to='', onClick, ...props } ) => to && !to.includes( 'http' ) ? <Link { ...props } to={ to } /> : <button onClick={ onClick || ( () => window.open( to, '_blank' ).focus() ) } { ...props } />
|
||||||
|
|
||||||
|
|
||||||
export default styled( DynamicButton )`
|
const PrettyButton = styled( DynamicButton )`
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: ${ ( { direction='row' } ) => direction };
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px solid rgba( 0, 0, 0, .3 );
|
border: 1px solid ${ ( { theme } ) => theme.colors.text };
|
||||||
color: rgba( 0, 0, 0, .8 );
|
color: ${ ( { theme } ) => theme.colors.text };
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
padding: .5rem 1.1rem .5rem 1rem;
|
padding: .5rem 1.1rem .5rem 1rem;
|
||||||
margin-top: 1rem;
|
margin: 1rem .5rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 20px 2px rgb(0 0 0 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
& img {
|
||||||
|
height: 50px;
|
||||||
|
width: auto;
|
||||||
|
margin: ${ ( { direction='row' } ) => direction == 'row' ? '0 1rem 0 0' : '1rem' };
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export default ( { icon, ...props } ) => !icon ? <PrettyButton { ...props } /> : <PrettyButton { ...props }>
|
||||||
|
<img alt="Button icon" src={ icon } />
|
||||||
|
{ props.children }
|
||||||
|
</PrettyButton>
|
@ -1,7 +1,8 @@
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
import Home from '../../assets/home-2-line.svg'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
// Image that behaves like a background image
|
// Image that behaves like a background image
|
||||||
import LaunchBackground from '../../assets/undraw_launch_day_4e04.svg'
|
|
||||||
const BackgroundImage = styled.img.attrs( props => ( {
|
const BackgroundImage = styled.img.attrs( props => ( {
|
||||||
// src: LaunchBackground
|
// src: LaunchBackground
|
||||||
} ) )`
|
} ) )`
|
||||||
@ -21,7 +22,7 @@ const Wrapper = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: ${ ( { justify='flex-start' } ) => justify };
|
justify-content: ${ ( { justify='center' } ) => justify };
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: ${ ( { gutter=true } ) => gutter ? '0 max( 1rem, calc( 25vw - 4rem ) )' : 'none' };
|
padding: ${ ( { gutter=true } ) => gutter ? '0 max( 1rem, calc( 25vw - 4rem ) )' : 'none' };
|
||||||
@ -30,10 +31,27 @@ const Wrapper = styled.div`
|
|||||||
& * {
|
& * {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& #home {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// Container that always has the background image
|
// Container that always has the background image
|
||||||
export default ( { children, ...props } ) => <Wrapper { ...props }>
|
export default ( { children, ...props } ) => {
|
||||||
<BackgroundImage key='background' />
|
|
||||||
{ children }
|
const navigate = useNavigate()
|
||||||
</Wrapper>
|
|
||||||
|
return <Wrapper { ...props }>
|
||||||
|
<img id="home" onClick={ f => navigate( '/' ) } src={ Home } />
|
||||||
|
<BackgroundImage key='background' />
|
||||||
|
{ children }
|
||||||
|
</Wrapper>
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,7 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
export default styled.section`
|
export default styled.section`
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: ${ ( { gutter=false } ) => gutter ? '5rem max( 1rem, calc( 25vw - 4rem ) )' : '5rem 0' };
|
padding: ${ ( { topGutter=true, gutter=false } ) => gutter ? `${ topGutter ? '5rem' : '0' } max( 1rem, calc( 25vw - 4rem ) )` : `${ topGutter ? '5rem' : '0' } 0` };
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: ${ ( { direction } ) => direction || 'column' };
|
flex-direction: ${ ( { direction } ) => direction || 'column' };
|
||||||
width: ${ ( { width } ) => width || '100%' };
|
width: ${ ( { width } ) => width || '100%' };
|
||||||
|
@ -9,6 +9,8 @@ export const Text = styled.p`
|
|||||||
padding: ${ ( { banner } ) => banner ? '.5rem 1rem' : 'initial' };
|
padding: ${ ( { banner } ) => banner ? '.5rem 1rem' : 'initial' };
|
||||||
box-shadow: ${ ( { banner } ) => banner ? '0 0 20px 2px rgb(0 0 0 / 70%)' : '' };
|
box-shadow: ${ ( { banner } ) => banner ? '0 0 20px 2px rgb(0 0 0 / 70%)' : '' };
|
||||||
text-align: ${ ( { align } ) => align || 'left' };
|
text-align: ${ ( { align } ) => align || 'left' };
|
||||||
|
max-width: 90%;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
`
|
`
|
||||||
|
|
||||||
export const H1 = styled.h1`
|
export const H1 = styled.h1`
|
||||||
|
@ -12,11 +12,12 @@ export const Container = ( { className, children, ...props } ) => <main { ...pro
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
export const Loading = ( { message } ) => <Container>
|
export const Loading = ( { message, children } ) => <Container>
|
||||||
<div className="loading">
|
<div className="loading">
|
||||||
|
|
||||||
<div className="lds-dual-ring"></div>
|
<div className="lds-dual-ring"></div>
|
||||||
<p>{ message }</p>
|
<p>{ message }</p>
|
||||||
|
{ children }
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
58
minter/src/components/molecules/Input.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { useRef } from 'react'
|
||||||
|
|
||||||
|
const Input = styled.div`
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: ${ ( { type } ) => type == 'radio' ? 'row' : 'column' };
|
||||||
|
margin: 1rem 0;
|
||||||
|
justify-content: center;
|
||||||
|
width: 350px;
|
||||||
|
|
||||||
|
& input {
|
||||||
|
background: ${ ( { theme } ) => theme.colors.backdrop };
|
||||||
|
border: none;
|
||||||
|
border-left: 2px solid ${ ( { theme } ) => theme.colors.primary };
|
||||||
|
}
|
||||||
|
|
||||||
|
& input {
|
||||||
|
padding: 1rem 2rem 1rem 1rem;
|
||||||
|
width: ${ ( { type } ) => type == 'radio' ? 'auto' : '100%' };
|
||||||
|
}
|
||||||
|
|
||||||
|
& label {
|
||||||
|
opacity: .5;
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
display: flex;
|
||||||
|
width: ${ ( { type } ) => type == 'radio' ? 'auto' : '100%' };
|
||||||
|
color: ${ ( { theme } ) => theme.colors.text };
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: .9rem;
|
||||||
|
margin-left: auto;
|
||||||
|
font-style: normal;
|
||||||
|
background: ${ ( { theme } ) => theme.colors.hint };
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
export default ( { onChange, type, label, info, id, ...props } ) => {
|
||||||
|
|
||||||
|
const { current: internalId } = useRef( id || `input-${ Math.random() }` )
|
||||||
|
|
||||||
|
return <Input type={ type }>
|
||||||
|
|
||||||
|
{ label && <label htmlFor={ internalId }>{ label } { info && <span onClick={ f => alert( info ) }>?</span> }</label> }
|
||||||
|
<input data-testid={ internalId } { ...props } id={ internalId } onChange={ onChange } type={ type || 'text' } />
|
||||||
|
|
||||||
|
</Input>
|
||||||
|
|
||||||
|
}
|
@ -32,9 +32,10 @@ const Spinner = styled.div`
|
|||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
export default ( { message, ...props } ) => <Container justify="center" { ...props }>
|
export default ( { message, children, ...props } ) => <Container justify="center" { ...props }>
|
||||||
|
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{ message && <Text align="center">{ message }</Text> }
|
{ message && <Text align="center">{ message }</Text> }
|
||||||
|
{ children }
|
||||||
|
|
||||||
</Container>
|
</Container>
|
@ -1,10 +1,14 @@
|
|||||||
import { Container, Loading } from './generic'
|
import Container from '../atoms/Container'
|
||||||
import '../App.css'
|
import Section from '../atoms/Section'
|
||||||
|
import { H1, Text } from '../atoms/Text'
|
||||||
|
import Avatar from '../molecules/Avatar'
|
||||||
|
import Input from '../molecules/Input'
|
||||||
|
import Loading from '../molecules/Loading'
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { log } from '../modules/helpers'
|
import { log } from '../../modules/helpers'
|
||||||
import { useRocketeerImages, callApi } from '../modules/api'
|
import { useRocketeerImages, callApi } from '../../modules/api'
|
||||||
import { useAddress, useChainId, useBalanceOf, sign } from '../modules/web3'
|
import { useAddress, useChainId, sign } from '../../modules/web3'
|
||||||
|
|
||||||
|
|
||||||
export default function Verifier() {
|
export default function Verifier() {
|
||||||
@ -27,6 +31,9 @@ export default function Verifier() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
// Validate iput as eth1 address
|
||||||
|
if( !validatorAddress.match( /0x[a-zA-Z0-9]{40}/ ) ) throw new Error( `Please input a valid ETH1 address.` )
|
||||||
|
|
||||||
const confirmed = window.confirm( `This will assign Rocketeer ${ id } to ${ validatorAddress }.\n\nMetamask will ask you to sign a message, this is NOT A TRANSACTION.` )
|
const confirmed = window.confirm( `This will assign Rocketeer ${ id } to ${ validatorAddress }.\n\nMetamask will ask you to sign a message, this is NOT A TRANSACTION.` )
|
||||||
if( !confirmed ) throw new Error( `Operation cancelled` )
|
if( !confirmed ) throw new Error( `Operation cancelled` )
|
||||||
|
|
||||||
@ -75,36 +82,30 @@ export default function Verifier() {
|
|||||||
// Rendering
|
// Rendering
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
if( loading ) return <Loading message={ loading } />
|
if( loading ) return <Loading message={ loading } />
|
||||||
return <Container id="avatar" className={ rocketeers.length > 1 ? 'wide' : '' }>
|
return <Container>
|
||||||
|
|
||||||
<h1>Rocketeer avatar attribution</h1>
|
<H1>Rocketeer avatar attribution</H1>
|
||||||
|
|
||||||
<p>Input the address you want to assign the avatar to.</p>
|
<Text>Input the address you want to assign the avatar to.</Text>
|
||||||
<input type='text' onChange={ ( { target } ) => setValidatorAddress( target.value ) } value={ validatorAddress } />
|
<Input type='text' onChange={ ( { target } ) => setValidatorAddress( target.value ) } value={ validatorAddress } />
|
||||||
|
|
||||||
<p>Select the network you want to assign for:</p>
|
<Text>Select the network you want to assign for:</Text>
|
||||||
<div className="radios">
|
<Section topGutter={ false } direction="column">
|
||||||
<div className="row">
|
<Input label="Mainnet" onClick={ f => setNetwork( 'mainnet' ) } id="mainnet" type="radio" name="network" checked={ network == 'mainnet' }/>
|
||||||
<input onClick={ f => setNetwork( 'mainnet' ) } id="mainnet" type="radio" name="network" checked={ network == 'mainnet' }/>
|
<Input label="Testnet" onClick={ f => setNetwork( 'testnet' ) } id="testnet" type="radio" name="network" checked={ network == 'testnet' }/>
|
||||||
<label onClick={ f => setNetwork( 'mainnet' ) } for="mainnet">Mainnet</label>
|
</Section>
|
||||||
</div>
|
|
||||||
<div className="row">
|
|
||||||
<input onClick={ f => setNetwork( 'testnet' ) } id="testnet" type="radio" name="network" checked={ network == 'testnet' }/>
|
|
||||||
<label onClick={ f => setNetwork( 'testnet' ) } for="testnet">Testnet</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<p>Click the Rocketeer you want to assign to this address.</p>
|
<Text>Click the Rocketeer you want to assign to this address.</Text>
|
||||||
<div className="row">
|
<Section direction="row">
|
||||||
|
|
||||||
{ rocketeers.map( ( { id, src } ) => {
|
{ rocketeers.map( ( { id, src } ) => {
|
||||||
|
|
||||||
return <img key={ id } onClick={ f => attribute( id ) } className='rocketeer' src={ src } alt={ `Rocketeer number ${ id }` } />
|
return <Avatar key={ id } onClick={ f => attribute( id ) } src={ src } alt={ `Rocketeer number ${ id }` } />
|
||||||
|
|
||||||
} ) }
|
} ) }
|
||||||
|
|
||||||
</div>
|
</Section>
|
||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
}
|
}
|
@ -1,9 +1,24 @@
|
|||||||
import Fox from '../assets/metamask-fox.svg'
|
// Icons
|
||||||
import { Container, Loading } from './generic'
|
import Fox from '../../assets/metamask-fox.svg'
|
||||||
|
import Discord from '../../assets/discord-logo-black.svg'
|
||||||
|
import Mint from '../../assets/rocket-fill.svg'
|
||||||
|
import Avatar from '../../assets/account-circle-fill.svg'
|
||||||
|
import Outfits from '../../assets/door-closed-fill.svg'
|
||||||
|
import Portfolio from '../../assets/pie-chart-fill.svg'
|
||||||
|
|
||||||
|
// Functionality
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { log } from '../modules/helpers'
|
import { log } from '../../modules/helpers'
|
||||||
import { useAddress, getAddress } from '../modules/web3'
|
import { useAddress, getAddress } from '../../modules/web3'
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
|
// Visual
|
||||||
|
import Container from '../atoms/Container'
|
||||||
|
import { H1 } from '../atoms/Text'
|
||||||
|
import Button from '../atoms/Button'
|
||||||
|
import Section from '../atoms/Section'
|
||||||
|
|
||||||
|
|
||||||
|
import Loading from '../molecules/Loading'
|
||||||
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
@ -54,25 +69,20 @@ export default function ComponentName( ) {
|
|||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
|
|
||||||
// Loading component
|
// Loading component
|
||||||
if( loading ) return <Loading message={ loading } />
|
if( loading || error ) return <Loading message={ loading || error } />
|
||||||
|
|
||||||
// Error interface
|
|
||||||
if( error ) return <Container>
|
|
||||||
<p>{ error }</p>
|
|
||||||
</Container>
|
|
||||||
|
|
||||||
// Actions menu
|
// Actions menu
|
||||||
if( address ) return <Container>
|
if( address ) return <Container>
|
||||||
|
|
||||||
<h1>Rocketeer Tools</h1>
|
<H1>Rocketeer NFT Tools</H1>
|
||||||
|
|
||||||
<div>
|
<Section direction="row">
|
||||||
<Link className='button' to='/mint'>Mint Rocketeer</Link>
|
<Button direction="column" icon={ Mint } to='/mint'>Mint Rocketeer</Button>
|
||||||
<Link className='button' to='/portfolio'>View Rocketeer Portfolio</Link>
|
<Button direction="column" icon={ Portfolio } to='/portfolio'>Rocketeer Portfolio</Button>
|
||||||
<Link className='button' to='/outfits'>Use Changing Room</Link>
|
<Button direction="column" icon={ Outfits } to='/outfits'>Changing Room</Button>
|
||||||
<Link className='button' to='/verify'>Discord verify</Link>
|
<Button direction="column" icon={ Discord } to='/verify'>Discord verify</Button>
|
||||||
<Link className='button' to='/avatar'>Set address avatar</Link>
|
<Button direction="column" icon={ Avatar } to='/avatar'>Set node avatar</Button>
|
||||||
</div>
|
</Section>
|
||||||
|
|
||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
@ -80,11 +90,10 @@ export default function ComponentName( ) {
|
|||||||
// Login interface
|
// Login interface
|
||||||
return <Container>
|
return <Container>
|
||||||
|
|
||||||
<h1>Rocketeer Interface</h1>
|
<H1>Rocketeer Interface</H1>
|
||||||
<a className="button" href="/#" onClick={ metamasklogin }>
|
<Button icon={ Fox } onClick={ metamasklogin }>
|
||||||
<img alt="metamask fox" src={ Fox } />
|
|
||||||
Connect wallet
|
Connect wallet
|
||||||
</a>
|
</Button>
|
||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
|
|
@ -1,11 +1,15 @@
|
|||||||
import Fox from '../assets/metamask-fox.svg'
|
import Fox from '../../assets/metamask-fox.svg'
|
||||||
import { Container } from './generic'
|
|
||||||
import '../App.css'
|
import Container from '../atoms/Container'
|
||||||
|
import { H1, Text } from '../atoms/Text'
|
||||||
|
import Button from '../atoms/Button'
|
||||||
|
import Input from '../molecules/Input'
|
||||||
|
import Loading from '../molecules/Loading'
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
import { useAddress, useTotalSupply, useContract, useChainId } from '../modules/web3'
|
import { useAddress, useTotalSupply, useContract, useChainId } from '../../modules/web3'
|
||||||
import { log, setListenerAndReturnUnlistener } from '../modules/helpers'
|
import { log, setListenerAndReturnUnlistener } from '../../modules/helpers'
|
||||||
|
|
||||||
export default function Minter() {
|
export default function Minter() {
|
||||||
|
|
||||||
@ -78,21 +82,14 @@ export default function Minter() {
|
|||||||
// Rendering
|
// Rendering
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
|
|
||||||
if( error || loading ) return <Container>
|
if( error || loading ) return <Loading message={ error || loading }>
|
||||||
{ error && <p>{ error }</p> }
|
{ txHash && <Button to={ `https://${ chainId === '0x1' ? 'etherscan' : 'rinkeby.etherscan' }.io/tx/${ txHash }` }>View tx on Etherscan</Button> }
|
||||||
{ !error && loading && <div className="loading">
|
</Loading>
|
||||||
|
|
||||||
<div className="lds-dual-ring"></div>
|
|
||||||
<p>{ loading }</p>
|
|
||||||
{ txHash && <a className="button" rel='noreferrer' target="_blank" href={ `https://${ chainId === '0x1' ? 'etherscan' : 'rinkeby.etherscan' }.io/tx/${ txHash }` }>View tx on Etherscan</a> }
|
|
||||||
|
|
||||||
</div> }
|
|
||||||
</Container>
|
|
||||||
|
|
||||||
if( mintedTokenId ) return <Container>
|
if( mintedTokenId ) return <Container>
|
||||||
|
|
||||||
<h1>Minting Successful!</h1>
|
<H1>Minting Successful!</H1>
|
||||||
<a className="button" rel="noreferrer" target="_blank" alt="Link to opensea details of Rocketeer" href='/#/portfolio'>View your Rocketeers</a>
|
<Button to='/portfolio'>View your Rocketeers</Button>
|
||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
@ -100,15 +97,15 @@ export default function Minter() {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|
||||||
<h1>Rocketeer Minter</h1>
|
<H1>Rocketeer Minter</H1>
|
||||||
<p>We are ready to mint! There are currently { totalSupply } minted Rocketeers.</p>
|
<Text>We are ready to mint! There are currently { totalSupply } minted Rocketeers.</Text>
|
||||||
|
|
||||||
<label htmlFor='address'>Minting to:</label>
|
|
||||||
<input id='address' value={ address } disabled />
|
<Input label="Minting to:" id="address" value={ address } info="This is the currently selected address in your Metamask" disabled />
|
||||||
{ contract && <a className="button" href="/#" onClick={ mintRocketeer }>
|
|
||||||
<img alt="metamask fox" src={ Fox } />
|
{ contract && <Button icon={ Fox } onClick={ mintRocketeer }>
|
||||||
Mint new Rocketeer
|
Mint new Rocketeer
|
||||||
</a> }
|
</Button> }
|
||||||
|
|
||||||
|
|
||||||
</Container>
|
</Container>
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useRocketeers, callApi } from '../../modules/api'
|
import { useRocketeers, callApi } from '../../modules/api'
|
||||||
import { useChainId, useAddress, sign } from '../../modules/web3'
|
import { useChainId, useAddress, sign } from '../../modules/web3'
|
||||||
@ -164,7 +163,7 @@ export default function Verifier() {
|
|||||||
if( !rocketeers.length || loading ) return <Loading message={ loading || "Loading Rocketeers, please make sure you selected the right wallet" } />
|
if( !rocketeers.length || loading ) return <Loading message={ loading || "Loading Rocketeers, please make sure you selected the right wallet" } />
|
||||||
|
|
||||||
// Rocketeer selector
|
// Rocketeer selector
|
||||||
if(!rocketeer ) return <Container>
|
if(!rocketeer ) return <Container justify="flex-start">
|
||||||
|
|
||||||
<H1>Rocketeers</H1>
|
<H1>Rocketeers</H1>
|
||||||
<Text>Click on a Rocketeer to manage it's outfits</Text>
|
<Text>Click on a Rocketeer to manage it's outfits</Text>
|
||||||
@ -184,7 +183,7 @@ export default function Verifier() {
|
|||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
// Changing room
|
// Changing room
|
||||||
if( rocketeer ) return <Container gutter={ false }>
|
if( rocketeer ) return <Container justify="flex-start" gutter={ false }>
|
||||||
|
|
||||||
{ /* Header */ }
|
{ /* Header */ }
|
||||||
<Hero background={ rocketeer.image } gutter={ true } shadow={ true }>
|
<Hero background={ rocketeer.image } gutter={ true } shadow={ true }>
|
||||||
|
52
minter/src/components/organisms/Portfolio.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import Container from '../atoms/Container'
|
||||||
|
import Section from '../atoms/Section'
|
||||||
|
import { H1, Text } from '../atoms/Text'
|
||||||
|
import Avatar from '../molecules/Avatar'
|
||||||
|
import Loading from '../molecules/Loading'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { useRocketeerImages } from '../../modules/api'
|
||||||
|
import { useAddress } from '../../modules/web3'
|
||||||
|
|
||||||
|
|
||||||
|
export default function Verifier() {
|
||||||
|
|
||||||
|
// ///////////////////////////////
|
||||||
|
// State management
|
||||||
|
// ///////////////////////////////
|
||||||
|
const address = useAddress()
|
||||||
|
const metamaskAddress = useAddress()
|
||||||
|
const [ validatorAddress, setValidatorAddress ] = useState( )
|
||||||
|
const rocketeers = useRocketeerImages()
|
||||||
|
|
||||||
|
|
||||||
|
// ///////////////////////////////
|
||||||
|
// Lifecycle
|
||||||
|
// ///////////////////////////////
|
||||||
|
useEffect( f => {
|
||||||
|
if( !validatorAddress && metamaskAddress ) setValidatorAddress( metamaskAddress )
|
||||||
|
}, [ metamaskAddress, validatorAddress ] )
|
||||||
|
|
||||||
|
// ///////////////////////////////
|
||||||
|
// Rendering
|
||||||
|
// ///////////////////////////////
|
||||||
|
if( !address && !rocketeers.length ) return <Loading message="Loading your Rocketeers, make sure you're connected to the right wallet address" />
|
||||||
|
return <Container>
|
||||||
|
|
||||||
|
<H1>Portfolio</H1>
|
||||||
|
<Text>Click a Rocketeer to view it's details.</Text>
|
||||||
|
<Section direction="row">
|
||||||
|
|
||||||
|
{ rocketeers.map( ( { id, src } ) => {
|
||||||
|
|
||||||
|
return <Avatar onClick={ f => window.location.href =`https://viewer.rocketeer.fans/?rocketeer=${ id }` } key={ id } src={ src } alt={ `Rocketeer number ${ id }` } />
|
||||||
|
|
||||||
|
} ) }
|
||||||
|
|
||||||
|
<Text>Rocketeers owned by: { address }.</Text>
|
||||||
|
|
||||||
|
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
import { Container } from './generic'
|
import Container from '../atoms/Container'
|
||||||
import '../App.css'
|
import { H1, Text } from '../atoms/Text'
|
||||||
|
import Button from '../atoms/Button'
|
||||||
|
import Input from '../molecules/Input'
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { log } from '../modules/helpers'
|
import { log } from '../../modules/helpers'
|
||||||
import { useAddress, useChainId, useBalanceOf } from '../modules/web3'
|
import { useAddress, useChainId, useBalanceOf } from '../../modules/web3'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
export default function Verifier() {
|
export default function Verifier() {
|
||||||
@ -83,29 +85,29 @@ export default function Verifier() {
|
|||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
log('Rendering with ', message, verifyUrl )
|
log('Rendering with ', message, verifyUrl )
|
||||||
if( message ) return <Container>
|
if( message ) return <Container>
|
||||||
{ message.balance > 0 && <p>✅ { message.username } has { message.balance } Rocketeers on chain { chainId }</p> }
|
{ message.balance > 0 && <Text>✅ { message.username } has { message.balance } Rocketeers on chain { chainId }</Text> }
|
||||||
{ message.balance < 1 && <p>🛑 Computer says no</p> }
|
{ message.balance < 1 && <Text>🛑 Computer says no</Text> }
|
||||||
{ error && <p>Something went wrong, contact #support in Discord</p> }
|
{ error && <Text>Something went wrong, contact #support in Discord</Text> }
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
if( verifyUrl ) return <Container>
|
if( verifyUrl ) return <Container>
|
||||||
|
|
||||||
{ !balance && <p>Checking your on-chain balance...</p> }
|
{ !balance && <Text>Checking your on-chain balance...</Text> }
|
||||||
|
|
||||||
{ balance && <>
|
{ balance && <>
|
||||||
<h1>Verification URL</h1>
|
<H1>Verification URL</H1>
|
||||||
<p>Post this in the Discord channel #get-verified:</p>
|
<Text align="center">Post this in the Discord channel #get-verified:</Text>
|
||||||
<p>{ verifyUrl }</p>
|
<Text align="center">{ verifyUrl }</Text>
|
||||||
</> }
|
</> }
|
||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
return <Container>
|
return <Container>
|
||||||
|
|
||||||
<h1>Verify your hodlr status</h1>
|
<H1 align="center">Verify your hodlr status</H1>
|
||||||
<p>Verify your Rocketeer status by logging in with your wallet. This does NOT trigger a transaction. Therefore it is free.</p>
|
<Text align="center">Verify your Rocketeer status by logging in with your wallet. This does NOT trigger a transaction. Therefore it is free.</Text>
|
||||||
<input onChange={ e => setUsername( e.target.value ) } type="text" placeholder="Your Discord username" />
|
<Input onChange={ e => setUsername( e.target.value ) } type="text" placeholder="Your Discord username"/>
|
||||||
<a onClick={ showVerificationUrl } href="/#" className="button">Verify</a>
|
<Button onClick={ showVerificationUrl }>Verify</Button>
|
||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
}
|
}
|
@ -1,198 +0,0 @@
|
|||||||
import { Container, Loading } from './generic'
|
|
||||||
import '../App.css'
|
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useRocketeers, callApi } from '../modules/api'
|
|
||||||
import { useChainId, useAddress, sign } from '../modules/web3'
|
|
||||||
import { log } from '../modules/helpers'
|
|
||||||
import { useParams, useNavigate } from 'react-router'
|
|
||||||
|
|
||||||
|
|
||||||
export default function Verifier() {
|
|
||||||
|
|
||||||
const { rocketeerId } = useParams()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
|
||||||
// State management
|
|
||||||
// ///////////////////////////////
|
|
||||||
const address = useAddress()
|
|
||||||
const metamaskAddress = useAddress()
|
|
||||||
const [ validatorAddress, setValidatorAddress ] = useState( )
|
|
||||||
const rocketeers = useRocketeers( rocketeerId )
|
|
||||||
const chainId = useChainId()
|
|
||||||
const [ rocketeer, setRocketeer ] = useState( )
|
|
||||||
const [ loading, setLoading ] = useState( )
|
|
||||||
|
|
||||||
|
|
||||||
/* ///////////////////////////////
|
|
||||||
// Functions
|
|
||||||
// /////////////////////////////*/
|
|
||||||
async function setPrimaryOutfit( outfitId ) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
log( `Setting outfit ${ outfitId } for Rocketeer #${ rocketeerId }` )
|
|
||||||
setLoading( `Setting outfit ${ outfitId } for Rocketeer #${ rocketeerId }` )
|
|
||||||
alert( 'You will be prompted to sign a message, this is NOT a transaction' )
|
|
||||||
|
|
||||||
const signature = await sign( JSON.stringify( {
|
|
||||||
signer: address.toLowerCase(),
|
|
||||||
outfitId,
|
|
||||||
chainId,
|
|
||||||
} ), address )
|
|
||||||
|
|
||||||
log( 'Making request with ', signature )
|
|
||||||
|
|
||||||
setLoading( 'Updating profile' )
|
|
||||||
|
|
||||||
const { error, success } = await callApi( `/rocketeer/${ rocketeerId }/outfits`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify( signature )
|
|
||||||
} )
|
|
||||||
|
|
||||||
if( error ) throw new Error( error )
|
|
||||||
|
|
||||||
alert( `Success! Outfit changed, please click "refresh metadata" on Opensea to update it there.\nForwarding you to the tools homepage.` )
|
|
||||||
navigate( `/` )
|
|
||||||
|
|
||||||
} catch( e ) {
|
|
||||||
|
|
||||||
log( e )
|
|
||||||
alert( e.message )
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
|
|
||||||
setLoading( false )
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateNewOutfit( ) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
log( `Generating new outfit for #${ rocketeerId }` )
|
|
||||||
setLoading( `Generating new outfit for #${ rocketeerId }` )
|
|
||||||
alert( 'You will be prompted to sign a message, this is NOT a transaction' )
|
|
||||||
|
|
||||||
const signature = await sign( JSON.stringify( {
|
|
||||||
signer: address.toLowerCase(),
|
|
||||||
rocketeerId,
|
|
||||||
chainId,
|
|
||||||
} ), address )
|
|
||||||
|
|
||||||
log( 'Making request with ', signature )
|
|
||||||
|
|
||||||
setLoading( 'Generating new outfit, this can take a minute' )
|
|
||||||
|
|
||||||
const { error, success } = await callApi( `/rocketeer/${ rocketeerId }/outfits`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify( signature )
|
|
||||||
} )
|
|
||||||
|
|
||||||
if( error ) throw new Error( error )
|
|
||||||
|
|
||||||
alert( `Success! Outfit generated.` )
|
|
||||||
window?.location.reload()
|
|
||||||
|
|
||||||
|
|
||||||
} catch( e ) {
|
|
||||||
|
|
||||||
log( e )
|
|
||||||
alert( e.message )
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
|
|
||||||
setLoading( false )
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
|
||||||
// Lifecycle
|
|
||||||
// ///////////////////////////////
|
|
||||||
useEffect( f => {
|
|
||||||
if( !validatorAddress && metamaskAddress ) setValidatorAddress( metamaskAddress )
|
|
||||||
}, [ metamaskAddress, validatorAddress ] )
|
|
||||||
|
|
||||||
useEffect( f => {
|
|
||||||
|
|
||||||
// Find the data for the clicked Rocketeer
|
|
||||||
const selected = rocketeers.find( ( { id } ) => id === rocketeerId )
|
|
||||||
|
|
||||||
// If the selected rocketeer is available, compute it's available outfits to an easy to access property
|
|
||||||
if( selected ) {
|
|
||||||
|
|
||||||
const newOutfitAllowedInterval = 1000 * 60 * 60 * 24 * 30
|
|
||||||
const { value: outfits } = selected.attributes.find( ( { trait_type } ) => trait_type === 'available outfits' ) || { value: 0 }
|
|
||||||
const { value: last_outfit_change } = selected.attributes.find( ( { trait_type } ) => trait_type === 'last outfit change' ) || { value: 0 }
|
|
||||||
const timeUntilAllowedToChange = newOutfitAllowedInterval - ( Date.now() - last_outfit_change )
|
|
||||||
|
|
||||||
selected.outfits = outfits
|
|
||||||
selected.last_outfit_change = last_outfit_change
|
|
||||||
selected.new_outfit_available = timeUntilAllowedToChange < 0
|
|
||||||
selected.when_new_outfit = new Date( Date.now() + timeUntilAllowedToChange )
|
|
||||||
}
|
|
||||||
|
|
||||||
log( "Selecting rocketeer ", selected )
|
|
||||||
|
|
||||||
// Set the selected rocketeer to state
|
|
||||||
if( selected ) setRocketeer( selected )
|
|
||||||
|
|
||||||
}, [ rocketeerId, rocketeers.length ] )
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
|
||||||
// Rendering
|
|
||||||
// ///////////////////////////////
|
|
||||||
|
|
||||||
if( !rocketeers.length || loading ) return <Loading message={ loading || "Loading Rocketeers, please make sure you selected the right wallet" } />
|
|
||||||
|
|
||||||
// Rocketeer selector
|
|
||||||
if(!rocketeer ) return <Container id="avatar" className={ rocketeers.length > 1 ? 'wide' : '' }>
|
|
||||||
|
|
||||||
<h1>Rocketeers</h1>
|
|
||||||
<p>Click on a Rocketeer to manage it's outfits</p>
|
|
||||||
<div className="row">
|
|
||||||
|
|
||||||
{ rocketeers.map( ( { id, image } ) => {
|
|
||||||
|
|
||||||
return <img id={ `rocketeer-${ id }` } onClick={ f => navigate( `/outfits/${ id }` ) } key={ id } className='rocketeer' src={ image } alt={ `Rocketeer number ${ id }` } />
|
|
||||||
|
|
||||||
} ) }
|
|
||||||
|
|
||||||
<p className="row">Rocketeers owned by: { address }.</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</Container>
|
|
||||||
|
|
||||||
// Changing room
|
|
||||||
if( rocketeer ) return <Container id="avatar" className={ rocketeer.outfits > 0 ? 'wide' : '' }>
|
|
||||||
|
|
||||||
<h1>{ rocketeer.name }</h1>
|
|
||||||
|
|
||||||
<img key={ rocketeer.id } className='rocketeer' src={ rocketeer.image } alt={ `Rocketeer number ${ rocketeer.id }` } />
|
|
||||||
{ rocketeer.new_outfit_available ? <button onClick={ generateNewOutfit } className="button">Generate new outfit</button> : <p>New outfit available on { rocketeer.when_new_outfit.toString() }</p> }
|
|
||||||
<p>This Rocketeer has { 1 + rocketeer.outfits } outfits. { rocketeer.outfits > 0 && 'Click any outfit to select it as primary.' }</p>
|
|
||||||
|
|
||||||
<div className="row">
|
|
||||||
|
|
||||||
<img key={ rocketeer.id + 0 } onClick={ f => setPrimaryOutfit( 0 ) } className='rocketeer' src={ rocketeer.image.replace( /-\d\.jpg/, '.jpg' ) } alt={ `Rocketeer number ${ rocketeer.id }` } />
|
|
||||||
|
|
||||||
{ Array.from( Array( rocketeer.outfits ) ).map( ( val, i ) => {
|
|
||||||
return <img onClick={ f => setPrimaryOutfit( i + 1 ) } key={ rocketeer.id + i } className='rocketeer' src={ rocketeer.image.replace( /-\d\.jpg/, `-${ i + 1 }.jpg` ) } alt={ `Rocketeer number ${ rocketeer.id }` } />
|
|
||||||
} ) }
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="row">Rocketeers owned by: { address }.</p>
|
|
||||||
|
|
||||||
</Container>
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import { Container } from './generic'
|
|
||||||
import '../App.css'
|
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useRocketeerImages } from '../modules/api'
|
|
||||||
import { useAddress } from '../modules/web3'
|
|
||||||
|
|
||||||
|
|
||||||
export default function Verifier() {
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
|
||||||
// State management
|
|
||||||
// ///////////////////////////////
|
|
||||||
const address = useAddress()
|
|
||||||
const metamaskAddress = useAddress()
|
|
||||||
const [ validatorAddress, setValidatorAddress ] = useState( )
|
|
||||||
const rocketeers = useRocketeerImages()
|
|
||||||
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
|
||||||
// Lifecycle
|
|
||||||
// ///////////////////////////////
|
|
||||||
useEffect( f => {
|
|
||||||
if( !validatorAddress && metamaskAddress ) setValidatorAddress( metamaskAddress )
|
|
||||||
}, [ metamaskAddress, validatorAddress ] )
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
|
||||||
// Rendering
|
|
||||||
// ///////////////////////////////
|
|
||||||
return <Container id="avatar" className={ rocketeers.length > 1 ? 'wide' : '' }>
|
|
||||||
|
|
||||||
<h1>Portfolio</h1>
|
|
||||||
<p>Click a Rocketeer to view it's details.</p>
|
|
||||||
<div className="row">
|
|
||||||
|
|
||||||
{ rocketeers.map( ( { id, src } ) => {
|
|
||||||
|
|
||||||
return <img onClick={ f => window.location.href =`https://viewer.rocketeer.fans/?rocketeer=${ id }` } key={ id } className='rocketeer' src={ src } alt={ `Rocketeer number ${ id }` } />
|
|
||||||
|
|
||||||
} ) }
|
|
||||||
|
|
||||||
<p className="row">Rocketeers owned by: { address }.</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</Container>
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
import Minter from './minter'
|
import Minter from './organisms/Minter'
|
||||||
import Metamask from './metamask'
|
import Home from './organisms/Home'
|
||||||
import Verifier from './verifier'
|
import Verifier from './organisms/Verifier'
|
||||||
import Avatar from './avatar'
|
import Avatar from './organisms/Avatar'
|
||||||
import Portfolio from './portfolio'
|
import Portfolio from './organisms/Portfolio'
|
||||||
import Outfits from './organisms/Outfits'
|
import Outfits from './organisms/Outfits'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { log } from '../modules/helpers'
|
import { log } from '../modules/helpers'
|
||||||
@ -46,7 +46,7 @@ function Router() {
|
|||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
return <Routes>
|
return <Routes>
|
||||||
|
|
||||||
<Route exact path='/' element={ <Metamask /> } />
|
<Route exact path='/' element={ <Home /> } />
|
||||||
<Route exact path='/mint' element={ <Minter /> } />
|
<Route exact path='/mint' element={ <Minter /> } />
|
||||||
<Route path='/verify/' element={ <Verifier /> }>
|
<Route path='/verify/' element={ <Verifier /> }>
|
||||||
<Route path='/verify/:verificationCode' element={ <Verifier /> } />
|
<Route path='/verify/:verificationCode' element={ <Verifier /> } />
|
||||||
|