diff --git a/src/components/ui/Containers.tsx b/src/components/ui/Containers.tsx new file mode 100644 index 00000000..3b7f1d83 --- /dev/null +++ b/src/components/ui/Containers.tsx @@ -0,0 +1,13 @@ +import { Grid, Typography, styled } from "@material-ui/core" + +const LoaderContainer = styled(Grid)({ + paddingTop: 40, + paddingBottom: 40 +}) + +const ContainerTitle = styled(Typography)({ + fontSize: 24, + fontWeight: 600 +}) + +export { LoaderContainer, ContainerTitle } diff --git a/src/components/ui/Table.tsx b/src/components/ui/Table.tsx index e868ac9a..2a120462 100644 --- a/src/components/ui/Table.tsx +++ b/src/components/ui/Table.tsx @@ -16,3 +16,7 @@ export const TableHeader = styled(Grid)(({ theme }: { theme: Theme }) => ({ export const TableContainer = styled(ContentContainer)({ width: "100%" }) + +export const TableContainerGrid = styled(Grid)({ + width: "100%" +}) diff --git a/src/components/ui/icons/CopyIcon.tsx b/src/components/ui/icons/CopyIcon.tsx new file mode 100644 index 00000000..78daadae --- /dev/null +++ b/src/components/ui/icons/CopyIcon.tsx @@ -0,0 +1,7 @@ +import { FileCopyOutlined } from "@material-ui/icons" +import { styled } from "@material-ui/core" + +export const CopyIcon = styled(FileCopyOutlined)({ + marginRight: 8, + cursor: "pointer" +}) diff --git a/src/modules/etherlink/components/EvmDaoProposalList.tsx b/src/modules/etherlink/components/EvmDaoProposalList.tsx index 50176d8e..4afd6d46 100644 --- a/src/modules/etherlink/components/EvmDaoProposalList.tsx +++ b/src/modules/etherlink/components/EvmDaoProposalList.tsx @@ -8,10 +8,9 @@ import { ProposalTableRow } from "modules/lite/explorer/components/ProposalTable import { Poll } from "models/Polls" import ReactPaginate from "react-paginate" import { EvmProposalItem } from "./EvmProposalItem" - -const TableContainer = styled(Grid)({ - width: "100%" -}) +import { IEvmProposal } from "../types" +import { TableContainerGrid } from "components/ui/Table" +import { LoaderContainer } from "components/ui/Containers" const CustomGrid = styled(Grid)({ "&:not(:last-child)": { @@ -19,37 +18,23 @@ const CustomGrid = styled(Grid)({ } }) -const LoaderContainer = styled(Grid)({ - paddingTop: 40, - paddingBottom: 40 -}) - -interface Props { - proposals: Proposal[] | undefined - showFooter?: boolean - rightItem?: (proposal: Proposal) => React.ReactElement - showFullList?: boolean -} - interface ProposalObj { type: string - proposal: Proposal | Poll + proposal: IEvmProposal | Poll } -export const EvmDaoProposalList: React.FC = ({ proposals, showFullList = true }) => { +export const EvmDaoProposalList: React.FC<{ + proposals: IEvmProposal[] | undefined + showFooter?: boolean + rightItem?: (proposal: Proposal) => React.ReactElement + showFullList?: boolean +}> = ({ proposals, showFullList = true }) => { const [currentPage, setCurrentPage] = useState(0) const [offset, setOffset] = useState(0) const offsetLimit = 50 const [filteredProposals, setFilteredProposals] = useState([]) - const [isLoading, setIsLoading] = useState(false) - console.log("EvmDaoProposalList", proposals) - console.log( - "EvmDaoProposalListX", - proposals?.filter((p: any) => p.proposalData?.length > 0).map((p: any) => p.type) - ) - const pageCount = Math.ceil(proposals ? proposals.length / offsetLimit : 0) // Invoke when user click to request another page. @@ -67,14 +52,14 @@ export const EvmDaoProposalList: React.FC = ({ proposals, showFullList = id: proposal?.id, title: proposal?.title, proposer: proposal?.proposer || (proposal?.author as string), - type: "lambda", + type: proposal?.type, proposal: proposal })) ?? [] ) }, [proposals]) return ( - + {isLoading ? ( @@ -84,13 +69,17 @@ export const EvmDaoProposalList: React.FC = ({ proposals, showFullList = {filteredProposals && filteredProposals.length && filteredProposals.length > 0 ? ( - {filteredProposals.slice(offset, offset + offsetLimit).map((p, i) => ( - - - - - - ))} + {filteredProposals.slice(offset, offset + offsetLimit).map((p, i) => { + const proposalLink = + p.type == "offchain" ? `offchain-proposal/${p.proposal.id}` : `proposal/${p.proposal.id}` + return ( + + + + + + ) + })} ) : ( No proposals found @@ -114,6 +103,6 @@ export const EvmDaoProposalList: React.FC = ({ proposals, showFullList = ) : null} )} - + ) } diff --git a/src/modules/etherlink/components/EvmProposalDetailCard.tsx b/src/modules/etherlink/components/EvmProposalDetailCard.tsx index 48c1e8d1..bc3320c5 100644 --- a/src/modules/etherlink/components/EvmProposalDetailCard.tsx +++ b/src/modules/etherlink/components/EvmProposalDetailCard.tsx @@ -7,13 +7,14 @@ import { FileCopyOutlined } from "@material-ui/icons" import Share from "assets/img/share.svg" import { CommunityBadge } from "modules/lite/explorer/components/CommunityBadge" import LinkIcon from "assets/img/link.svg" -import { Poll } from "models/Polls" import { useNotification } from "modules/common/hooks/useNotification" import ReactHtmlParser from "react-html-parser" import { EtherlinkContext } from "services/wagmi/context" import { Badge } from "components/ui/Badge" import { StatusBadge } from "modules/explorer/components/StatusBadge" +import { CopyIcon } from "components/ui/icons/CopyIcon" +import { IEvmProposal } from "../types" const LogoItem = styled("img")(({ theme }) => ({ cursor: "pointer", @@ -67,11 +68,6 @@ const StyledLink = styled(Link)(({ theme }) => ({ } })) -const CopyIcon = styled(FileCopyOutlined)({ - marginRight: 8, - cursor: "pointer" -}) - const CustomPopover = withStyles({ paper: { "marginTop": 10, @@ -84,10 +80,11 @@ const CustomPopover = withStyles({ } })(Popover) -export const EvmProposalDetailCard: React.FC<{ poll: Poll | undefined }> = ({ poll }) => { +export const EvmProposalDetailCard: React.FC<{ poll: IEvmProposal | undefined }> = ({ poll }) => { const theme = useTheme() const isMobileSmall = useMediaQuery(theme.breakpoints.down("sm")) - const { daoProposalSelected } = useContext(EtherlinkContext) + // const { daoProposalSelected } = useContext(EtherlinkContext) + const daoProposalSelected = poll const [anchorEl, setAnchorEl] = React.useState(null) const openNotification = useNotification() @@ -165,31 +162,34 @@ export const EvmProposalDetailCard: React.FC<{ poll: Poll | undefined }> = ({ po - - - - - - {/* */} - - - - - {/* */} - - - Posted by: - - + {daoProposalSelected?.status ? ( + + + + + + {/* */} + + + + + {/* */} + + + Posted by: + + + - + ) : null} + diff --git a/src/modules/etherlink/components/EvmProposalItem.tsx b/src/modules/etherlink/components/EvmProposalItem.tsx index 680dd0ed..81a1c6ce 100644 --- a/src/modules/etherlink/components/EvmProposalItem.tsx +++ b/src/modules/etherlink/components/EvmProposalItem.tsx @@ -23,7 +23,7 @@ const CreatedText = styled(Typography)({ export const EvmProposalItem: React.FC<{ proposal: Proposal | any }> = ({ proposal, children }) => { - const formattedDate = dayjs(proposal.startDate).format("LLL") + const formattedDate = proposal.createdAt.format("LLL") return ( diff --git a/src/modules/etherlink/components/EvmProposalVoteDetail.tsx b/src/modules/etherlink/components/EvmProposalVoteDetail.tsx index e77c7d68..c1e1e999 100644 --- a/src/modules/etherlink/components/EvmProposalVoteDetail.tsx +++ b/src/modules/etherlink/components/EvmProposalVoteDetail.tsx @@ -1,10 +1,8 @@ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import React, { useContext, useEffect, useMemo, useState } from "react" -import { Button, Grid, styled, Theme, Typography, useMediaQuery, useTheme } from "@material-ui/core" +import React, { useContext, useMemo, useState } from "react" +import { Grid, styled, Theme, Typography, useMediaQuery, useTheme } from "@material-ui/core" import { GridContainer } from "modules/common/GridContainer" -import { Poll } from "models/Polls" -import { Choice } from "models/Choice" import ProgressBar from "react-customizable-progressbar" import { useTezos } from "services/beacon/hooks/useTezos" @@ -14,17 +12,14 @@ import { EtherlinkContext } from "services/wagmi/context" import { LinearProgress } from "components/ui/LinearProgress" import { useEvmDaoOps } from "services/contracts/etherlinkDAO/hooks/useEvmDaoOps" import { EVM_PROPOSAL_CHOICES } from "../config" +import { IEvmOffchainChoice, IEvmProposal } from "../types" +import { ContainerTitle } from "components/ui/Containers" const Container = styled(Grid)(({ theme }) => ({ background: theme.palette.primary.main, borderRadius: 8 })) -const ContainerTitle = styled(Typography)({ - fontSize: 24, - fontWeight: 600 -}) - const ProgressText = styled(Typography)(({ textcolor }: { textcolor: string }) => ({ color: textcolor, display: "flex", @@ -89,18 +84,112 @@ const HistoryValue = styled(Typography)({ color: "#BFC5CA" }) +const RenderChoices = ({ + mode, + isMobileSmall, + choices, + tokenSymbol, + daoProposalSelected, + totalVoteCount +}: { + mode: string + isMobileSmall: boolean + choices: any[] + tokenSymbol: string + daoProposalSelected: IEvmProposal + totalVoteCount: number +}) => { + console.log({ mode, choices, tokenSymbol, daoProposalSelected, totalVoteCount }) + if (mode === "offchain") { + const totalVotees = choices?.reduce((acc, curr) => acc + curr.walletAddresses.length, 0) + return ( + <> + {choices.map((choice: IEvmOffchainChoice, idx: number) => { + const voteCount = choice.walletAddresses.length + const linearProgressValue = (voteCount / totalVotees) * 100 + const isFor = choice.name === "For" + const votePercentage = (choice.walletAddresses.length / totalVotees) * 100 + return ( + + + + + {choice.name} + + + + + {voteCount} Voters - {tokenSymbol} + + + + + + + + + + {linearProgressValue}% + + + + + ) + })} + + ) + } + return ( + <> + {choices.map((choice, idx) => { + const isFor = choice.name === "For" + const voteCount = isFor ? daoProposalSelected?.votesFor : daoProposalSelected?.votesAgainst + const linearProgressValue = totalVoteCount > 0 ? (voteCount / totalVoteCount) * 100 : 0 + + return ( + + + + + {choice.name} + + + + + {voteCount} Voters - {tokenSymbol} + + + + + + + + + + {linearProgressValue}% + + + + + ) + })} + + ) +} + export const EvmProposalVoteDetail: React.FC<{ - poll: Poll | undefined + poll: IEvmProposal token: any }> = ({ poll, token }) => { const theme = useTheme() const isMobileSmall = useMediaQuery(theme.breakpoints.down("xs")) - const choices = EVM_PROPOSAL_CHOICES + const choices = poll.type === "offchain" ? poll.choices : EVM_PROPOSAL_CHOICES const { network } = useTezos() const [turnout, setTurnout] = useState() - const [votes, setVotes] = useState([]) const { showProposalVoterList, setShowProposalVoterList } = useEvmDaoOps() - const { daoSelected, daoProposalSelected } = useContext(EtherlinkContext) + const { daoSelected } = useContext(EtherlinkContext) + + const daoProposalSelected = poll const tokenData = useMemo( () => ({ tokenAddress: daoSelected?.token, @@ -136,7 +225,7 @@ export const EvmProposalVoteDetail: React.FC<{ const votesQuorumPercentage = daoProposalSelected?.votesWeightPercentage - console.log({ daoProposalSelected }) + console.log({ poll, choices }) return ( <> @@ -165,47 +254,16 @@ export const EvmProposalVoteDetail: React.FC<{ - {choices && - choices?.map((choice: Choice, index) => { - const isFor = choice.name === "For" - const voteCount = isFor ? daoProposalSelected?.votesFor : daoProposalSelected?.votesAgainst - - const linearProgressValue = totalVoteCount > 0 ? (voteCount / totalVoteCount) * 100 : 0 - // const linearProgressValue = 50 - return ( - - - - - {choice.name} - - - - - {voteCount} Voters - {tokenData?.symbol} - - - - - - - - - - {linearProgressValue}% - - - - - ) - })} + {choices && choices?.length > 0 ? ( + + ) : null} @@ -215,7 +273,7 @@ export const EvmProposalVoteDetail: React.FC<{ handleClickOpen()}> Votes - {isTokenDelegationSupported && turnout && !poll?.isXTZ ? ( + {isTokenDelegationSupported && turnout ? ( ({turnout.toFixed(2)} % Turnout) @@ -275,78 +333,81 @@ export const EvmProposalVoteDetail: React.FC<{ - - {/* Quorum */} - - - Quorum - - - -
- {`${votesQuorumPercentage}%`} -
-
+ {daoProposalSelected?.type !== "offchain" ? ( + + {/* Quorum */} + + + + Quorum + + + +
+ {`${votesQuorumPercentage}%`} +
+
+
-
-
-
- {/* History */} - - - - - History - - {daoProposalSelected?.statusHistoryMap?.map( - ( - item: { - status: string - timestamp: number - timestamp_human: string - }, - index: number - ) => { - return ( - - - {item.status} - - - - {item.timestamp_human} - - - - ) - } - )} + + + + {/* History */} + + + + + History + + {daoProposalSelected?.statusHistoryMap?.map( + ( + item: { + status: string + timestamp: number + timestamp_human: string + }, + index: number + ) => { + return ( + + + {item.status} + + + + {item.timestamp_human} + + + + ) + } + )} - {/* {isLambdaProposal ? ( + {/* {isLambdaProposal ? ( <> @@ -360,10 +421,11 @@ export const EvmProposalVoteDetail: React.FC<{ ) : null} */} - +
+
-
+ ) : null}
diff --git a/src/modules/etherlink/explorer/EtherlinkDAO/EvmOffchainProposalDetailPage.tsx b/src/modules/etherlink/explorer/EtherlinkDAO/EvmOffchainProposalDetailPage.tsx new file mode 100644 index 00000000..de76b6d0 --- /dev/null +++ b/src/modules/etherlink/explorer/EtherlinkDAO/EvmOffchainProposalDetailPage.tsx @@ -0,0 +1,290 @@ +import { GridContainer } from "modules/common/GridContainer" +import { + Button, + Grid, + TableRow, + TableBody, + Table, + Typography, + useMediaQuery, + useTheme, + TableCell, + IconButton +} from "@mui/material" +import { PageContainer } from "components/ui/DaoCreator" +import { useContext, useEffect, useState } from "react" +import { useParams } from "react-router-dom" +import { EtherlinkContext } from "services/wagmi/context" +import { EvmProposalDetailCard } from "modules/etherlink/components/EvmProposalDetailCard" +import { EvmProposalVoteDetail } from "modules/etherlink/components/EvmProposalVoteDetail" +import { EvmProposalCountdown } from "modules/etherlink/components/EvmProposalCountdown" +import { EvmProposalVoterList } from "modules/etherlink/components/EvmProposalVoterList" +import { ThumbDownAlt } from "@mui/icons-material" +import { ThumbUpAlt } from "@mui/icons-material" +import { useNotification } from "modules/common/hooks/useNotification" +import { useEvmProposalOps } from "services/contracts/etherlinkDAO/hooks/useEvmProposalOps" +import { ProposalStatus } from "services/services/dao/mappers/proposal/types" +import { CopyButton } from "modules/common/CopyButton" +import { styled } from "@material-ui/core" +import { EvmChoiceItemSelected } from "../EvmProposals/EvmChoiceItemSelected" +import { IEvmOffchainChoice, IEvmProposal } from "modules/etherlink/types" +import BigNumber from "bignumber.js" +import { SmallButton } from "modules/common/SmallButton" +import React from "react" + +const DescriptionText = styled(Typography)({ + fontSize: 24, + fontWeight: 600 +}) + +const LinearContainer = styled(GridContainer)(({ theme }) => ({ + background: theme.palette.secondary.light, + borderRadius: 8 +})) + +const RenderProposalAction = ({ daoProposalSelected }: { daoProposalSelected: IEvmProposal }) => { + const { castOffchainVote } = useEvmProposalOps() + const isVotingActive = daoProposalSelected?.isVotingActive + const [isCastingVote, setIsCastingVote] = useState(false) + const openNotification = useNotification() + const theme = useTheme() + const isMobileSmall = useMediaQuery(theme.breakpoints.down("sm")) + const { castVote, queueForExecution, executeProposal } = useEvmProposalOps() + const [selectedOffchainVotes, setSelectedOffchainVotes] = useState([]) + + if (daoProposalSelected?.type === "offchain" && daoProposalSelected?.status === ProposalStatus.ACTIVE) { + return ( + <> + + + + + Options + {isVotingActive && ( + + + {daoProposalSelected?.choices?.map((choice: any) => ( + + ))} + + + )} + + { + castOffchainVote( + selectedOffchainVotes.map(x => { + return { + choice: x.name, + choiceId: x._id, + pollID: daoProposalSelected?.id + } + }) + ) + }} + style={{ marginTop: 20, opacity: selectedOffchainVotes.length === 0 ? 0.5 : 1 }} + > + Cast your vote + + + + ) + } + if (daoProposalSelected?.status === ProposalStatus.PASSED) { + return ( + + + + ) + } + + if (daoProposalSelected?.status === ProposalStatus.EXECUTABLE) { + return ( + + + + ) + } + + if (daoProposalSelected?.status === ProposalStatus.EXECUTED) { + return ( + + + + ) + } + + if (daoProposalSelected?.status === ProposalStatus.ACTIVE) { + return ( + <> + + + + + {isVotingActive && ( + + + + + )} + + ) + } + + return ( + + + Proposal is not active +
+ (No Quorum) +
+
+ ) +} + +export const EvmOffchainProposalDetailsPage = () => { + const params = useParams() as { proposalId: string } + const proposalId = params?.proposalId + const [daoProposalSelected, setDaoProposalSelected] = useState({}) + const { daoSelected, daoProposals, selectDaoProposal } = useContext(EtherlinkContext) + + const theme = useTheme() + + useEffect(() => { + console.log("Offchain proposal Id", proposalId, daoSelected) + const proposal = daoProposals?.find((x: any) => x.id === proposalId) + console.log("All Dao Proposals", daoProposals) + console.log("All Dao proposal Selected", proposal) + setDaoProposalSelected(proposal) + }, [proposalId, daoProposals, daoSelected]) + + return ( +
+ + + + + + + + + + + + + + + {daoProposalSelected?.type ? ( + + ) : null} + + + + Proposal Data + + + {daoProposalSelected?.description} + + + + +
+ ) +} diff --git a/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalDetailsPage.tsx b/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalDetailsPage.tsx index bb6d13fa..7dcec8a3 100644 --- a/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalDetailsPage.tsx +++ b/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalDetailsPage.tsx @@ -251,43 +251,3 @@ export const EvmProposalDetailsPage = () => { ) } - -const UnusedBlock = ({ choices, isMobileSmall }: { choices: any; isMobileSmall: boolean }) => ( - - {choices && choices.length > 0 ? ( - - - {/* {[choices].map((choice, index) => { - return ( - {}} - /> - ) - })} */} - - {/* {poll?.isActive === ProposalStatus.ACTIVE ? ( - - ) : null} */} - - ) : null} - -) diff --git a/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalsPage.tsx b/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalsPage.tsx index 6e9c617a..2a8dd98f 100644 --- a/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalsPage.tsx +++ b/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalsPage.tsx @@ -13,16 +13,13 @@ import { useQueryParam } from "modules/home/hooks/useQueryParam" import StatusButton from "components/ui/StatusButton" export const EvmProposalsPage = () => { - // const daoId = useEtherlinkDAOID() const [proposalType, setProposalType] = useQueryParam("type") const [proposalStatus, setProposalStatus] = useQueryParam("status") // TODO: Replace useContext with a useEtherlinkProvider const { daoProposals } = useContext(EtherlinkContext) - // const { data, cycleInfo } = useDAO(daoId) - // const { data: proposals } = useProposals(daoId) + const theme = useTheme() - // const isMobileSmall = useMediaQuery(theme.breakpoints.down("xs")) const [openDialog, setOpenDialog] = useState(false) console.log({ daoProposals }) const handleCloseModal = () => { @@ -30,8 +27,12 @@ export const EvmProposalsPage = () => { } const filteredProposals = useMemo(() => { - if (proposalType === "all" && proposalStatus === "all") return daoProposals - if (!proposalType && !proposalStatus) return daoProposals + if ( + (proposalType === "all" && proposalStatus === "all") || + (!proposalType && !proposalStatus) || + (proposalType === "all" && !proposalStatus) + ) + return daoProposals return daoProposals?.filter((proposal: any) => { if (proposalType && proposalType !== "all" && proposal.type === proposalType) return true @@ -63,7 +64,7 @@ export const EvmProposalsPage = () => { onChange={e => setProposalType(e.target.value)} > All - Off-Chain + Off-Chain Token Operation Registry Transfer diff --git a/src/modules/etherlink/explorer/EvmProposals/EvmChoiceItemSelected.tsx b/src/modules/etherlink/explorer/EvmProposals/EvmChoiceItemSelected.tsx new file mode 100644 index 00000000..20d76828 --- /dev/null +++ b/src/modules/etherlink/explorer/EvmProposals/EvmChoiceItemSelected.tsx @@ -0,0 +1,87 @@ +import React, { useEffect } from "react" +import { Button, Divider, Grid, styled, Theme, Typography, useMediaQuery, useTheme } from "@material-ui/core" +import { Choice } from "models/Choice" +import { IEvmOffchainChoice } from "modules/etherlink/types" + +const StyledContainer = styled(Grid)(({ theme }: { theme: Theme }) => ({ + borderRadius: 8, + minHeight: 75, + border: "none", + cursor: "pointer", + backgroundColor: theme.palette.primary.main +})) + +const Text = styled(Typography)({ + fontWeight: 300 +}) + +const StyledButton = styled(Button)({ + "width": "100%", + "minHeight": "inherit", + "&:hover": { + background: "#2d433c" + } +}) + +export const EvmChoiceItemSelected: React.FC<{ + choice: IEvmOffchainChoice + setSelectedVotes: (votes: IEvmOffchainChoice[]) => void + votes: IEvmOffchainChoice[] + multiple: boolean +}> = ({ choice, setSelectedVotes, votes, multiple }) => { + const theme = useTheme() + const isMobileSmall = useMediaQuery(theme.breakpoints.down("sm")) + + const handleVotes = (choice: IEvmOffchainChoice) => { + if (multiple) { + let votesArray = [...votes] + if (!votesArray.includes(choice)) { + votesArray?.push(choice) + } else { + votesArray = votesArray.filter(vote => vote._id !== choice._id) + } + const result = votesArray.map(vote => { + vote.selected = true + return vote + }) + setSelectedVotes(result) + } else { + choice.selected = true + setSelectedVotes([choice]) + } + } + + const isPartOfVotes = () => { + const found = votes.filter(vote => vote._id === choice._id) + return found.length > 0 + } + + return ( + + { + handleVotes(choice) + return + }} + > + {choice.name} + + + ) +} diff --git a/src/modules/etherlink/explorer/router.tsx b/src/modules/etherlink/explorer/router.tsx index fd9e4c74..52b3c4bc 100644 --- a/src/modules/etherlink/explorer/router.tsx +++ b/src/modules/etherlink/explorer/router.tsx @@ -16,6 +16,7 @@ import { EvmMembersPage } from "./EtherlinkDAO/EvmMembersPage" import { EvmRegistryPage } from "./EtherlinkDAO/EvmRegistryPage" import { EvmProposalDetailsPage } from "./EtherlinkDAO/EvmProposalDetailsPage" import { EvmUserPage } from "./EtherlinkDAO/EvmUserPage" +import { EvmOffchainProposalDetailsPage } from "./EtherlinkDAO/EvmOffchainProposalDetailPage" enum DAOState { NOT_FOUND = 0, @@ -97,6 +98,9 @@ export const EtherlinkDAORouter = (): JSX.Element => { + + + diff --git a/src/modules/etherlink/types.d.ts b/src/modules/etherlink/types.d.ts new file mode 100644 index 00000000..2ef9d592 --- /dev/null +++ b/src/modules/etherlink/types.d.ts @@ -0,0 +1,50 @@ +export interface IEvmOffchainChoiceForVote { + address?: string + choice: string + choiceId: string + pollID: string +} + +export interface IEvmOffchainChoice { + _id: string + name: string + pollID: string + address?: string // While Voting + walletAddresses: string[] + selected?: boolean +} + +export interface IEvmProposal { + against: string + author: string + callDataPlain: string[] + callDatas: string[] + calldata: string + createdAt: dayjs.Dayjs + description: string + executionHash: string + externalResource: string + hash: string + id: string + isVotingActive: boolean + latestStage: string + referenceBlock: number + status: string + statusHistoryMap: any[] + statusHistory: Record + targets: string[] + title: string + totalVotes: BigNumber + totalVoteCount: number + timerLabel: string + transactions: any[] + txHash: string + type: string + values: any[] + votesAgainst: number + votesFor: number + votesWeightPercentage: number + votingExpiresAt: dayjs.Dayjs + votingStartTimestamp: dayjs.Dayjs + choices?: IEvmOffchainChoice[] +} diff --git a/src/modules/explorer/components/StatusBadge.tsx b/src/modules/explorer/components/StatusBadge.tsx index 7c44e402..366fe9ab 100644 --- a/src/modules/explorer/components/StatusBadge.tsx +++ b/src/modules/explorer/components/StatusBadge.tsx @@ -2,7 +2,7 @@ import React from "react" import { styled, Grid, Theme, Typography, GridProps } from "@material-ui/core" import { ProposalStatus } from "services/services/dao/mappers/proposal/types" -export const statusColors = (status: ProposalStatus | "all") => { +export const statusColors = (status: ProposalStatus | string): { background: string; color: string; text: string } => { switch (status) { case ProposalStatus.ACTIVE: return { @@ -65,9 +65,14 @@ export const statusColors = (status: ProposalStatus | "all") => { text: "All" } } + return { + background: "#81feb733", + color: "#81feb7", + text: "All" + } } -export const Badge = styled(Grid)(({ status }: { status: ProposalStatus | "all"; theme: Theme }) => ({ +export const Badge = styled(Grid)(({ status }: { status: ProposalStatus | string; theme: Theme }) => ({ "borderRadius": 50, "boxSizing": "border-box", "minWidth": 87, @@ -86,7 +91,7 @@ const Text = styled(Typography)({ textTransform: "capitalize" }) -export const StatusBadge: React.FC<{ status: ProposalStatus | "all" } & GridProps> = ({ status, ...props }) => ( +export const StatusBadge: React.FC<{ status: ProposalStatus | string } & GridProps> = ({ status, ...props }) => ( diff --git a/src/modules/explorer/pages/ProposalDetails/index.tsx b/src/modules/explorer/pages/ProposalDetails/index.tsx index d2faf7d3..38bab934 100644 --- a/src/modules/explorer/pages/ProposalDetails/index.tsx +++ b/src/modules/explorer/pages/ProposalDetails/index.tsx @@ -316,7 +316,10 @@ export const ProposalDetails: React.FC = () => { } const showStatusText = - statusColors(status).text !== ProposalStatus.ACTIVE || statusColors(status).text !== ProposalStatus.PENDING + status && + statusColors(status)?.text && + (statusColors(status)?.text !== ProposalStatus.ACTIVE || statusColors(status)?.text !== ProposalStatus.PENDING) + return ( <> diff --git a/src/modules/lite/explorer/components/VoteDetails.tsx b/src/modules/lite/explorer/components/VoteDetails.tsx index f4af8065..c3a04664 100644 --- a/src/modules/lite/explorer/components/VoteDetails.tsx +++ b/src/modules/lite/explorer/components/VoteDetails.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import React, { useEffect, useMemo, useState } from "react" -import { Button, Grid, LinearProgress, styled, Typography, useMediaQuery, useTheme } from "@material-ui/core" +import React, { useMemo, useState } from "react" +import { Grid, LinearProgress, styled, Typography, useMediaQuery, useTheme } from "@material-ui/core" import { GridContainer } from "modules/common/GridContainer" import { VotesDialog } from "./VotesDialog" import { Poll } from "models/Polls" diff --git a/src/services/contracts/etherlinkDAO/hooks/useEvmDaoOps.tsx b/src/services/contracts/etherlinkDAO/hooks/useEvmDaoOps.tsx index 300cf6a0..d1a5dbd6 100644 --- a/src/services/contracts/etherlinkDAO/hooks/useEvmDaoOps.tsx +++ b/src/services/contracts/etherlinkDAO/hooks/useEvmDaoOps.tsx @@ -1,6 +1,6 @@ import { ethers } from "ethers" import { create } from "zustand" -import { useCallback, useContext, useEffect, useRef, useState } from "react" +import { useCallback, useContext, useEffect, useState } from "react" import { useTezos } from "services/beacon/hooks/useTezos" import { EtherlinkContext } from "services/wagmi/context" @@ -29,7 +29,7 @@ export const useEvmDaoOps = () => { const openNotification = useNotification() const loggedInUserAddress = etherlink?.signer?.address - const { daoSelected, daoMembers, daoProposalSelected } = useContext(EtherlinkContext) + const { daoSelected, daoMembers } = useContext(EtherlinkContext) const [userVotingWeight, setUserVotingWeight] = useState(0) const [userTokenBalance, setUserTokenBalance] = useState(0) const selectedUser = daoMembers?.find((member: any) => member.address === loggedInUserAddress) diff --git a/src/services/contracts/etherlinkDAO/hooks/useEvmProposalOps.tsx b/src/services/contracts/etherlinkDAO/hooks/useEvmProposalOps.tsx index c8cb33b3..d906d82a 100644 --- a/src/services/contracts/etherlinkDAO/hooks/useEvmProposalOps.tsx +++ b/src/services/contracts/etherlinkDAO/hooks/useEvmProposalOps.tsx @@ -14,7 +14,8 @@ import { useHistory } from "react-router-dom" import dayjs from "dayjs" import { getSignature } from "services/lite/utils" import { getEthSignature } from "services/utils/utils" -import { saveLiteProposal } from "services/services/lite/lite-services" +import { saveLiteProposal, voteOnLiteProposal } from "services/services/lite/lite-services" +import { IEvmOffchainChoice, IEvmOffchainChoiceForVote } from "modules/etherlink/types" function getDaoConfigType(type: string) { if (type === "quorumNumerator") return "quorum" @@ -459,7 +460,7 @@ export const useEvmProposalOps = () => { ) const castVote = useCallback( - async (proposalId: number, support: boolean) => { + async (proposalId: string, support: boolean) => { if (!daoContract || !proposalId) return alert("No dao contract or proposal id") const tx = await daoContract.castVote(proposalId?.toString(), support ? 1 : 0) @@ -652,11 +653,54 @@ export const useEvmProposalOps = () => { } } + const castOffchainVote = useCallback( + async (votesData: IEvmOffchainChoiceForVote[]) => { + const publicKey = etherlink.account.address + if (!publicKey) return alert("No public key") + + votesData = votesData.map(x => { + return { + ...x, + address: publicKey + } + }) + + const { signature, payloadBytes } = await getEthSignature(publicKey, JSON.stringify(votesData)) + if (!signature) { + openNotification({ + message: `Issue with Signature`, + autoHideDuration: 3000, + variant: "error" + }) + return + } + const resp = await voteOnLiteProposal(signature, publicKey, payloadBytes, network) + const response = await resp.json() + if (resp.ok) { + openNotification({ + message: "Your vote has been submitted", + autoHideDuration: 3000, + variant: "success" + }) + } else { + console.log("Error: ", response.message) + openNotification({ + message: response.message, + autoHideDuration: 3000, + variant: "error" + }) + return + } + }, + [daoContract, daoProposalSelected?.id, daoSelected?.address, etherlink.account.address, network, openNotification] + ) + return { isLoading, setIsLoading, createProposal, castVote, + castOffchainVote, queueForExecution, executeProposal, signer: etherlink?.signer, diff --git a/src/services/services/lite/lite-services.ts b/src/services/services/lite/lite-services.ts index a24fc0c9..b8242558 100644 --- a/src/services/services/lite/lite-services.ts +++ b/src/services/services/lite/lite-services.ts @@ -202,3 +202,14 @@ export const updateCount = async (id: string) => { }) return resp } + +export const fetchOffchainProposals = async (daoId: string) => { + return await fetch(`${getEnv(EnvKey.REACT_APP_LITE_API_URL)}/daos/${daoId}?include=polls`, { + method: "GET", + headers: { + "Content-Type": "application/json" + } + }) + .then(res => res.json()) + .then(data => data?.polls || []) +} diff --git a/src/services/wagmi/context.tsx b/src/services/wagmi/context.tsx index c5dbdb72..51c88805 100644 --- a/src/services/wagmi/context.tsx +++ b/src/services/wagmi/context.tsx @@ -19,6 +19,7 @@ import { parseTransactionHash } from "modules/etherlink/utils" import { proposalInterfaces } from "modules/etherlink/config" +import { fetchOffchainProposals } from "services/services/lite/lite-services" interface EtherlinkType { isConnected: boolean @@ -82,9 +83,10 @@ const useEtherlinkDao = ({ network }: { network: string }) => { const [daoRegistryDetails, setDaoRegistryDetails] = useState<{ balance: string }>({ balance: "0" }) - + const [daoOffchainProposals, setDaoOffchainProposals] = useState([]) const [daoProposals, setDaoProposals] = useState([]) const [daoProposalSelected, setDaoProposalSelected] = useState({}) + const [daoProposalOffchainSelected, setDaoProposalOffchainSelected] = useState({}) const [daoProposalVoters, setDaoProposalVoters] = useState< { cast: Timestamp @@ -95,17 +97,9 @@ const useEtherlinkDao = ({ network }: { network: string }) => { }[] >([]) - console.log( - "AllCallData", - daoProposals?.map((x: any) => x.callDataPlain?.[0]) - ) - const [daoMembers, setDaoMembers] = useState([]) const { data: firestoreData, loading, fetchCollection } = useFirestoreStore() - // console.log({ firestoreData }) - - console.log({ daoProposals, daoSelected }) useEffect(() => { fetchCollection("contracts") if (firebaseRootCollection) { @@ -121,6 +115,8 @@ const useEtherlinkDao = ({ network }: { network: string }) => { useEffect(() => { console.log("Firestore Data", firestoreData) if (!firebaseRootCollection) return + if (daoProposalSelected?.type === "offchain") return + if (firestoreData?.[firebaseRootCollection]) { setDaoData(firestoreData[firebaseRootCollection]) setIsLoadingDaos(false) @@ -140,133 +136,132 @@ const useEtherlinkDao = ({ network }: { network: string }) => { if (firestoreData?.[daoProposalKey]) { const timeNow = dayjs() - setDaoProposals( - firestoreData[daoProposalKey] - ?.sort((a: any, b: any) => b.createdAt - a.createdAt) - .map(firebaseProposal => { - const votesInFavor = new BigNumber(firebaseProposal?.inFavor) - const votesAgainst = new BigNumber(firebaseProposal?.against) - const votesInFavorWeight = new BigNumber(firebaseProposal?.inFavor) - - const totalVotes = votesInFavor.plus(votesAgainst) - const totalVoteCount = parseInt(firebaseProposal?.votesFor) + parseInt(firebaseProposal?.votesAgainst) - const totalSupply = new BigNumber(daoSelected?.totalSupply ?? "1") - const votesPercentage = totalVotes.div(totalSupply).times(100) - const daoMinimumQuorum = new BigNumber(daoSelected?.quorum ?? "0") - const daoTotalVotingWeight = new BigNumber(daoSelected?.totalSupply ?? "0") - console.log("votesPercentage", firebaseProposal?.title, votesPercentage.toString()) - - const proposalCreatedAt = dayjs.unix(firebaseProposal.createdAt?.seconds as unknown as number) - const votingDelayInMinutes = daoSelected?.votingDelay || 1 - const votingDurationInMinutes = daoSelected?.votingDuration || 1 - - const activeStartTimestamp = proposalCreatedAt.add(votingDelayInMinutes, "minutes") - const votingExpiresAt = activeStartTimestamp.add(votingDurationInMinutes, "minutes") - - const votingEndTimestamp = activeStartTimestamp.add(votingDurationInMinutes, "minutes") - - // Flutter Refernce - // if (votePercentage < org.quorum) { - // newStatus = ProposalStatus.noQuorum; // or "no quorum" in getStatus() - // statusHistory.clear(); - // statusHistory.addAll({"pending": start}); - // statusHistory.addAll({"active": activeStart}); - // statusHistory.addAll({"no quorum": votingEnd}); - // status = "no quorum"; - // return newStatus; - // } - const statusHistoryMap = Object.entries(firebaseProposal.statusHistory) - .map(([status, timestamp]: [string, any]) => ({ - status, - timestamp: timestamp?.seconds as unknown as number, - timestamp_human: dayjs.unix(timestamp?.seconds as unknown as number).format("MMM DD, YYYY hh:mm A") - })) - .sort((a, b) => b.timestamp - a.timestamp) + const onChainProposals = firestoreData[daoProposalKey] + ?.sort((a: any, b: any) => b.createdAt - a.createdAt) + .map(firebaseProposal => { + const votesInFavor = new BigNumber(firebaseProposal?.inFavor) + const votesAgainst = new BigNumber(firebaseProposal?.against) + const votesInFavorWeight = new BigNumber(firebaseProposal?.inFavor) + + const totalVotes = votesInFavor.plus(votesAgainst) + const totalVoteCount = parseInt(firebaseProposal?.votesFor) + parseInt(firebaseProposal?.votesAgainst) + const totalSupply = new BigNumber(daoSelected?.totalSupply ?? "1") + const votesPercentage = totalVotes.div(totalSupply).times(100) + const daoMinimumQuorum = new BigNumber(daoSelected?.quorum ?? "0") + const daoTotalVotingWeight = new BigNumber(daoSelected?.totalSupply ?? "0") + console.log("votesPercentage", firebaseProposal?.title, votesPercentage.toString()) + + const proposalCreatedAt = dayjs.unix(firebaseProposal.createdAt?.seconds as unknown as number) + const votingDelayInMinutes = daoSelected?.votingDelay || 1 + const votingDurationInMinutes = daoSelected?.votingDuration || 1 + + const activeStartTimestamp = proposalCreatedAt.add(votingDelayInMinutes, "minutes") + const votingExpiresAt = activeStartTimestamp.add(votingDurationInMinutes, "minutes") + + const votingEndTimestamp = activeStartTimestamp.add(votingDurationInMinutes, "minutes") + + // Flutter Refernce + // if (votePercentage < org.quorum) { + // newStatus = ProposalStatus.noQuorum; // or "no quorum" in getStatus() + // statusHistory.clear(); + // statusHistory.addAll({"pending": start}); + // statusHistory.addAll({"active": activeStart}); + // statusHistory.addAll({"no quorum": votingEnd}); + // status = "no quorum"; + // return newStatus; + // } + const statusHistoryMap = Object.entries(firebaseProposal.statusHistory) + .map(([status, timestamp]: [string, any]) => ({ + status, + timestamp: timestamp?.seconds as unknown as number, + timestamp_human: dayjs.unix(timestamp?.seconds as unknown as number).format("MMM DD, YYYY hh:mm A") + })) + .sort((a, b) => b.timestamp - a.timestamp) + + statusHistoryMap.push({ + status: "active", + timestamp: activeStartTimestamp.unix(), + timestamp_human: activeStartTimestamp.format("MMM DD, YYYY hh:mm A") + }) + if (votesInFavorWeight.div(daoTotalVotingWeight).times(100).gt(daoMinimumQuorum)) { statusHistoryMap.push({ - status: "active", - timestamp: activeStartTimestamp.unix(), - timestamp_human: activeStartTimestamp.format("MMM DD, YYYY hh:mm A") + status: "passed", + timestamp: votingEndTimestamp.unix(), + timestamp_human: votingEndTimestamp.format("MMM DD, YYYY hh:mm A") }) + } - if (votesInFavorWeight.div(daoTotalVotingWeight).times(100).gt(daoMinimumQuorum)) { + const statusQueued = statusHistoryMap.find(x => x.status === "queued") + if (statusQueued) { + const executionDelayInSeconds = daoSelected?.executionDelay || 0 + const proposalExecutableAt = statusQueued.timestamp + executionDelayInSeconds + console.log("proposalExecutableAt", proposalExecutableAt, timeNow.unix()) + if (proposalExecutableAt < timeNow.unix()) { statusHistoryMap.push({ - status: "passed", - timestamp: votingEndTimestamp.unix(), - timestamp_human: votingEndTimestamp.format("MMM DD, YYYY hh:mm A") + status: "executable", + timestamp: proposalExecutableAt, + timestamp_human: dayjs.unix(proposalExecutableAt).format("MMM DD, YYYY hh:mm A") }) } + } - const statusQueued = statusHistoryMap.find(x => x.status === "queued") - if (statusQueued) { - const executionDelayInSeconds = daoSelected?.executionDelay || 0 - const proposalExecutableAt = statusQueued.timestamp + executionDelayInSeconds - console.log("proposalExecutableAt", proposalExecutableAt, timeNow.unix()) - if (proposalExecutableAt < timeNow.unix()) { - statusHistoryMap.push({ - status: "executable", - timestamp: proposalExecutableAt, - timestamp_human: dayjs.unix(proposalExecutableAt).format("MMM DD, YYYY hh:mm A") - }) - } - } - - if (votesPercentage.lt(daoSelected?.quorum) && votingEndTimestamp.isBefore(timeNow)) { - statusHistoryMap.push({ - status: "no quorum", - timestamp: votingEndTimestamp.unix(), - timestamp_human: votingEndTimestamp.format("MMM DD, YYYY hh:mm A") - }) - } - console.log({ statusHistoryMap }) - - const callDatas = firebaseProposal?.callDatas - const callDataPlain = callDatas?.map((x: any) => getCallDataFromBytes(x)) - const proposalStatus = getStatusByHistory(statusHistoryMap, { - votesPercentage, - votingExpiresAt, - activeStartTimestamp, - daoQuorum: daoSelected?.quorum, - executionDelayInSeconds: daoSelected?.executionDelay + if (votesPercentage.lt(daoSelected?.quorum) && votingEndTimestamp.isBefore(timeNow)) { + statusHistoryMap.push({ + status: "no quorum", + timestamp: votingEndTimestamp.unix(), + timestamp_human: votingEndTimestamp.format("MMM DD, YYYY hh:mm A") }) + } - // Setting up timerLabel - let isVotingActive = false - let timerLabel = "Voting concluded" - let timerTargetDate = null - if (activeStartTimestamp?.isAfter(timeNow)) { - timerLabel = "Voting starts in" - timerTargetDate = activeStartTimestamp - } else if (votingExpiresAt?.isAfter(timeNow) && activeStartTimestamp?.isBefore(timeNow)) { - isVotingActive = true - timerLabel = "Time left to vote" - timerTargetDate = votingExpiresAt - } else if (proposalStatus === ProposalStatus.PASSED) { - timerLabel = "Execution available in" - timerTargetDate = votingEndTimestamp - } - - return { - ...firebaseProposal, - createdAt: dayjs.unix(firebaseProposal.createdAt?.seconds as unknown as number), - callDataPlain, - status: proposalStatus, - proposalData: [], - statusHistoryMap: statusHistoryMap.sort((a, b) => b.timestamp - a.timestamp), - votingStartTimestamp: activeStartTimestamp, - votingExpiresAt: votingExpiresAt, - totalVotes: totalVotes, - totalVoteCount, - timerLabel, - timerTargetDate, - isVotingActive, - votesWeightPercentage: Number(votesPercentage.toFixed(2)), - txHash: firebaseProposal?.executionHash - ? `https://testnet.explorer.etherlink.com/tx/0x${parseTransactionHash(firebaseProposal?.executionHash)}` - : "" - } + const callDatas = firebaseProposal?.callDatas + const callDataPlain = callDatas?.map((x: any) => getCallDataFromBytes(x)) + const proposalStatus = getStatusByHistory(statusHistoryMap, { + votesPercentage, + votingExpiresAt, + activeStartTimestamp, + daoQuorum: daoSelected?.quorum, + executionDelayInSeconds: daoSelected?.executionDelay }) - ) + + // Setting up timerLabel + let isVotingActive = false + let timerLabel = "Voting concluded" + let timerTargetDate = null + if (activeStartTimestamp?.isAfter(timeNow)) { + timerLabel = "Voting starts in" + timerTargetDate = activeStartTimestamp + } else if (votingExpiresAt?.isAfter(timeNow) && activeStartTimestamp?.isBefore(timeNow)) { + isVotingActive = true + timerLabel = "Time left to vote" + timerTargetDate = votingExpiresAt + } else if (proposalStatus === ProposalStatus.PASSED) { + timerLabel = "Execution available in" + timerTargetDate = votingEndTimestamp + } + + return { + ...firebaseProposal, + createdAt: dayjs.unix(firebaseProposal.createdAt?.seconds as unknown as number), + callDataPlain, + status: proposalStatus, + proposalData: [], + statusHistoryMap: statusHistoryMap.sort((a, b) => b.timestamp - a.timestamp), + votingStartTimestamp: activeStartTimestamp, + votingExpiresAt: votingExpiresAt, + totalVotes: totalVotes, + totalVoteCount, + timerLabel, + timerTargetDate, + isVotingActive, + votesWeightPercentage: Number(votesPercentage.toFixed(2)), + txHash: firebaseProposal?.executionHash + ? `https://testnet.explorer.etherlink.com/tx/0x${parseTransactionHash(firebaseProposal?.executionHash)}` + : "" + } + }) + + setDaoProposals(onChainProposals) } if (firestoreData?.[daoMembersKey]) { @@ -279,7 +274,7 @@ const useEtherlinkDao = ({ network }: { network: string }) => { }, [daoProposalSelected?.id, daoSelected, daoSelected?.votingDuration, firebaseRootCollection, firestoreData]) useEffect(() => { - if (daoSelected?.id && firebaseRootCollection) { + if (daoSelected?.id && firebaseRootCollection && daoProposalSelected?.type !== "offchain") { fetchCollection(`${firebaseRootCollection}/${daoSelected.id}/proposals`) fetchCollection(`${firebaseRootCollection}/${daoSelected.id}/members`) console.log({ daoSelected }) @@ -296,30 +291,76 @@ const useEtherlinkDao = ({ network }: { network: string }) => { console.log("Treasury Data", ethBalance) }) - if (daoProposalSelected?.id) { + if (daoProposalSelected?.id && daoProposalSelected?.type !== "offchain") { fetchCollection(`${firebaseRootCollection}/${daoSelected?.id}/proposals/${daoProposalSelected?.id}/votes`) } } }, [daoProposalSelected?.id, daoSelected, fetchCollection, firebaseRootCollection]) - // useEffect(() => { - // if (!daoProposalSelected?.id) return - // fetchCollection(`${firebaseRootCollection}/${daoSelected?.id}/proposals/${daoProposalSelected?.id}/voters`) - // }, [daoProposalSelected, daoSelected, fetchCollection, firebaseRootCollection]) + useEffect(() => { + if (!daoSelected?.id) return + fetchOffchainProposals(daoSelected?.id).then(offchainProposals => { + console.log("offchainProposals", offchainProposals) + setDaoOffchainProposals(offchainProposals) + }) + }, [daoSelected?.id]) + + console.log("Proposal List", daoProposals) + const daoOffchainPollList = daoOffchainProposals.map(x => ({ + against: "tbd", + author: x.author, + callDataPlain: [], + callDatas: [], + calldata: "0x", + choices: x.choices, + createdAt: dayjs(x.createdAt), + description: x.description, + executionHash: "", + externalResource: x.externalLink, + hash: "", + id: x._id, + isVotingActive: true, // TBD, + latestStage: "pending", // TBD, + status: dayjs.unix(x.endTime).isAfter(dayjs()) ? "active" : "pending", + statusHistoryMap: [], + statusHistory: {}, + targets: [], + title: x.name, + totalVotes: new BigNumber(0), + totalVoteCount: 0, + timerLabel: "Voting starts in", + transactions: [], + txHash: "", + type: "offchain", + values: [], + votesAgainst: 0, + votesFor: 1, //TBD, + votesWeightPercentage: 0, // TBD + votingExpiresAt: dayjs.unix(x.endTime), + votingStartTimestamp: dayjs.unix(x.startTime) + })) + + const allDaoProposals = [...daoProposals, ...daoOffchainPollList].sort( + (a, b) => b.createdAt.unix() - a.createdAt.unix() + ) return { contractData, daos: daoData, daoSelected, daoRegistryDetails, - daoProposals, + daoProposals: allDaoProposals, daoProposalSelected, + daoProposalOffchainSelected, daoMembers, daoProposalVoters, selectDaoProposal: (proposalId: string) => { - const proposal = daoProposals.find((proposal: any) => proposal.id === proposalId) + const proposal = allDaoProposals.find((proposal: any) => proposal.id === proposalId) console.log("selectDaoProposal", proposal) - if (proposal && proposal?.type !== "contract call") { + if (proposal && proposal?.type === "offchain") { + console.log("Selecing Offchain Proposal", proposal) + // setDaoProposalOffchainSelected(proposal) + } else if (proposal && proposal?.type !== "contract call") { const proposalInterface = proposalInterfaces.find((x: any) => { let fbType = proposal?.type?.toLowerCase() if (fbType?.startsWith("mint")) fbType = "mint"