From 024c9ac1f5b81000526bf017dbc6fa7b9f27bce5 Mon Sep 17 00:00:00 2001 From: Maria Rushkova <66270386+mrushkova@users.noreply.github.com> Date: Mon, 13 Sep 2021 10:55:03 +0200 Subject: [PATCH] Add vote modals (#56) --- .../src/components/Poll.tsx | 8 +- .../src/components/PollCreation.tsx | 7 +- .../src/components/PollList.tsx | 6 +- .../src/components/WakuPolling.tsx | 11 +- .../src/components/Input.tsx | 21 +++ .../src/components/Proposal.tsx | 2 +- .../src/components/ProposalCard.tsx | 6 +- .../src/components/ProposalHeader.tsx | 2 +- .../src/components/ProposalList.tsx | 8 +- .../ProposalVoteCard/ProposalVote.tsx | 76 +++++++++- .../src/components/VoteAnimatedModal.tsx | 55 +++++++ .../src/components/VoteModal.tsx | 76 ++++++++++ .../src/components/VotePropose.tsx | 136 ++++++++++++++++++ .../src/assets/svg/blueClose.svg | 3 + .../react-components/src/components/Modal.tsx | 15 +- .../src/components/TopBar.tsx | 2 +- .../src/components/misc/Buttons.tsx | 15 +- 17 files changed, 417 insertions(+), 32 deletions(-) create mode 100644 packages/proposal-components/src/components/Input.tsx create mode 100644 packages/proposal-components/src/components/VoteAnimatedModal.tsx create mode 100644 packages/proposal-components/src/components/VoteModal.tsx create mode 100644 packages/proposal-components/src/components/VotePropose.tsx create mode 100644 packages/react-components/src/assets/svg/blueClose.svg diff --git a/packages/polling-components/src/components/Poll.tsx b/packages/polling-components/src/components/Poll.tsx index cba786f..bb6ba79 100644 --- a/packages/polling-components/src/components/Poll.tsx +++ b/packages/polling-components/src/components/Poll.tsx @@ -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(undefined) const [tokenAmount, setTokenAmount] = useState(0) @@ -83,7 +85,7 @@ export function Poll({ poll, wakuPolling, signer }: PollProps) { )} {showNotEnoughTokens && ( - + You don't have enough tokens to vote )} diff --git a/packages/polling-components/src/components/PollCreation.tsx b/packages/polling-components/src/components/PollCreation.tsx index d4e98e3..f2e848d 100644 --- a/packages/polling-components/src/components/PollCreation.tsx +++ b/packages/polling-components/src/components/PollCreation.tsx @@ -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(['', '']) 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 ( - + {!showCreateConfirmation && !showNotEnoughTokens && ( diff --git a/packages/polling-components/src/components/PollList.tsx b/packages/polling-components/src/components/PollList.tsx index 71fb05c..b28e852 100644 --- a/packages/polling-components/src/components/PollList.tsx +++ b/packages/polling-components/src/components/PollList.tsx @@ -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([]) const [dividedPolls, setDividedPolls] = useState([[], [], []]) useEffect(() => { @@ -43,7 +45,7 @@ export function PollList({ wakuPolling, signer }: PollListProps) { return ( {pollArray.map((poll) => { - return + return })} ) diff --git a/packages/polling-page/src/components/WakuPolling.tsx b/packages/polling-page/src/components/WakuPolling.tsx index 047652f..06634ab 100644 --- a/packages/polling-page/src/components/WakuPolling.tsx +++ b/packages/polling-page/src/components/WakuPolling.tsx @@ -22,7 +22,12 @@ export function WakuPolling({ appName, signer, theme }: WakuPollingProps) { return ( {showPollCreation && signer && ( - + )} {account ? ( setShowPollCreation(true)}> @@ -41,12 +46,12 @@ export function WakuPolling({ appName, signer, theme }: WakuPollingProps) { )} {selectConnect && ( - + )} - + ) } diff --git a/packages/proposal-components/src/components/Input.tsx b/packages/proposal-components/src/components/Input.tsx new file mode 100644 index 0000000..fbe961a --- /dev/null +++ b/packages/proposal-components/src/components/Input.tsx @@ -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%; + } +` diff --git a/packages/proposal-components/src/components/Proposal.tsx b/packages/proposal-components/src/components/Proposal.tsx index e394a76..cd69607 100644 --- a/packages/proposal-components/src/components/Proposal.tsx +++ b/packages/proposal-components/src/components/Proposal.tsx @@ -8,7 +8,7 @@ export function Proposal() { return ( - + ) } diff --git a/packages/proposal-components/src/components/ProposalCard.tsx b/packages/proposal-components/src/components/ProposalCard.tsx index d35a3ea..3c2b4cd 100644 --- a/packages/proposal-components/src/components/ProposalCard.tsx +++ b/packages/proposal-components/src/components/ProposalCard.tsx @@ -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 ( - + ) } diff --git a/packages/proposal-components/src/components/ProposalHeader.tsx b/packages/proposal-components/src/components/ProposalHeader.tsx index 6c5f5d2..371cb8a 100644 --- a/packages/proposal-components/src/components/ProposalHeader.tsx +++ b/packages/proposal-components/src/components/ProposalHeader.tsx @@ -38,7 +38,7 @@ export function ProposalHeader({ theme }: ProposalHeaderProps) { )} {selectConnect && ( - + )} diff --git a/packages/proposal-components/src/components/ProposalList.tsx b/packages/proposal-components/src/components/ProposalList.tsx index caf0ee1..dc5ae3d 100644 --- a/packages/proposal-components/src/components/ProposalList.tsx +++ b/packages/proposal-components/src/components/ProposalList.tsx @@ -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 ( ) diff --git a/packages/proposal-components/src/components/ProposalVoteCard/ProposalVote.tsx b/packages/proposal-components/src/components/ProposalVoteCard/ProposalVote.tsx index 250ba8d..22755d6 100644 --- a/packages/proposal-components/src/components/ProposalVoteCard/ProposalVote.tsx +++ b/packages/proposal-components/src/components/ProposalVoteCard/ProposalVote.tsx @@ -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 ( + {showVoteModal && ( + + {' '} + + )} + {showConfirmModal && ( + + + + )} {voteWinner ? Proposal {voteWinner == 1 ? 'rejected' : 'passed'} : } - + {voteWinner ? ( Finalize the vote ) : ( - Vote Against - Vote For + { + setSelectedVoted(0) + setShowVoteModal(true) + }} + > + Vote Against + + { + setSelectedVoted(1) + setShowVoteModal(true) + }} + > + Vote For + )} diff --git a/packages/proposal-components/src/components/VoteAnimatedModal.tsx b/packages/proposal-components/src/components/VoteAnimatedModal.tsx new file mode 100644 index 0000000..e83d9ff --- /dev/null +++ b/packages/proposal-components/src/components/VoteAnimatedModal.tsx @@ -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 ( + + Your vote for this proposal has been cast! + + + setShowModal(false)}>Close + + ) +} + +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; + } +` diff --git a/packages/proposal-components/src/components/VoteModal.tsx b/packages/proposal-components/src/components/VoteModal.tsx new file mode 100644 index 0000000..6275d98 --- /dev/null +++ b/packages/proposal-components/src/components/VoteModal.tsx @@ -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 ( + + + + + {selectedVote === 0 ? ( + setShowConfirmModal(true)}> + Vote Against + + ) : ( + setShowConfirmModal(true)}> + Vote For + + )} + + ) +} + +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; +` diff --git a/packages/proposal-components/src/components/VotePropose.tsx b/packages/proposal-components/src/components/VotePropose.tsx new file mode 100644 index 0000000..db3a24f --- /dev/null +++ b/packages/proposal-components/src/components/VotePropose.tsx @@ -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) => { + 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 ( + + +

My vote

+ Available {addCommas(availableAmount)} ABC +
+ { + setProposingAmount(Number(e.currentTarget.value)) + setDisplayAmount(e.currentTarget.value) + }} + onBlur={onInputAmountBlur} + onFocus={() => setDisplayAmount(proposingAmount.toString())} + /> + + + +
+ ) +} + +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; + } +` diff --git a/packages/react-components/src/assets/svg/blueClose.svg b/packages/react-components/src/assets/svg/blueClose.svg new file mode 100644 index 0000000..4818253 --- /dev/null +++ b/packages/react-components/src/assets/svg/blueClose.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/react-components/src/components/Modal.tsx b/packages/react-components/src/components/Modal.tsx index d1692f0..84b2cfe 100644 --- a/packages/react-components/src/components/Modal.tsx +++ b/packages/react-components/src/components/Modal.tsx @@ -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) { e.stopPropagation()}> {heading} - setShowModal(false)} /> + setShowModal(false)} /> {children} @@ -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; -` diff --git a/packages/react-components/src/components/TopBar.tsx b/packages/react-components/src/components/TopBar.tsx index 10a8fa3..1572128 100644 --- a/packages/react-components/src/components/TopBar.tsx +++ b/packages/react-components/src/components/TopBar.tsx @@ -59,7 +59,7 @@ export function TopBar({ logo, title, theme, activate, deactivate, account }: To {selectConnect && ( - + )} diff --git a/packages/react-components/src/components/misc/Buttons.tsx b/packages/react-components/src/components/misc/Buttons.tsx index 3da8f4f..9cfc2cf 100644 --- a/packages/react-components/src/components/misc/Buttons.tsx +++ b/packages/react-components/src/components/misc/Buttons.tsx @@ -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` color: ${({ theme }) => theme.activeTextColor}; } ` +interface CloseProps { + theme: Theme +} + +export const CloseButton = styled.button` + width: 24px; + height: 24px; + background-image: url(${({ theme }) => (theme === orangeTheme ? closeButton : blueCloseButton)}); + background-color: transparent; + border: none; +`