diff --git a/src/App.css b/src/App.css index a30d21d2..29ffe5b5 100644 --- a/src/App.css +++ b/src/App.css @@ -56,4 +56,14 @@ a { .proposal-details a:hover { text-decoration: underline; /* border-bottom: 1px solid #81feb7; */ +} + +:root { + --rsbs-backdrop-bg: rgba(0, 0, 0, 0.6); + --rsbs-bg: #1c2024; + --rsbs-handle-bg: hsla(0, 0%, 0%, 0.14); + --rsbs-max-w: auto; + --rsbs-ml: env(safe-area-inset-left); + --rsbs-mr: env(safe-area-inset-right); + --rsbs-overlay-rounded: 16px; } \ No newline at end of file diff --git a/src/modules/etherlink/components/EvmDaoSettingsModal.tsx b/src/modules/etherlink/components/EvmDaoSettingsModal.tsx index cb4016e6..fdc94e40 100644 --- a/src/modules/etherlink/components/EvmDaoSettingsModal.tsx +++ b/src/modules/etherlink/components/EvmDaoSettingsModal.tsx @@ -102,8 +102,8 @@ export const EvmDaoSettingModal: React.FC<{ {daoSelected?.id && - tableData.map((item: { key: string; value: string }) => ( - + tableData.map((item: { key: string; value: string }, index: number) => ( + {item.key} diff --git a/src/modules/etherlink/components/EvmRegistryTable.tsx b/src/modules/etherlink/components/EvmRegistryTable.tsx new file mode 100644 index 00000000..4af59396 --- /dev/null +++ b/src/modules/etherlink/components/EvmRegistryTable.tsx @@ -0,0 +1,169 @@ +import React, { useState } from "react" +import { + Button, + Grid, + styled, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Typography, + useMediaQuery, + useTheme +} from "@material-ui/core" +import dayjs from "dayjs" + +import { ContentContainer } from "modules/explorer/components/ContentContainer" + +const localizedFormat = require("dayjs/plugin/localizedFormat") +dayjs.extend(localizedFormat) + +const titles = ["Registry Items", "Value", "Last Updated"] + +interface RowData { + key: string + value: string + lastUpdated?: string + onClick: () => void +} + +interface Props { + data: RowData[] +} + +export const OverflowCell = styled(TableCell)({ + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + maxWidth: 300 +}) + +const MobileTableHeader = styled(Grid)({ + width: "100%", + padding: 20, + borderBottom: "0.3px solid #3D3D3D" +}) + +const MobileTableRow = styled(Grid)({ + padding: "30px", + borderBottom: "0.3px solid #3D3D3D" +}) + +const OverflowItem = styled(Grid)({ + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + maxWidth: 300 +}) + +const TableContainer = styled(ContentContainer)({ + width: "100%" +}) + +const titleDataMatcher = (title: (typeof titles)[number], rowData: RowData) => { + switch (title) { + case "Registry Items": + return rowData.key + case "Value": + return rowData.value + case "Last Updated": + return rowData.lastUpdated || "-" + } +} + +const MobileRegistryTable: React.FC = ({ data }) => { + return ( + + + + Registry + + + {data.map((rowData, i) => ( + rowData.onClick()} + style={{ gap: 19 }} + > + {titles.map((title, j) => ( + + + {title === "Registry Items" ? "Proposal Key" : title} + + + {titleDataMatcher(title, rowData)} + + + ))} + + + + + ))} + + ) +} + +const DesktopRegistryTable: React.FC = ({ data }) => { + return ( +
+ + + {titles.map((title, i) => ( + {title} + ))} + + + + {data.map((rowData, i) => ( + rowData.onClick()}> + {rowData.key.toUpperCase()} + {rowData.value} + {rowData.lastUpdated ? dayjs(rowData.lastUpdated).format("L") : "-"} + + + + + ))} + +
+ ) +} + +export const EvmRegistryTable: React.FC<{ data: RowData[] }> = ({ data }) => { + const theme = useTheme() + const isSmall = useMediaQuery(theme.breakpoints.down("sm")) + const [selectedItem, setSelectedItem] = useState() + const [open, setOpen] = useState(false) + + return ( + <> + + {isSmall ? : } + + + ) +} diff --git a/src/modules/etherlink/creator/EvmDaoSummary.tsx b/src/modules/etherlink/creator/EvmDaoSummary.tsx index 4a75e018..fa5e92a0 100644 --- a/src/modules/etherlink/creator/EvmDaoSummary.tsx +++ b/src/modules/etherlink/creator/EvmDaoSummary.tsx @@ -1,13 +1,79 @@ import { DescriptionText } from "components/ui/DaoCreator" import { TitleBlock } from "modules/common/TitleBlock" import React from "react" +import { + Grid, + styled, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + useMediaQuery, + useTheme +} from "@material-ui/core" import useEvmDaoCreateStore from "services/contracts/etherlinkDAO/hooks/useEvmDaoCreateStore" +import { CopyAddress } from "modules/common/CopyAddress" +import { CopyButton } from "modules/common/CopyButton" interface EvmDaoSummaryProps { // Add props as needed } +const CustomTableContainer = styled(TableContainer)(({ theme }) => ({ + width: "inherit", + [theme.breakpoints.down("sm")]: {} +})) + +const CustomTableCell = styled(TableCell)(({ theme }) => ({ + [theme.breakpoints.down("sm")]: { + paddingBottom: 0, + paddingLeft: "16px !important", + textAlign: "end" + } +})) + +const CustomTableCellValue = styled(TableCell)(({ theme }) => ({ + [theme.breakpoints.down("sm")]: { + paddingTop: 0, + paddingRight: "16px !important", + textAlign: "end", + paddingBottom: 0 + } +})) + +const RowValue = styled(Typography)(({ theme }) => ({ + fontWeight: 300, + fontSize: 18, + [theme.breakpoints.down("sm")]: { + fontSize: 16 + } +})) export const EvmDaoSummary: React.FC = () => { + const theme = useTheme() + const isMobileSmall = useMediaQuery(theme.breakpoints.down("sm")) + const { data } = useEvmDaoCreateStore() + const tableData = [ + { key: "Name", value: data?.name }, + { key: "Symbol", value: data?.governanceToken?.symbol }, + { + key: "Total Members", + value: data?.members.length + }, + { + key: "Total Supply", + value: data?.members.reduce( + (acc: number, member: { amountOfTokens: number }) => Number(acc) + Number(member.amountOfTokens), + 0 + ) + }, + { + key: "Quorum", + value: `${data?.quorum?.returnedTokenPercentage}%` + } + ] return (
= () => { These settings will define the summary for your DAO. } /> -
+
+ + + + {tableData.map((item: { key: string; value: string }) => ( + + + {item.key} + + + {typeof item.value === "string" && item?.value?.startsWith("0x") ? ( + + {isMobileSmall ? ( + + ) : ( + <> + + {item.value} + + + + )} + + ) : ( + + {item.value} + + )} + + + ))} + +
+
+
) } diff --git a/src/modules/etherlink/creator/EvmDaoTemplate.tsx b/src/modules/etherlink/creator/EvmDaoTemplate.tsx index 0e4bcbcc..61e36a3d 100644 --- a/src/modules/etherlink/creator/EvmDaoTemplate.tsx +++ b/src/modules/etherlink/creator/EvmDaoTemplate.tsx @@ -11,7 +11,7 @@ import useEvmDaoCreateStore from "services/contracts/etherlinkDAO/hooks/useEvmDa const LambdaCustomBox = styled(Grid)(({ theme }) => ({ "height": 273, "marginTop": 30, - "background": "#2F3438", + "background": "#1c2024", "borderRadius": 8, "maxWidth": 342, "width": "-webkit-fill-available", diff --git a/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalDetailsPage.tsx b/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalDetailsPage.tsx index 398b23b3..bb6d13fa 100644 --- a/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalDetailsPage.tsx +++ b/src/modules/etherlink/explorer/EtherlinkDAO/EvmProposalDetailsPage.tsx @@ -1,5 +1,16 @@ import { GridContainer } from "modules/common/GridContainer" -import { Button, Grid, TableRow, TableBody, Table, Typography, useMediaQuery, useTheme, TableCell } from "@mui/material" +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" @@ -13,6 +24,7 @@ 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" const RenderProposalAction = () => { const { daoProposalSelected } = useContext(EtherlinkContext) @@ -193,6 +205,8 @@ export const EvmProposalDetailsPage = () => { {daoProposalSelected?.proposalData?.map( ({ parameter, value }: { parameter: string; value: string }, idx: number) => { + console.log("callDataXYB", parameter, value) + const textValue = value?.length > 64 ? value.slice(0, 8) + "..." + value.slice(-4) : value return ( { Value - {value} +
+ {textValue} + navigator.clipboard.writeText(value)} + style={{ marginLeft: 8, padding: 4 }} + > + + +
diff --git a/src/modules/etherlink/explorer/EtherlinkDAO/EvmRegistryPage.tsx b/src/modules/etherlink/explorer/EtherlinkDAO/EvmRegistryPage.tsx index e2b88585..f48aa9ac 100644 --- a/src/modules/etherlink/explorer/EtherlinkDAO/EvmRegistryPage.tsx +++ b/src/modules/etherlink/explorer/EtherlinkDAO/EvmRegistryPage.tsx @@ -1,20 +1,20 @@ import { Button, Grid, Tooltip, useMediaQuery, useTheme } from "@material-ui/core" import { CopyAddress } from "modules/common/CopyAddress" -import { ProposalFormContainer, ProposalFormDefaultValues } from "modules/explorer/components/ProposalForm" import React, { useContext, useMemo, useState } from "react" import { useDAO } from "services/services/dao/hooks/useDAO" -import { useProposals } from "services/services/dao/hooks/useProposals" -import { LambdaProposal } from "services/services/dao/mappers/proposal/types" + import { Hero } from "modules/explorer/components/Hero" import { HeroTitle } from "modules/explorer/components/HeroTitle" import { RegistryTable } from "modules/explorer/pages/Registry/components/RegistryTable" import { useIsProposalButtonDisabled } from "../../../../services/contracts/baseDAO/hooks/useCycleInfo" import { InfoIcon } from "modules/explorer/components/styled/InfoIcon" import { MainButton } from "modules/common/MainButton" -import { LambdaDAO } from "services/contracts/baseDAO/lambdaDAO" import { useEtherlinkDAOID } from "../router" import { EtherlinkContext } from "services/wagmi/context" +import { useEvmProposalOps } from "services/contracts/etherlinkDAO/hooks/useEvmProposalOps" +import { useHistory } from "react-router-dom" +import { EvmRegistryTable } from "modules/etherlink/components/EvmRegistryTable" export const EvmRegistryPage: React.FC = () => { const theme = useTheme() @@ -22,64 +22,21 @@ export const EvmRegistryPage: React.FC = () => { const daoId = useEtherlinkDAOID() const { data: dao } = useDAO(daoId) const { daoSelected } = useContext(EtherlinkContext) - const [updateRegistryOpen, setUpdateRegistryOpen] = useState(false) - const { data: proposalsData } = useProposals(daoId) - const [defaultData, setDefaultData] = useState() const shouldDisable = useIsProposalButtonDisabled(daoId) + const { setMetadataFieldValue, setCurrentStep, setDaoRegistry } = useEvmProposalOps() + const history = useHistory() - const onCloseRegistryUpdate = () => { - setUpdateRegistryOpen(false) - setDefaultData(undefined) - } - - const proposals = useMemo(() => { - if (!proposalsData || !dao) { + const registryList = useMemo(() => { + if (!daoSelected?.registry) { return [] } - - const registryDAO = dao as LambdaDAO - const registryProposalsData = proposalsData as LambdaProposal[] - - const registryAffectedKeysProposalIds = registryDAO.decoded.decodedRegistryAffected.map(r => r.proposalId) - - return registryProposalsData - .filter(proposal => registryAffectedKeysProposalIds.includes(proposal.id)) - .sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime()) - .map(proposal => ({ - ...proposal, - description: "Proposal description", - address: proposal.id, - lastUpdated: proposal.startDate, - list: proposal.metadata.list, - proposalId: proposal.id, - agoraPostId: Number(proposal.metadata.agoraPostId) - })) - .flatMap(proposal => - proposal.list.map(({ key }) => ({ - ...proposal, - key - })) - ) - }, [dao, proposalsData]) - - const onClickItem = (item: { key: string; value: string }) => { - setDefaultData({ - registryUpdateForm: { - isBatch: false, - list: [item] - } - }) - setUpdateRegistryOpen(true) - } - - const registryList = Object.entries(daoSelected?.registry).map(([key, value]) => ({ - key, - value: value as string, - onClick: () => {}, - lastUpdated: "" - })) - - console.log("registryList", registryList) + return Object.entries(daoSelected?.registry).map(([key, value]) => ({ + key, + value: value as string, + onClick: () => {}, + lastUpdated: "" + })) + }, [daoSelected?.registry]) return ( <> @@ -101,7 +58,11 @@ export const EvmRegistryPage: React.FC = () => { setUpdateRegistryOpen(true)} + onClick={() => { + setMetadataFieldValue("type", "edit_registry") + setCurrentStep(1) + history.push(`${window.location.pathname.slice(0, -8)}proposals`) + }} disabled={shouldDisable} > New Item @@ -114,15 +75,22 @@ export const EvmRegistryPage: React.FC = () => { - + ({ + key: rItem.key, + value: rItem.value, + lastUpdated: rItem.lastUpdated, + onClick: () => { + setMetadataFieldValue("type", "edit_registry") + setDaoRegistry("key", rItem.key) + setDaoRegistry("value", rItem.value) + setCurrentStep(1) + history.push(`${window.location.pathname.slice(0, -8)}proposals`) + } + }))} + /> - {/* */} ) } diff --git a/src/modules/etherlink/explorer/EtherlinkDAO/EvmUserPage.tsx b/src/modules/etherlink/explorer/EtherlinkDAO/EvmUserPage.tsx index a40ff638..ec3885e7 100644 --- a/src/modules/etherlink/explorer/EtherlinkDAO/EvmUserPage.tsx +++ b/src/modules/etherlink/explorer/EtherlinkDAO/EvmUserPage.tsx @@ -17,7 +17,7 @@ const StatsContainer = styled(Grid)({ const StatBox = styled(Box)({ padding: "20px", - background: "#2F3438", + background: "#1c2024", borderRadius: "8px", flex: 1, minWidth: 0 @@ -36,7 +36,7 @@ const StatValue = styled(Typography)({ }) const DelegationBox = styled(Box)(({ theme }) => ({ - background: "#2F3438", + background: "#1c2024", borderRadius: "8px", padding: "32px", marginBottom: "20px", diff --git a/src/modules/etherlink/explorer/EvmProposals/EvmOffchainDebate.tsx b/src/modules/etherlink/explorer/EvmProposals/EvmOffchainDebate.tsx new file mode 100644 index 00000000..48958e18 --- /dev/null +++ b/src/modules/etherlink/explorer/EvmProposals/EvmOffchainDebate.tsx @@ -0,0 +1,145 @@ +import React, { useState } from "react" +import { Grid, styled, Typography, IconButton, FormControlLabel, Switch } from "@material-ui/core" +import { Add as AddIcon, RemoveCircleOutline } from "@material-ui/icons" +import { StyledTextField } from "components/ui/StyledTextField" + +const InputContainer = styled(Grid)({ + background: "#1c2024", + padding: "16px", + borderRadius: "4px", + marginBottom: "8px" +}) + +const AddButton = styled(Grid)({ + background: "#1c2024", + padding: "12px", + borderRadius: "4px", + cursor: "pointer", + display: "flex", + alignItems: "center", + justifyContent: "center", + marginTop: "16px" +}) + +const RemoveButton = styled(IconButton)({ + color: "#FF4D4D", + padding: "4px" +}) + +export const EvmOffchainDebate: React.FC = () => { + const [duration, setDuration] = useState({ + days: "", + hours: "", + minutes: "" + }) + + const [choices, setChoices] = useState(["", ""]) + const [isMultiChoice, setIsMultiChoice] = useState(false) + + const handleDurationChange = (field: string, value: string) => { + setDuration(prev => ({ + ...prev, + [field]: value + })) + } + + const handleChoiceChange = (index: number, value: string) => { + const newChoices = [...choices] + newChoices[index] = value + setChoices(newChoices) + } + + const addChoice = () => { + setChoices([...choices, ""]) + } + + const removeChoice = (index: number) => { + if (choices.length <= 2) return + const newChoices = choices.filter((_, i) => i !== index) + setChoices(newChoices) + } + + return ( + + + + + Poll Duration + + + + handleDurationChange("days", e.target.value)} + inputProps={{ min: 0 }} + /> + + + handleDurationChange("hours", e.target.value)} + inputProps={{ min: 0, max: 23 }} + /> + + + handleDurationChange("minutes", e.target.value)} + inputProps={{ min: 0, max: 59 }} + /> + + + + + + + Poll Options + + setIsMultiChoice(e.target.checked)} color="primary" /> + } + style={{ color: "#fff" }} + label={isMultiChoice ? "Multi Choice" : "Single Choice"} + /> + + {choices.map((choice, index) => ( + + + handleChoiceChange(index, e.target.value)} + /> + + + {choices.length > 2 && ( + removeChoice(index)}> + + + )} + + + ))} + + + + + Add Option + + + + + + ) +} diff --git a/src/modules/etherlink/explorer/EvmProposals/EvmPropContractCall.tsx b/src/modules/etherlink/explorer/EvmProposals/EvmPropContractCall.tsx index 6d691934..d12fedf9 100644 --- a/src/modules/etherlink/explorer/EvmProposals/EvmPropContractCall.tsx +++ b/src/modules/etherlink/explorer/EvmProposals/EvmPropContractCall.tsx @@ -4,7 +4,7 @@ import { StyledTextField } from "components/ui/StyledTextField" import { useEvmProposalOps } from "services/contracts/etherlinkDAO/hooks/useEvmProposalOps" const InputContainer = styled(Grid)({ - background: "#2F3438", + background: "#1c2024", padding: "16px", borderRadius: "4px", marginBottom: "8px" @@ -33,7 +33,7 @@ export const EvmPropContractCall: React.FC = () => { onChange={e => setDaoContractCall("value", e.target.value)} /> - + {/* { variant="outlined" onChange={e => setDaoContractCall("functionDefinition", e.target.value)} /> - + */} ({ @@ -48,6 +49,7 @@ const TitleContainer = styled(Grid)({ }) const renderModal = (modal: string) => { + console.log("renderModal", modal) switch (modal) { case "transfer_assets": return @@ -59,6 +61,8 @@ const renderModal = (modal: string) => { return case "token_operation": return + case "off_chain_debate": + return default: return null } diff --git a/src/modules/explorer/components/ResponsiveDialog.tsx b/src/modules/explorer/components/ResponsiveDialog.tsx index 3e7cca8e..60eca368 100644 --- a/src/modules/explorer/components/ResponsiveDialog.tsx +++ b/src/modules/explorer/components/ResponsiveDialog.tsx @@ -51,7 +51,11 @@ export const ResponsiveDialog: React.FC<{ return isSmall ? ( - + {onGoBack !== undefined ? ( diff --git a/src/services/contracts/etherlinkDAO/hooks/useEvmProposalOps.tsx b/src/services/contracts/etherlinkDAO/hooks/useEvmProposalOps.tsx index 169953e5..f87f2f78 100644 --- a/src/services/contracts/etherlinkDAO/hooks/useEvmProposalOps.tsx +++ b/src/services/contracts/etherlinkDAO/hooks/useEvmProposalOps.tsx @@ -188,8 +188,42 @@ const useEvmProposalCreateZustantStore = create()( functionDefinition: "", callData: "" }, + // TODO: @ashutoshpw Fix this setDaoContractCall: (type: "targetAddress" | "value" | "functionDefinition" | "callData", value: string) => { set({ daoContractCall: { ...get().daoContractCall, [type]: value } }) + if (type === "callData") { + set({ + createProposalPayload: { + ...get().createProposalPayload, + calldatas: [value] + } + }) + } + if (type === "value") { + set({ + createProposalPayload: { + ...get().createProposalPayload, + values: [Number(value)] + } + }) + } + if (type === "targetAddress") { + set({ + createProposalPayload: { + ...get().createProposalPayload, + targets: [value] + } + }) + } + // const selectedInterface = proposalInterfaces.find(p => p.name === "contractCall") + // if (!selectedInterface) return + // const iface = new ethers.Interface(selectedInterface.interface) + // const encodedData = iface.encodeFunctionData(selectedInterface.name, [ + // get().daoContractCall.targetAddress, + // get().daoContractCall.value, + // get().daoContractCall.functionDefinition, + // get().daoContractCall.callData + // ]) }, daoConfig: { type: "", @@ -391,7 +425,7 @@ export const useEvmProposalOps = () => { const createProposal = useCallback( async (payload: Record) => { if (!daoSelected || !daoContract) return - + console.log("createProposal", payload) const tx = await daoContract.propose(...Object.values(payload)) console.log("Proposal transaction sent:", tx.hash) const receipt = await tx.wait() diff --git a/src/services/wagmi/context.tsx b/src/services/wagmi/context.tsx index 6cd84f7e..c5dbdb72 100644 --- a/src/services/wagmi/context.tsx +++ b/src/services/wagmi/context.tsx @@ -318,7 +318,8 @@ const useEtherlinkDao = ({ network }: { network: string }) => { daoProposalVoters, selectDaoProposal: (proposalId: string) => { const proposal = daoProposals.find((proposal: any) => proposal.id === proposalId) - if (proposal) { + console.log("selectDaoProposal", proposal) + if (proposal && proposal?.type !== "contract call") { const proposalInterface = proposalInterfaces.find((x: any) => { let fbType = proposal?.type?.toLowerCase() if (fbType?.startsWith("mint")) fbType = "mint" @@ -326,13 +327,17 @@ const useEtherlinkDao = ({ network }: { network: string }) => { return x.tags?.includes(fbType) }) const functionAbi = proposalInterface?.interface?.[0] as string + console.log("functionAbi", functionAbi) if (!functionAbi) return [] const proposalData = proposalInterface ? proposal?.callDataPlain?.map((callData: any) => { + console.log("callDataXYB", callData) const formattedCallData = callData.startsWith("0x") ? callData : `0x${callData}` const decodedDataPair = decodeCalldataWithEthers(functionAbi, formattedCallData) const decodedDataPairLegacy = decodeFunctionParametersLegacy(functionAbi, formattedCallData) + console.log("decodedDataPair", decodedDataPair) + console.log("decodedDataPairLegacy", decodedDataPairLegacy) const functionName = decodedDataPair?.functionName const functionParams = decodedDataPair?.decodedData const proposalInterface = proposalInterfaces.find((x: any) => x.name === functionName) @@ -345,6 +350,14 @@ const useEtherlinkDao = ({ network }: { network: string }) => { : [] proposal.proposalData = proposalData setDaoProposalSelected(proposal) + } else if (proposal?.type === "contract call") { + proposal.proposalData = [ + { + parameter: `Contract Call from ${proposal?.targets?.[0]}`, + value: proposal?.callDataPlain?.[0] + } + ] + setDaoProposalSelected(proposal) } }, selectDao: (daoId: string) => {