Add vote modals (#56)

This commit is contained in:
Maria Rushkova 2021-09-13 10:55:03 +02:00 committed by GitHub
parent b937d77d26
commit 024c9ac1f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 417 additions and 32 deletions

View File

@ -5,17 +5,19 @@ import React, { useEffect, useState } from 'react'
import { JsonRpcSigner } from '@ethersproject/providers'
import { PollType } from '@status-waku-voting/core/dist/esm/src/types/PollType'
import styled from 'styled-components'
import { RadioGroup, SmallButton } from '@status-waku-voting/react-components'
import { RadioGroup, SmallButton, Theme } from '@status-waku-voting/react-components'
import { PollResults } from './PollResults'
import { useEthers } from '@usedapp/core'
import { Modal } from '@status-waku-voting/react-components'
type PollProps = {
theme: Theme
poll: DetailedTimedPoll
wakuPolling: WakuPolling | undefined
signer: Wallet | JsonRpcSigner | undefined
}
export function Poll({ poll, wakuPolling, signer }: PollProps) {
export function Poll({ poll, wakuPolling, theme, signer }: PollProps) {
const { account } = useEthers()
const [selectedAnswer, setSelectedAnswer] = useState<number | undefined>(undefined)
const [tokenAmount, setTokenAmount] = useState(0)
@ -83,7 +85,7 @@ export function Poll({ poll, wakuPolling, signer }: PollProps) {
</SmallButton>
)}
{showNotEnoughTokens && (
<Modal heading={''} setShowModal={setShowNotEnoughTokens}>
<Modal heading={''} setShowModal={setShowNotEnoughTokens} theme={theme}>
<ModalTextWrapper>You don't have enough tokens to vote</ModalTextWrapper>
</Modal>
)}

View File

@ -4,7 +4,7 @@ import { JsonRpcSigner } from '@ethersproject/providers'
import styled from 'styled-components'
import { PollType } from '@status-waku-voting/core/dist/esm/src/types/PollType'
import { WakuPolling } from '@status-waku-voting/core'
import { Input, addIcon, SmallButton, Modal } from '@status-waku-voting/react-components'
import { Input, addIcon, SmallButton, Modal, Theme } from '@status-waku-voting/react-components'
function getLocaleIsoTime(dateTime: Date) {
const MS_PER_MINUTE = 60000
@ -35,12 +35,13 @@ function ConfirmScreen({ children, setShowModal }: ConfirmScreenProps) {
}
type PollCreationProps = {
theme: Theme
signer: JsonRpcSigner | Wallet
wakuPolling: WakuPolling | undefined
setShowPollCreation: (val: boolean) => void
}
export function PollCreation({ signer, wakuPolling, setShowPollCreation }: PollCreationProps) {
export function PollCreation({ signer, wakuPolling, theme, setShowPollCreation }: PollCreationProps) {
const [answers, setAnswers] = useState<string[]>(['', ''])
const [question, setQuestion] = useState('')
const [showCreateConfirmation, setShowCreateConfirmation] = useState(false)
@ -49,7 +50,7 @@ export function PollCreation({ signer, wakuPolling, setShowPollCreation }: PollC
const [endTimePicker, setEndTimePicker] = useState(new Date(new Date().getTime() + 10000000))
return (
<Modal heading="Create a poll" setShowModal={setShowPollCreation}>
<Modal heading="Create a poll" setShowModal={setShowPollCreation} theme={theme}>
<NewPollBox>
{!showCreateConfirmation && !showNotEnoughTokens && (
<PollForm>

View File

@ -5,13 +5,15 @@ import React, { useEffect, useState } from 'react'
import { Poll } from './Poll'
import { JsonRpcSigner } from '@ethersproject/providers'
import styled from 'styled-components'
import { Theme } from '@status-waku-voting/react-components'
type PollListProps = {
theme: Theme
wakuPolling: WakuPolling | undefined
signer: Wallet | JsonRpcSigner | undefined
}
export function PollList({ wakuPolling, signer }: PollListProps) {
export function PollList({ wakuPolling, signer, theme }: PollListProps) {
const [polls, setPolls] = useState<DetailedTimedPoll[]>([])
const [dividedPolls, setDividedPolls] = useState<DetailedTimedPoll[][]>([[], [], []])
useEffect(() => {
@ -43,7 +45,7 @@ export function PollList({ wakuPolling, signer }: PollListProps) {
return (
<ColumnWrapper key={idx}>
{pollArray.map((poll) => {
return <Poll key={poll.poll.id} poll={poll} wakuPolling={wakuPolling} signer={signer} />
return <Poll key={poll.poll.id} poll={poll} wakuPolling={wakuPolling} signer={signer} theme={theme} />
})}
</ColumnWrapper>
)

View File

@ -22,7 +22,12 @@ export function WakuPolling({ appName, signer, theme }: WakuPollingProps) {
return (
<Wrapper>
{showPollCreation && signer && (
<PollCreation signer={signer} wakuPolling={wakuPolling} setShowPollCreation={setShowPollCreation} />
<PollCreation
signer={signer}
wakuPolling={wakuPolling}
setShowPollCreation={setShowPollCreation}
theme={theme}
/>
)}
{account ? (
<CreateButton theme={theme} disabled={!signer} onClick={() => setShowPollCreation(true)}>
@ -41,12 +46,12 @@ export function WakuPolling({ appName, signer, theme }: WakuPollingProps) {
</CreateButton>
)}
{selectConnect && (
<Modal heading="Connect" setShowModal={setSelectConnect}>
<Modal heading="Connect" theme={theme} setShowModal={setSelectConnect}>
<Networks />
</Modal>
)}
<PollList wakuPolling={wakuPolling} signer={signer} />
<PollList wakuPolling={wakuPolling} signer={signer} theme={theme} />
</Wrapper>
)
}

View File

@ -0,0 +1,21 @@
import styled from 'styled-components'
export const Input = styled.input`
max-width: 420px;
padding: 11px 20px;
background: #f0f1f3;
color: #00000;
border-radius: 8px;
border: 1px solid #eef2f5;
outline: none;
&:active,
&:focus {
border: 1px solid #5d7be2;
caret-color: #5d7be2;
}
@media (max-width: 600px) {
max-width: 100%;
}
`

View File

@ -8,7 +8,7 @@ export function Proposal() {
return (
<ProposalWrapper>
<ProposalHeader theme={blueTheme} />
<ProposalList />
<ProposalList theme={blueTheme} />
</ProposalWrapper>
)
}

View File

@ -1,9 +1,11 @@
import React from 'react'
import styled from 'styled-components'
import { Theme } from '@status-waku-voting/react-components'
import { ProposalInfo } from './ProposalInfo'
import { ProposalVote } from './ProposalVoteCard/ProposalVote'
interface ProposalCardProps {
theme: Theme
heading: string
text: string
address: string
@ -12,11 +14,11 @@ interface ProposalCardProps {
hideModalFunction?: (val: boolean) => void
}
export function ProposalCard({ heading, text, address, vote, voteWinner }: ProposalCardProps) {
export function ProposalCard({ heading, text, address, vote, voteWinner, theme }: ProposalCardProps) {
return (
<Card>
<ProposalInfo heading={heading} text={text} address={address} />
<ProposalVote vote={vote} voteWinner={voteWinner} address={address} />
<ProposalVote vote={vote} voteWinner={voteWinner} address={address} heading={heading} theme={theme} />
</Card>
)
}

View File

@ -38,7 +38,7 @@ export function ProposalHeader({ theme }: ProposalHeaderProps) {
</CreateButton>
)}
{selectConnect && (
<Modal heading="Connect" setShowModal={setSelectConnect}>
<Modal heading="Connect" setShowModal={setSelectConnect} theme={theme}>
<Networks />
</Modal>
)}

View File

@ -1,8 +1,12 @@
import React from 'react'
import styled from 'styled-components'
import { Theme } from '@status-waku-voting/react-components'
import { ProposalCard } from './ProposalCard'
export function ProposalList() {
type ProposalListProps = {
theme: Theme
}
export function ProposalList({ theme }: ProposalListProps) {
return (
<List>
<ProposalCard
@ -13,6 +17,7 @@ export function ProposalList() {
address={'#'}
vote={2345678}
voteWinner={2}
theme={theme}
/>
<ProposalCard
heading={'Short proposal title'}
@ -20,6 +25,7 @@ export function ProposalList() {
'This is a shorter description of the proposal. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque interdum rutrum sodales.'
}
address={'#'}
theme={theme}
/>
</List>
)

View File

@ -5,31 +5,99 @@ import { FinalBtn, VoteBtnAgainst, VoteBtnFor } from '../Buttons'
import { VoteSubmitButton } from './VoteSubmitButton'
import { VoteChart } from './VoteChart'
import { ViewLink } from '../ViewLink'
import { Modal, Theme } from '@status-waku-voting/react-components'
import { VoteModal } from '../VoteModal'
import { VoteAnimatedModal } from '../VoteAnimatedModal'
interface ProposalVoteProps {
theme: Theme
vote?: number
voteWinner?: number
heading: string
address: string
hideModalFunction?: (val: boolean) => void
}
export function ProposalVote({ vote, voteWinner, address, hideModalFunction }: ProposalVoteProps) {
export function ProposalVote({ vote, voteWinner, address, heading, theme, hideModalFunction }: ProposalVoteProps) {
const { account } = useEthers()
const [showVoteModal, setShowVoteModal] = useState(false)
const [showConfirmModal, setShowConfirmModal] = useState(false)
const [proposingAmount, setProposingAmount] = useState(0)
const [selectedVoted, setSelectedVoted] = useState(0)
const setNext = (val: boolean) => {
setShowConfirmModal(val)
setShowVoteModal(false)
}
const hideConfirm = (val: boolean) => {
if (hideModalFunction) {
hideModalFunction(false)
}
setShowConfirmModal(val)
}
return (
<Card>
{showVoteModal && (
<Modal heading={heading} setShowModal={setShowVoteModal} theme={theme}>
<VoteModal
votesFor={1865567}
votesAgainst={1740235}
timeLeft={4855555577}
availableAmount={65245346}
selectedVote={selectedVoted}
proposingAmount={proposingAmount}
setShowConfirmModal={setNext}
setProposingAmount={setProposingAmount}
/>{' '}
</Modal>
)}
{showConfirmModal && (
<Modal heading={heading} setShowModal={hideConfirm} theme={theme}>
<VoteAnimatedModal
votesFor={1865567}
votesAgainst={1740235}
timeLeft={4855555577}
selectedVote={selectedVoted}
setShowModal={hideConfirm}
proposingAmount={proposingAmount}
/>
</Modal>
)}
{voteWinner ? <CardHeading>Proposal {voteWinner == 1 ? 'rejected' : 'passed'}</CardHeading> : <CardHeading />}
<VoteChart votesFor={1865567} votesAgainst={1740235} timeLeft={4855555577} voteWinner={voteWinner} />
<VoteChart
votesFor={1865567}
votesAgainst={1740235}
timeLeft={4855555577}
voteWinner={voteWinner}
selectedVote={selectedVoted}
/>
<CardButtons>
{voteWinner ? (
<FinalBtn disabled={!account}>Finalize the vote</FinalBtn>
) : (
<VotesBtns>
<VoteBtnAgainst disabled={!account}>Vote Against</VoteBtnAgainst>
<VoteBtnFor disabled={!account}>Vote For</VoteBtnFor>
<VoteBtnAgainst
disabled={!account}
onClick={() => {
setSelectedVoted(0)
setShowVoteModal(true)
}}
>
Vote Against
</VoteBtnAgainst>
<VoteBtnFor
disabled={!account}
onClick={() => {
setSelectedVoted(1)
setShowVoteModal(true)
}}
>
Vote For
</VoteBtnFor>
</VotesBtns>
)}
</CardButtons>

View File

@ -0,0 +1,55 @@
import React from 'react'
import styled from 'styled-components'
import { FinalBtn } from './Buttons'
import { VoteChart } from './ProposalVoteCard/VoteChart'
interface VoteAnimatedModalProps {
votesFor: number
votesAgainst: number
timeLeft: number
proposingAmount: number
selectedVote: number
setShowModal: (val: boolean) => void
}
export function VoteAnimatedModal({
votesFor,
votesAgainst,
timeLeft,
selectedVote,
proposingAmount,
setShowModal,
}: VoteAnimatedModalProps) {
return (
<VoteConfirm>
<ConfirmText>Your vote for this proposal has been cast!</ConfirmText>
<VoteChart
votesFor={votesFor}
votesAgainst={votesAgainst}
timeLeft={timeLeft}
proposingAmount={proposingAmount}
selectedVote={selectedVote}
isAnimation={true}
/>
<FinalBtn onClick={() => setShowModal(false)}>Close</FinalBtn>
</VoteConfirm>
)
}
const VoteConfirm = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 32px;
`
const ConfirmText = styled.div`
margin-bottom: 32px;
text-align: center;
line-height: 22px;
& > span {
font-weight: bold;
}
`

View File

@ -0,0 +1,76 @@
import React from 'react'
import styled from 'styled-components'
import { VoteChart } from './ProposalVoteCard/VoteChart'
import { VoteBtnAgainst, VoteBtnFor } from './Buttons'
import { VotePropose } from './VotePropose'
export interface VoteModalProps {
votesFor: number
votesAgainst: number
timeLeft: number
voteWinner?: number
selectedVote: number
availableAmount: number
proposingAmount: number
setShowConfirmModal: (show: boolean) => void
setProposingAmount: (val: number) => void
}
export function VoteModal({
votesFor,
votesAgainst,
timeLeft,
voteWinner,
selectedVote,
availableAmount,
proposingAmount,
setShowConfirmModal,
setProposingAmount,
}: VoteModalProps) {
const disabled = proposingAmount === 0
return (
<Column>
<VoteChart
votesFor={votesFor}
votesAgainst={votesAgainst}
timeLeft={timeLeft}
voteWinner={voteWinner}
proposingAmount={proposingAmount}
selectedVote={selectedVote}
/>
<VotePropose
availableAmount={availableAmount}
setProposingAmount={setProposingAmount}
proposingAmount={proposingAmount}
/>
{selectedVote === 0 ? (
<ModalVoteBtnAgainst disabled={disabled} onClick={() => setShowConfirmModal(true)}>
Vote Against
</ModalVoteBtnAgainst>
) : (
<ModalVoteBtnFor disabled={disabled} onClick={() => setShowConfirmModal(true)}>
Vote For
</ModalVoteBtnFor>
)}
</Column>
)
}
const Column = styled.div`
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 32px;
`
const ModalVoteBtnAgainst = styled(VoteBtnAgainst)`
width: 100%;
margin-top: 32px;
`
const ModalVoteBtnFor = styled(VoteBtnFor)`
width: 100%;
margin-top: 32px;
`

View File

@ -0,0 +1,136 @@
import React from 'react'
import { useState } from 'react'
import styled from 'styled-components'
import { addCommas } from '../helpers/addCommas'
import { Input } from './Input'
export interface VoteProposingProps {
availableAmount: number
setProposingAmount: (show: number) => void
proposingAmount: number
disabled?: boolean
}
export function VotePropose({ availableAmount, proposingAmount, setProposingAmount }: VoteProposingProps) {
const [displayAmount, setDisplayAmount] = useState(addCommas(proposingAmount) + ' SNT')
let step = 10 ** (Math.floor(Math.log10(availableAmount)) - 2)
if (availableAmount < 100) {
step = 1
}
const setAvailableAmount = () => {
setProposingAmount(availableAmount)
setDisplayAmount(addCommas(availableAmount) + ' SNT')
}
const sliderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (Number(e.target.value) == step * Math.floor(availableAmount / step)) {
setAvailableAmount()
} else {
setProposingAmount(Number(e.target.value))
setDisplayAmount(addCommas(Number(e.target.value)) + ' SNT')
}
}
const progress = (proposingAmount / availableAmount) * 100 + '%'
const onInputAmountBlur = () => {
if (proposingAmount > availableAmount) {
setAvailableAmount()
} else {
setDisplayAmount(addCommas(proposingAmount) + ' SNT')
}
}
return (
<VoteProposing>
<VoteProposingInfo>
<p>My vote</p>
<span>Available {addCommas(availableAmount)} ABC</span>
</VoteProposingInfo>
<VoteProposingAmount
value={displayAmount}
onInput={(e) => {
setProposingAmount(Number(e.currentTarget.value))
setDisplayAmount(e.currentTarget.value)
}}
onBlur={onInputAmountBlur}
onFocus={() => setDisplayAmount(proposingAmount.toString())}
/>
<VoteProposingRangeWrap>
<VoteProposingRange
type="range"
min={0}
max={availableAmount}
step={step}
value={proposingAmount}
onChange={sliderChange}
style={{
background: `linear-gradient(90deg, #0F3595 0% ${progress}, #EDF1FF ${progress} 100%)`,
}}
/>
</VoteProposingRangeWrap>
</VoteProposing>
)
}
const VoteProposing = styled.div`
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
`
const VoteProposingInfo = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
& > span {
font-size: 12px;
line-height: 16px;
color: #939ba1;
}
`
const VoteProposingAmount = styled(Input)`
width: 100%;
margin-bottom: 16px;
font-size: 15px;
line-height: 22px;
`
const VoteProposingRangeWrap = styled.div`
width: 294px;
`
const VoteProposingRange = styled.input`
appearance: none;
width: 100%;
height: 4px;
padding: 0;
margin: 10px 0;
border-radius: 2px;
outline: none;
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #5d7be2;
cursor: pointer;
}
&::-moz-range-thumb {
width: 20px;
height: 20px;
background: #5d7be2;
border: 0.5px solid rgba(0, 0, 0, 0);
border-radius: 50px;
cursor: pointer;
}
`

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.4697 17.5303C16.7626 17.8232 17.2374 17.8232 17.5303 17.5303C17.8232 17.2374 17.8232 16.7626 17.5303 16.4697L13.4142 12.3536C13.219 12.1583 13.219 11.8417 13.4142 11.6464L17.5303 7.53033C17.8232 7.23744 17.8232 6.76256 17.5303 6.46967C17.2374 6.17678 16.7626 6.17678 16.4697 6.46967L12.3536 10.5858C12.1583 10.781 11.8417 10.781 11.6464 10.5858L7.53033 6.46967C7.23744 6.17678 6.76256 6.17678 6.46967 6.46967C6.17678 6.76256 6.17678 7.23744 6.46967 7.53033L10.5858 11.6464C10.781 11.8417 10.781 12.1583 10.5858 12.3536L6.46967 16.4697C6.17678 16.7626 6.17678 17.2374 6.46967 17.5303C6.76256 17.8232 7.23744 17.8232 7.53033 17.5303L11.6464 13.4142C11.8417 13.219 12.1583 13.219 12.3536 13.4142L16.4697 17.5303Z" fill="#0F3595"/>
</svg>

After

Width:  |  Height:  |  Size: 844 B

View File

@ -1,14 +1,16 @@
import React, { ReactNode, useEffect } from 'react'
import styled from 'styled-components'
import closeButton from '../assets/svg/close.svg'
import { Theme } from '../style/themes'
import { CloseButton } from './misc/Buttons'
type ModalProps = {
heading: string
children: ReactNode
theme: Theme
setShowModal: (val: boolean) => void
}
export function Modal({ heading, children, setShowModal }: ModalProps) {
export function Modal({ heading, children, theme, setShowModal }: ModalProps) {
const body = document.getElementById('root')
useEffect(() => {
@ -25,7 +27,7 @@ export function Modal({ heading, children, setShowModal }: ModalProps) {
<PopUpWindow onClick={(e) => e.stopPropagation()}>
<PopUpHeader>
<PopUpHeading>{heading}</PopUpHeading>
<CloseButton onClick={() => setShowModal(false)} />
<CloseButton theme={theme} onClick={() => setShowModal(false)} />
</PopUpHeader>
<PopUpContetnt>{children}</PopUpContetnt>
</PopUpWindow>
@ -85,10 +87,3 @@ const PopUpHeading = styled.p`
const PopUpContetnt = styled.div`
width: 100%;
`
const CloseButton = styled.button`
width: 24px;
height: 24px;
background-image: url(${closeButton});
background-color: transparent;
border: none;
`

View File

@ -59,7 +59,7 @@ export function TopBar({ logo, title, theme, activate, deactivate, account }: To
</ContentWrapper>
{selectConnect && (
<Modal heading="Connect" setShowModal={setSelectConnect}>
<Modal heading="Connect" theme={theme} setShowModal={setSelectConnect}>
<Networks />
</Modal>
)}

View File

@ -1,5 +1,7 @@
import styled, { css } from 'styled-components'
import { Theme } from '../../style/themes'
import { orangeTheme, Theme } from '../../style/themes'
import closeButton from '../../assets/svg/close.svg'
import blueCloseButton from '../../assets/svg/blueClose.svg'
export const Button = styled.button`
height: 44px;
@ -154,3 +156,14 @@ export const ButtonDisconnect = styled.button<DisconnectProps>`
color: ${({ theme }) => theme.activeTextColor};
}
`
interface CloseProps {
theme: Theme
}
export const CloseButton = styled.button<CloseProps>`
width: 24px;
height: 24px;
background-image: url(${({ theme }) => (theme === orangeTheme ? closeButton : blueCloseButton)});
background-color: transparent;
border: none;
`