import React, { useEffect, useState, useMemo } from "react"; import { Button, Checkbox, Divider, Space, List, Steps, Typography, Badge, Spin, Row, Col } from "antd"; import { SmileTwoTone, LikeTwoTone, CheckCircleTwoTone, MinusOutlined, PlusOutlined } from "@ant-design/icons"; import { Address } from "../components"; const { Title, Text } = Typography; export default function QuadraticDiplomacyVote({ voteCredits, contributorEntries, tx, writeContracts, isVoter, mainnetProvider, }) { const [selectedContributors, setSelectedContributors] = useState({}); const [currentStep, setCurrentStep] = useState(1); const [spentVoteTokens, setSpentVoteTokens] = useState(0); const [isSendingTx, setIsSendingTx] = useState(false); const availableVoteTokens = voteCredits?.toNumber() ?? 0; const remainingVoteTokens = availableVoteTokens - spentVoteTokens; const contributors = useMemo( () => contributorEntries.reduce((entries, current) => { entries[current.wallet] = 0; return entries; }, {}), [contributorEntries], ); const allContributorsSelected = Object.keys(contributors).length === Object.keys(selectedContributors).length; if (!isVoter) { return ( <div style={{ border: "1px solid", padding: "40px", width: 800, margin: "auto", marginTop: 64, textAlign: "left" }}> <Title level={4} style={{ fontFamily: "Space Mono" }}>Access denied</Title> <p>You are not part of the members of this election.</p> </div> ); } const handleSelectAllContributors = () => allContributorsSelected ? setSelectedContributors({}) : setSelectedContributors(contributors); const handleContributorSelection = (e, contributorAddress) => { setSelectedContributors(prevSelectedContributors => { if (prevSelectedContributors[contributorAddress] !== undefined) { const state = { ...prevSelectedContributors }; delete state[contributorAddress]; return state; } else { return { ...prevSelectedContributors, [contributorAddress]: contributors[contributorAddress], }; } }); }; const handleContributorVote = (e, op, clickedContributorAddress) => { // adjust available vote tokens setSpentVoteTokens(prevSpentVoteTokens => (op === "add" ? prevSpentVoteTokens + 1 : prevSpentVoteTokens - 1)); // update contributor vote tokens setSelectedContributors(prevSelectedContributors => ({ ...prevSelectedContributors, [clickedContributorAddress]: op === "add" ? Math.min(prevSelectedContributors[clickedContributorAddress] + 1, availableVoteTokens) : Math.max(prevSelectedContributors[clickedContributorAddress] - 1, 0), })); }; const handleSubmitVotes = async () => { const wallets = []; const amounts = []; Object.entries(selectedContributors).forEach(([contributorAddress, voteTokens]) => { wallets.push(contributorAddress); amounts.push(voteTokens); }); setIsSendingTx(true); await tx(writeContracts.QuadraticDiplomacyContract.voteMultiple(wallets, amounts), update => { if (update && (update.status === "confirmed" || update.status === 1)) { setIsSendingTx(false); setSpentVoteTokens(0); setCurrentStep(3); } else if (update.error) { setIsSendingTx(false); } }); }; return ( <div style={{ border: "1px solid", padding: "40px", width: 800, margin: "auto", marginTop: 64, textAlign: "left" }}> <Steps initial={1} current={currentStep} labelPlacement="vertical"> <Steps.Step title="Select Contributors" subTitle={`${contributorEntries.length} contributors`} icon={<SmileTwoTone />} /> <Steps.Step title="Allocate Votes" subTitle={`${remainingVoteTokens} votes left`} icon={<LikeTwoTone twoToneColor="#eb2f96" />} /> <Steps.Step title="Done" subTitle="Thank you!" icon={<CheckCircleTwoTone twoToneColor="#52c41a" />} /> </Steps> <Divider /> {currentStep === 1 ? ( <List size="large" itemLayout="horizontal" header={<Title level={4} style={{ fontFamily: "Space Mono" }}>1. Who've you been working with?</Title>} style={{ width: "600px", margin: "0 auto" }} footer={ <Row justify="end"> <Button type="primary" onClick={() => setCurrentStep(2)} disabled={!Object.keys(selectedContributors).length} > Next </Button> </Row> } dataSource={Object.entries(contributors)} renderItem={([contributorAddress, votes], index) => ( <> {index === 0 && ( <List.Item> <Checkbox indeterminate={!allContributorsSelected && Object.keys(selectedContributors).length} checked={allContributorsSelected} onChange={handleSelectAllContributors} > Select All </Checkbox> </List.Item> )} <List.Item key={contributorAddress}> <Checkbox size="large" onClick={e => handleContributorSelection(e, contributorAddress)} checked={selectedContributors[contributorAddress] !== undefined} > <Address address={contributorAddress} ensProvider={mainnetProvider} fontSize={16} size="short" /> </Checkbox> </List.Item> </> )} /> ) : currentStep === 2 ? ( <List size="large" itemLayout="horizontal" style={{ width: "600px", margin: "0 auto" }} header={ <Space direction="vertical"> <Title level={4} style={{ fontFamily: "Space Mono" }}>2. Allocate votes</Title> <Title level={5}> Remaining vote tokens: <Badge showZero overflowCount={1000} count={remainingVoteTokens} style={{ backgroundColor: "#52c41a" }} /> </Title> </Space> } footer={ <Row justify="end"> {!isSendingTx ? ( <> <Button onClick={() => setCurrentStep(1)} style={{ marginRight: "8px" }} type="secondary">Go back</Button> <Button type="primary" onClick={handleSubmitVotes}> Commit votes </Button> </> ) : ( <Spin size="small" /> )} </Row> } dataSource={Object.entries(selectedContributors)} renderItem={([contributorAddress, contributor]) => ( <> <Badge.Ribbon showZero overflowCount={1000} text={<Title level={5}>{contributor} </Title>} style={{ backgroundColor: contributor ? "#eb2f96" : "grey", height: 24, width: 30, marginRight: -5, }} /> <List.Item key={contributorAddress} extra={ <Button.Group> <Button danger ghost onClick={e => handleContributorVote(e, "remove", contributorAddress)} disabled={!contributor} > <MinusOutlined /> </Button> <Button type="primary" ghost onClick={e => handleContributorVote(e, "add", contributorAddress)} disabled={!remainingVoteTokens} > <PlusOutlined /> </Button> </Button.Group> } > <List.Item.Meta avatar={ <Address address={contributorAddress} fontSize={16} size="short" ensProvider={mainnetProvider} /> } /> </List.Item> </> )} /> ) : ( currentStep === 3 && ( <> <Title level={3} style={{ fontFamily: "Space Mono" }}>Thank you for voting.</Title> <p>The allocation to this workstream will be informed by your votes. See you next month!</p> <Title level={5} style={{ marginTop: "24px" }}>Your votes:</Title> {Object.entries(selectedContributors).map(([contributorAddress, voteTokens]) => ( <p key={contributorAddress}> <Address address={contributorAddress} fontSize={16} size="short" ensProvider={mainnetProvider} /> ( <Text>{voteTokens}</Text>) </p> ))} </> ) )} </div> ); }