mirror of
https://github.com/stronk-dev/RandomChad.git
synced 2025-07-05 02:35:08 +02:00
Working outfit switch, started on styled components
This commit is contained in:
parent
605e977bad
commit
f3fe307daa
18
minter/src/components/atoms/Button.js
Normal file
18
minter/src/components/atoms/Button.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const DynamicButton = ( { to, ...props } ) => to ? <link { ...props } to={ to } /> : <button { ...props } />
|
||||
|
||||
|
||||
export default styled( DynamicButton )`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid rgba( 0, 0, 0, .3 );
|
||||
color: rgba( 0, 0, 0, .8 );
|
||||
text-decoration: none;
|
||||
font-size: 1.5rem;
|
||||
padding: .5rem 1.1rem .5rem 1rem;
|
||||
margin-top: 1rem;
|
||||
`
|
38
minter/src/components/atoms/Container.js
Normal file
38
minter/src/components/atoms/Container.js
Normal file
@ -0,0 +1,38 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
// Image that behaves like a background image
|
||||
import LaunchBackground from '../../assets/undraw_launch_day_4e04.svg'
|
||||
const BackgroundImage = styled.img.attrs( props => ( {
|
||||
src: LaunchBackground
|
||||
} ) )`
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
right: 50%;
|
||||
transform: translateY( -50% );
|
||||
/*top: 50%;*/
|
||||
transform: translateX( 50% );
|
||||
width: 90%;
|
||||
opacity: .05;
|
||||
`
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
padding: 0 max( 1rem, calc( 25vw - 4rem ) );
|
||||
box-sizing: border-box;
|
||||
& * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`
|
||||
|
||||
// Container that always has the background image
|
||||
export default ( { children, ...props } ) => <Wrapper { ...props }>
|
||||
<BackgroundImage key='background' />
|
||||
{ children }
|
||||
</Wrapper>
|
39
minter/src/components/atoms/Text.js
Normal file
39
minter/src/components/atoms/Text.js
Normal file
@ -0,0 +1,39 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const Text = styled.p`
|
||||
font-size: 1rem;
|
||||
margin: 1rem 0;
|
||||
line-height: 1.5rem;
|
||||
color: ${ ( { theme } ) => theme.colors.text };
|
||||
text-align: ${ ( { align } ) => align || 'left' }
|
||||
`
|
||||
|
||||
export const H1 = styled.h1`
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
font-family: sans-serif;
|
||||
text-align: ${ ( { align } ) => align || 'left' };
|
||||
color: ${ ( { theme } ) => theme.colors.primary };
|
||||
`
|
||||
|
||||
export const H2 = styled.h2`
|
||||
font-size: 1.5rem;
|
||||
margin: 0 0 1rem;
|
||||
line-height: 1.2;
|
||||
font-weight: 400;
|
||||
text-align: ${ ( { align } ) => align || 'left' };
|
||||
color: ${ ( { theme } ) => theme.colors.accent };
|
||||
`
|
||||
|
||||
export const Sidenote = styled.p`
|
||||
color: ${ ( { theme } ) => theme.colors.hint };
|
||||
font-style: italic;
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
export const Br = styled.span`
|
||||
width: 100%;
|
||||
margin: 2rem 0;
|
||||
`
|
14
minter/src/components/atoms/Theme.js
Normal file
14
minter/src/components/atoms/Theme.js
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { ThemeProvider } from 'styled-components'
|
||||
|
||||
const theme = {
|
||||
colors: {
|
||||
primary: 'teal',
|
||||
text: 'rgb(77, 86, 128)',
|
||||
accent: 'orange',
|
||||
hint: 'rgba( 0, 0, 0, .4 )',
|
||||
backdrop: 'rgba( 0, 0, 0, .05 )'
|
||||
}
|
||||
}
|
||||
|
||||
export default props => <ThemeProvider { ...props } theme={ theme } />
|
40
minter/src/components/molecules/Loading.js
Normal file
40
minter/src/components/molecules/Loading.js
Normal file
@ -0,0 +1,40 @@
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import Container from '../atoms/Container'
|
||||
import { Text } from '../atoms/Text'
|
||||
|
||||
const rotate = keyframes`
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`
|
||||
|
||||
const Spinner = styled.div`
|
||||
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 2rem;
|
||||
|
||||
&:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 8px;
|
||||
border-radius: 50%;
|
||||
border: 6px solid ${ ( { theme } ) => theme.colors.primary };
|
||||
border-color: ${ ( { theme } ) => theme.colors.primary } transparent ${ ( { theme } ) => theme.colors.primary } transparent;
|
||||
animation: ${ rotate } 1.2s linear infinite;
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
export default ( { message, ...props } ) => <Container { ...props }>
|
||||
|
||||
<Spinner />
|
||||
{ message && <Text align="center">{ message }</Text> }
|
||||
|
||||
</Container>
|
200
minter/src/components/organisms/Outfits.js
Normal file
200
minter/src/components/organisms/Outfits.js
Normal file
@ -0,0 +1,200 @@
|
||||
|
||||
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'
|
||||
|
||||
import { H1, Text } from '../atoms/Text'
|
||||
import Button from '../atoms/Button'
|
||||
import Loading from '../molecules/Loading'
|
||||
import Container from '../atoms/Container'
|
||||
|
||||
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>
|
||||
|
||||
<H1>Rocketeers</H1>
|
||||
<Text>Click on a Rocketeer to manage it's outfits</Text>
|
||||
<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 }` } />
|
||||
|
||||
} ) }
|
||||
|
||||
<Text className="row">Rocketeers owned by: { address }.</Text>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</Container>
|
||||
|
||||
// Changing room
|
||||
if( rocketeer ) return <Container>
|
||||
|
||||
<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 }>Generate new outfit</Button> : <Text>New outfit available on { rocketeer.when_new_outfit.toString() }</Text> }
|
||||
<Text>This Rocketeer has { 1 + rocketeer.outfits } outfits. { rocketeer.outfits > 0 && 'Click any outfit to select it as primary.' }</Text>
|
||||
|
||||
<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>
|
||||
|
||||
<Text className="row">Rocketeers owned by: { address }.</Text>
|
||||
|
||||
</Container>
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user