mirror of
https://github.com/status-im/wakuconnect-vote-poll-sdk.git
synced 2025-02-23 23:58:14 +00:00
Make react component functional (#8)
This commit is contained in:
parent
9cfb94ece5
commit
7531ad8f3a
@ -7,6 +7,7 @@ import PollInit from './utils/proto/PollInit'
|
|||||||
import { WakuMessage } from 'js-waku'
|
import { WakuMessage } from 'js-waku'
|
||||||
import { TimedPollVoteMsg } from './models/TimedPollVoteMsg'
|
import { TimedPollVoteMsg } from './models/TimedPollVoteMsg'
|
||||||
import TimedPollVote from './utils/proto/TimedPollVote'
|
import TimedPollVote from './utils/proto/TimedPollVote'
|
||||||
|
import { DetailedTimedPoll } from './models/DetailedTimedPoll'
|
||||||
|
|
||||||
class WakuVoting {
|
class WakuVoting {
|
||||||
private appName: string
|
private appName: string
|
||||||
@ -60,12 +61,14 @@ class WakuVoting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getTimedPolls() {
|
private async getTimedPolls() {
|
||||||
const messages = await this.waku?.store.queryHistory({ contentTopics: [this.pollInitTopic] })
|
const messages = await this.waku?.store.queryHistory({ contentTopics: [this.pollInitTopic] })
|
||||||
return messages
|
return (
|
||||||
?.filter((e): e is WakuMessage & { payload: Uint8Array } => !!e?.payload)
|
messages
|
||||||
.map((msg) => PollInit.decode(msg.payload, msg.timestamp))
|
?.filter((e): e is WakuMessage & { payload: Uint8Array } => !!e?.payload)
|
||||||
.filter((poll): poll is PollInitMsg => !!poll)
|
.map((msg) => PollInit.decode(msg.payload, msg.timestamp))
|
||||||
|
.filter((poll): poll is PollInitMsg => !!poll) ?? []
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sendTimedPollVote(
|
public async sendTimedPollVote(
|
||||||
@ -84,12 +87,26 @@ class WakuVoting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getTimedPollVotes(id: string) {
|
private async getTimedPollsVotes() {
|
||||||
const messages = await this.waku?.store.queryHistory({ contentTopics: [this.timedPollVoteTopic] })
|
const messages = await this.waku?.store.queryHistory({ contentTopics: [this.timedPollVoteTopic] })
|
||||||
return messages
|
return (
|
||||||
?.filter((e): e is WakuMessage & { payload: Uint8Array } => !!e?.payload)
|
messages
|
||||||
.map((msg) => TimedPollVote.decode(msg.payload, msg.timestamp))
|
?.filter((e): e is WakuMessage & { payload: Uint8Array } => !!e?.payload)
|
||||||
.filter((poll): poll is TimedPollVoteMsg => !!poll && poll.id === id)
|
.map((msg) => TimedPollVote.decode(msg.payload, msg.timestamp))
|
||||||
|
.filter((poll): poll is TimedPollVoteMsg => !!poll) ?? []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getDetailedTimedPolls() {
|
||||||
|
const polls = await this.getTimedPolls()
|
||||||
|
const votes = await this.getTimedPollsVotes()
|
||||||
|
return polls.map(
|
||||||
|
(poll) =>
|
||||||
|
new DetailedTimedPoll(
|
||||||
|
poll,
|
||||||
|
votes.filter((vote) => vote.id === poll.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
packages/core/src/models/DetailedTimedPoll.ts
Normal file
39
packages/core/src/models/DetailedTimedPoll.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { BigNumber } from 'ethers'
|
||||||
|
import { PollType } from '../types/PollType'
|
||||||
|
import { PollInitMsg } from './PollInitMsg'
|
||||||
|
import { TimedPollVoteMsg } from './TimedPollVoteMsg'
|
||||||
|
|
||||||
|
export type TimedPollAnswer = {
|
||||||
|
text: string
|
||||||
|
votes: BigNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DetailedTimedPoll {
|
||||||
|
public answers: TimedPollAnswer[]
|
||||||
|
public poll: PollInitMsg
|
||||||
|
public votesMessages: TimedPollVoteMsg[]
|
||||||
|
|
||||||
|
constructor(poll: PollInitMsg, answers: TimedPollVoteMsg[]) {
|
||||||
|
this.poll = poll
|
||||||
|
const summedAnswers = poll.answers.map((answer) => {
|
||||||
|
return { text: answer, votes: BigNumber.from(0) }
|
||||||
|
})
|
||||||
|
const filteredAnswers: TimedPollVoteMsg[] = []
|
||||||
|
answers
|
||||||
|
.sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1))
|
||||||
|
.forEach((answer) => {
|
||||||
|
if (filteredAnswers.findIndex((val) => val.voter === answer.voter) === -1) {
|
||||||
|
if (poll.pollType === PollType.WEIGHTED && answer.tokenAmount) {
|
||||||
|
filteredAnswers.push(answer)
|
||||||
|
summedAnswers[answer.answer].votes = summedAnswers[answer.answer].votes.add(answer.tokenAmount)
|
||||||
|
}
|
||||||
|
if (poll.pollType === PollType.NON_WEIGHTED) {
|
||||||
|
filteredAnswers.push(answer)
|
||||||
|
summedAnswers[answer.answer].votes = summedAnswers[answer.answer].votes.add(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.votesMessages = filteredAnswers
|
||||||
|
this.answers = summedAnswers
|
||||||
|
}
|
||||||
|
}
|
@ -24,10 +24,6 @@
|
|||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.15.0",
|
|
||||||
"@babel/preset-env": "^7.15.0",
|
|
||||||
"@babel/preset-react": "^7.14.5",
|
|
||||||
"@babel/preset-typescript": "^7.15.0",
|
|
||||||
"@testing-library/react-hooks": "^7.0.1",
|
"@testing-library/react-hooks": "^7.0.1",
|
||||||
"@types/chai": "^4.2.21",
|
"@types/chai": "^4.2.21",
|
||||||
"@types/mocha": "^9.0.0",
|
"@types/mocha": "^9.0.0",
|
||||||
@ -39,8 +35,6 @@
|
|||||||
"@types/styled-components": "^5.1.12",
|
"@types/styled-components": "^5.1.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||||
"@typescript-eslint/parser": "^4.29.0",
|
"@typescript-eslint/parser": "^4.29.0",
|
||||||
"babel-loader": "^8.2.2",
|
|
||||||
"babel-preset-minify": "^0.5.1",
|
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-hooks": "^0.2.0",
|
"eslint-plugin-hooks": "^0.2.0",
|
||||||
@ -52,6 +46,7 @@
|
|||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
"mocha": "^9.0.3",
|
"mocha": "^9.0.3",
|
||||||
"source-map-loader": "^3.0.0",
|
"source-map-loader": "^3.0.0",
|
||||||
|
"ts-loader": "^9.2.5",
|
||||||
"ts-node": "^10.1.0",
|
"ts-node": "^10.1.0",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.3.5",
|
||||||
"webpack": "^5.48.0",
|
"webpack": "^5.48.0",
|
||||||
|
@ -4,7 +4,7 @@ import { Example } from '@status-waku-voting/react-components'
|
|||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<div>
|
<div>
|
||||||
<Example />
|
<Example appName={'testApp'} />
|
||||||
</div>,
|
</div>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,7 @@ const webpack = require('webpack')
|
|||||||
|
|
||||||
module.exports = (env) => {
|
module.exports = (env) => {
|
||||||
let environment = 'development'
|
let environment = 'development'
|
||||||
if(env.ENV){
|
if (env.ENV) {
|
||||||
environment = env.ENV
|
environment = env.ENV
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,25 +28,9 @@ module.exports = (env) => {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.[jt]sx?$/,
|
test: /\.tsx?$/,
|
||||||
use: {
|
use: 'ts-loader',
|
||||||
loader: 'babel-loader',
|
exclude: /node_modules/,
|
||||||
options: {
|
|
||||||
cacheDirectory: true,
|
|
||||||
babelrc: false,
|
|
||||||
presets: [
|
|
||||||
'@babel/preset-typescript',
|
|
||||||
[
|
|
||||||
'@babel/preset-env',
|
|
||||||
{ targets: { browsers: '> 0.25%, not dead' } },
|
|
||||||
],
|
|
||||||
'@babel/preset-react',
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
'babel-plugin-styled-components',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
@ -71,7 +55,7 @@ module.exports = (env) => {
|
|||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
process: 'process/browser.js',
|
process: 'process/browser.js',
|
||||||
Buffer: ['buffer', 'Buffer'],
|
Buffer: ['buffer', 'Buffer'],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
devServer: {
|
devServer: {
|
||||||
historyApiFallback: true,
|
historyApiFallback: true,
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@status-waku-voting/core": "link:../core",
|
"@status-waku-voting/core": "link:../core",
|
||||||
|
"ethers": "^5.4.4",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-components": "^0.5.1"
|
"react-components": "^0.5.1"
|
||||||
},
|
},
|
||||||
|
74
packages/react-components/src/Poll.tsx
Normal file
74
packages/react-components/src/Poll.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import WakuVoting from '@status-waku-voting/core'
|
||||||
|
import { DetailedTimedPoll } from '@status-waku-voting/core/dist/esm/src/models/DetailedTimedPoll'
|
||||||
|
import { Wallet, BigNumber } from 'ethers'
|
||||||
|
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'
|
||||||
|
|
||||||
|
type PollProps = {
|
||||||
|
poll: DetailedTimedPoll
|
||||||
|
wakuVoting: WakuVoting | undefined
|
||||||
|
signer: Wallet | JsonRpcSigner
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Poll({ poll, wakuVoting, signer }: PollProps) {
|
||||||
|
const [selectedAnswer, setSelectedAnswer] = useState(0)
|
||||||
|
const [tokenAmount, setTokenAmount] = useState(0)
|
||||||
|
const [address, setAddress] = useState('')
|
||||||
|
useEffect(() => {
|
||||||
|
signer.getAddress().then((e) => setAddress(e))
|
||||||
|
}, [signer])
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{poll.poll.question}
|
||||||
|
<ul>
|
||||||
|
{!poll.votesMessages.find((vote) => vote.voter === address) && (
|
||||||
|
<div onChange={(e) => setSelectedAnswer(Number.parseInt((e.target as any).value ?? 0))}>
|
||||||
|
{poll.poll.answers.map((answer, idx) => {
|
||||||
|
return (
|
||||||
|
<div key={idx}>
|
||||||
|
<input type="radio" value={idx} name={poll.poll.id} /> {answer}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{poll.votesMessages.find((vote) => vote.voter === address) &&
|
||||||
|
poll.answers.map((answer, idx) => {
|
||||||
|
return (
|
||||||
|
<div key={idx}>
|
||||||
|
{answer.text}
|
||||||
|
<VoteCount>Votes : {answer.votes.toString()}</VoteCount>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
{poll.poll.pollType === PollType.WEIGHTED && (
|
||||||
|
<div>
|
||||||
|
Token amount
|
||||||
|
<input onChange={(e) => setTokenAmount(Number.parseInt(e.target.value))} value={tokenAmount} type="number" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (wakuVoting) {
|
||||||
|
wakuVoting.sendTimedPollVote(
|
||||||
|
signer,
|
||||||
|
poll.poll.id,
|
||||||
|
selectedAnswer,
|
||||||
|
poll.poll.pollType === PollType.WEIGHTED ? BigNumber.from(tokenAmount) : undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
Vote
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const VoteCount = styled.div`
|
||||||
|
margin-left: 20px;
|
||||||
|
`
|
36
packages/react-components/src/PollList.tsx
Normal file
36
packages/react-components/src/PollList.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import WakuVoting from '@status-waku-voting/core'
|
||||||
|
import { DetailedTimedPoll } from '@status-waku-voting/core/dist/esm/src/models/DetailedTimedPoll'
|
||||||
|
import { Wallet } from 'ethers'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { Poll } from './Poll'
|
||||||
|
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||||
|
|
||||||
|
type PollListProps = {
|
||||||
|
wakuVoting: WakuVoting | undefined
|
||||||
|
signer: Wallet | JsonRpcSigner
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PollList({ wakuVoting, signer }: PollListProps) {
|
||||||
|
const [polls, setPolls] = useState<DetailedTimedPoll[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
if (wakuVoting) {
|
||||||
|
setPolls(await wakuVoting.getDetailedTimedPolls())
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [wakuVoting])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{polls.map((poll) => {
|
||||||
|
return (
|
||||||
|
<li key={poll.poll.id}>
|
||||||
|
<Poll poll={poll} wakuVoting={wakuVoting} signer={signer} />
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
@ -1,7 +1,78 @@
|
|||||||
import React from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import WakuVoting from '@status-waku-voting/core'
|
||||||
|
import { providers } from 'ethers'
|
||||||
|
import { PollList } from './PollList'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { PollType } from '@status-waku-voting/core/dist/esm/src/types/PollType'
|
||||||
|
|
||||||
function Example() {
|
const provider = new providers.Web3Provider((window as any).ethereum)
|
||||||
return <div>This is example components</div>
|
|
||||||
|
type ExampleProps = {
|
||||||
|
appName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Example({ appName }: ExampleProps) {
|
||||||
|
const [signer, setSigner] = useState(provider.getSigner())
|
||||||
|
const [wakuVoting, setWakuVoting] = useState<WakuVoting | undefined>(undefined)
|
||||||
|
|
||||||
|
const [answers, setAnswers] = useState<string[]>([])
|
||||||
|
const [question, setQuestion] = useState('')
|
||||||
|
|
||||||
|
const [selectedType, setSelectedType] = useState(PollType.WEIGHTED)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
;(window as any).ethereum.on('accountsChanged', async () => setSigner(provider.getSigner()))
|
||||||
|
WakuVoting.create(appName, '0x01').then((e) => setWakuVoting(e))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<PollList wakuVoting={wakuVoting} signer={signer} />
|
||||||
|
<NewPollBox>
|
||||||
|
Question
|
||||||
|
<input value={question} onChange={(e) => setQuestion(e.target.value)} />
|
||||||
|
Answers
|
||||||
|
<button onClick={() => setAnswers((answers) => [...answers, ''])}>Add answer</button>
|
||||||
|
{answers.map((answer, idx) => (
|
||||||
|
<input
|
||||||
|
key={idx}
|
||||||
|
value={answer}
|
||||||
|
onChange={(e) =>
|
||||||
|
setAnswers((answers) => {
|
||||||
|
const newAnswers = [...answers]
|
||||||
|
newAnswers[idx] = e.target.value
|
||||||
|
return newAnswers
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<div onChange={(e) => setSelectedType(Number.parseInt((e.target as any).value))}>
|
||||||
|
<input type="radio" value={PollType.WEIGHTED} name="newPollType" /> Weighted
|
||||||
|
<input type="radio" value={PollType.NON_WEIGHTED} name="newPollType" /> Non weighted
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
await wakuVoting?.createTimedPoll(signer, question, answers, selectedType)
|
||||||
|
setAnswers([])
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</NewPollBox>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`
|
||||||
|
|
||||||
|
const NewPollBox = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 10px 10px 31px -2px #a3a1a1;
|
||||||
|
`
|
||||||
|
|
||||||
export { Example }
|
export { Example }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user