diff --git a/src/components/ui/etherlink/ProposalDialog.tsx b/src/components/ui/etherlink/ProposalDialog.tsx
index 68495a4ff..007056c1b 100644
--- a/src/components/ui/etherlink/ProposalDialog.tsx
+++ b/src/components/ui/etherlink/ProposalDialog.tsx
@@ -1,13 +1,14 @@
import { Grid, Typography, styled } from "@mui/material"
export const OptionContainer = styled(Grid)(({ theme }) => ({
- "minHeight": 80,
+ "minHeight": 110,
"background": theme.palette.primary.main,
"borderRadius": 8,
"padding": "35px 42px",
- "marginBottom": 16,
"cursor": "pointer",
- "height": 110,
+ // fill the grid cell so cards in a row share the height of the tallest description
+ "height": "100%",
+ "boxSizing": "border-box",
"&:hover:enabled": {
background: theme.palette.secondary.dark,
scale: 1.01,
diff --git a/src/modules/etherlink/components/EvmResponsiveDialog.tsx b/src/modules/etherlink/components/EvmResponsiveDialog.tsx
index 7bc95b187..42229c8f6 100644
--- a/src/modules/etherlink/components/EvmResponsiveDialog.tsx
+++ b/src/modules/etherlink/components/EvmResponsiveDialog.tsx
@@ -24,6 +24,7 @@ const TitleText = styled(Typography)(({ theme }) => ({
const CustomDialog = styled(Dialog)({
"& .MuiDialog-paperWidthMd": {
width: "-webkit-fill-available",
+ maxWidth: "960px !important",
maxHeight: "calc(100vh - 96px)",
display: "flex",
flexDirection: "column"
diff --git a/src/modules/etherlink/explorer/EvmProposalsActionDialog.tsx b/src/modules/etherlink/explorer/EvmProposalsActionDialog.tsx
index d1d97d65d..a03c470a3 100644
--- a/src/modules/etherlink/explorer/EvmProposalsActionDialog.tsx
+++ b/src/modules/etherlink/explorer/EvmProposalsActionDialog.tsx
@@ -84,7 +84,7 @@ export const EvmProposalsActionDialog = ({ open, handleClose }: { open: boolean;
Select Proposal Type
-
+
{availableOptions.map((option: any, index) => (
- We need to deploy the ACI Deployer Contract
-
- Deploy ACI Deployer Contract
-
-
+
+
+ In order to use open-ended Contract Calls, the DAO contract must be amended.
+
+
+ If you have the minimum amount of tokens for the proposal fee, click Submit to create a proposal for adding
+ the Contract Call capability. Once that proposal is executed, this form becomes available.
+
+
+
+
+
+
+
)
}
@@ -474,11 +483,16 @@ const ContractInteractionForm = ({
})
console.log("Lambda Param", param)
+ // the form takes tez, the lambda pushes mutez
+ const amountMutez = new BigNumber(values.amount || 0)
+ .multipliedBy(1_000_000)
+ .integerValue(BigNumber.ROUND_DOWN)
+ .toNumber()
const lambda = generateExecuteContractMichelson("1.0.0", {
address: values.destination_contract_address,
entrypoint,
type,
- amount: values.amount,
+ amount: amountMutez,
param
})
@@ -534,7 +548,6 @@ export const ArbitraryContractInteractionForm: React.FC<{
showHeader: (state: boolean) => void
}> = ({ daoLambdas, showHeader }) => {
const daoId = useDAOID()
- const [aciProposalOpen, setAciProposalOpen] = useState(false)
const isInvalidKtOrTzAddress = (address: string) => validateContractAddress(address) !== 3
const initialValue: ACIValues = {
@@ -578,8 +591,6 @@ export const ArbitraryContractInteractionForm: React.FC<{
console.log({ daoLambdas })
}, [daoLambdas])
- const isAciDeployerDeployed = daoLambdas?.find((lambda: any) => lambda.key === AppConfig.ACI.EXECUTOR_FUNCTION_NAME)
-
return (
- {daoLambdas !== undefined && (
- setAciProposalOpen(false)}
- title={"Proposal to Enable Arbitrary Contract Interaction"}
- template="sm"
- >
- In order to use open-ended Contract Calls, the DAO contract must be amended.
-
- If you have the minimum amount of tokens for the proposal fee, click Submit to create a proposal for
- adding the ACI capability.
-
-
-
-
-
- )}
)
}}
diff --git a/src/modules/explorer/components/ConfigProposalFormLambda.tsx b/src/modules/explorer/components/ConfigProposalFormLambda.tsx
index cdd417b1c..49f414fea 100644
--- a/src/modules/explorer/components/ConfigProposalFormLambda.tsx
+++ b/src/modules/explorer/components/ConfigProposalFormLambda.tsx
@@ -417,9 +417,14 @@ export const ProposalFormLambda: React.FC<{
return ProposalAction[action] + " Function Proposal"
}
+ // Without the aci_executor handler the Contract Call dialog only shows a short explanation and a
+ // Submit button; the "md" template stretches to the full viewport, so use the compact one there.
+ const isAciEnabled = !!daoLambdas?.find(lambda => lambda.key === AppConfig.ACI.EXECUTOR_FUNCTION_NAME)
+ const dialogTemplate = action === ProposalAction.aci && daoLambdas !== undefined && !isAciEnabled ? "sm" : "md"
+
return (
-
+
{state === LambdaProposalState.write_action ? (
<>
diff --git a/src/modules/explorer/components/ResponsiveDialog.tsx b/src/modules/explorer/components/ResponsiveDialog.tsx
index 235b750fa..805e0ebbd 100644
--- a/src/modules/explorer/components/ResponsiveDialog.tsx
+++ b/src/modules/explorer/components/ResponsiveDialog.tsx
@@ -22,7 +22,8 @@ const TitleText = styled(Typography)(({ theme }) => ({
const CustomDialog = styled(Dialog)({
"& .MuiDialog-paperWidthMd": {
width: "-webkit-fill-available",
- height: "-webkit-fill-available"
+ height: "auto",
+ maxWidth: "960px !important"
},
"& .MuiDialog-paperWidthXs": {
width: "-webkit-fill-available",
diff --git a/src/modules/explorer/components/VoteDialog.tsx b/src/modules/explorer/components/VoteDialog.tsx
index 252e2584e..eef8b4bcb 100644
--- a/src/modules/explorer/components/VoteDialog.tsx
+++ b/src/modules/explorer/components/VoteDialog.tsx
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo } from "react"
import { Grid, Typography, TextField, Button, useTheme, styled } from "@mui/material"
-import { useParams } from "react-router-dom"
+import { Link, useParams } from "react-router-dom"
import { useVote } from "services/contracts/baseDAO/hooks/useVote"
import BigNumber from "bignumber.js"
import { useDAO } from "services/services/dao/hooks/useDAO"
@@ -13,6 +13,8 @@ import { ProposalFormInput } from "./ProposalFormInput"
import { getUserTokenBalance } from "services/bakingBad/tokenBalances"
import { useTezos } from "services/beacon/hooks/useTezos"
import { useTezosBalance } from "services/contracts/baseDAO/hooks/useTezosBalance"
+import { useProposals } from "services/services/dao/hooks/useProposals"
+import { ProposalStatus } from "services/services/dao/mappers/proposal/types"
const CustomLabelsContainer = styled(Grid)({
marginBottom: 12
@@ -31,6 +33,14 @@ const CustomMaxLabel = styled(Typography)({
marginLeft: 12
})
+const HintText = styled(Typography)(({ theme }) => ({
+ "fontSize": 14,
+ "marginTop": 12,
+ "color": theme.palette.text.primary,
+ "opacity": 0.75,
+ "& a": { color: "inherit", textDecoration: "underline" }
+}))
+
const ErrorText = styled(Typography)({
fontSize: 13,
marginTop: 8,
@@ -82,6 +92,17 @@ export const VoteDialog: React.FC<{
}, [amount, dao, mutate, onClose, proposalId, support, validate])
const { account } = useTezos()
+ // votes stay staked after a proposal is executed or dropped until the holder releases them
+ const { data: executedProposals } = useProposals(daoId, ProposalStatus.EXECUTED)
+ const { data: droppedProposals } = useProposals(daoId, ProposalStatus.DROPPED)
+ const releasableStake = useMemo(
+ () =>
+ (executedProposals ?? []).concat(droppedProposals ?? []).reduce((acc, finishedProposal) => {
+ const vote = finishedProposal.voters.find(voter => voter.address.toLowerCase() === account?.toLowerCase())
+ return vote && vote.staked ? acc.plus(vote.value) : acc
+ }, new BigNumber(0)),
+ [executedProposals, droppedProposals, account]
+ )
useMemo(() => {
const found = ledger?.find(l => l.holder.address.toLowerCase() === account.toLowerCase())
@@ -157,6 +178,12 @@ export const VoteDialog: React.FC<{
/>
{error ? Amount cannot be greater than max available : null}
+ {releasableStake.gt(0) ? (
+
+ {releasableStake.toFormat()} {dao?.data.token.symbol} are still staked on finished proposals. Release
+ them with Unstake on the User page to vote with them.
+
+ ) : null}
diff --git a/src/modules/explorer/pages/ProposalDetails/index.tsx b/src/modules/explorer/pages/ProposalDetails/index.tsx
index 70c70b507..c34e1eb4d 100644
--- a/src/modules/explorer/pages/ProposalDetails/index.tsx
+++ b/src/modules/explorer/pages/ProposalDetails/index.tsx
@@ -160,6 +160,19 @@ const formatConfig = {
trimMantissa: true
}
+const proposalKindLabel = (proposal: Proposal | undefined): string => {
+ if (!proposal || !(proposal instanceof LambdaProposal)) {
+ return "Treasury Proposal"
+ }
+ const { lambdaType, lambdaHandler } = proposal.metadata
+ if (lambdaType === "add_handler") return "Add Function Proposal"
+ if (lambdaType === "remove_handler") return "Remove Function Proposal"
+ if (lambdaType === "execute_handler") {
+ return lambdaHandler?.handler_name === "aci_executor" ? "Contract Call Proposal" : "Execute Function Proposal"
+ }
+ return "Proposal"
+}
+
export const ProposalDetails: React.FC = () => {
const { proposalId } = useParams<{
proposalId: string
@@ -333,7 +346,7 @@ export const ProposalDetails: React.FC = () => {
- Treasury Proposal • Created by
+ {proposalKindLabel(proposal)} • Created by
{proposal && cycleInfo && (
{
- const [isLoading, setIsLoading] = useState(true)
- const [proposalData, setProposalData] = useState([])
- const { data: daoData, isLoading: isLoadingDAO, error, cycleInfo } = useDAO(contractAddress)
+ const { data: daoData, error, cycleInfo } = useDAO(contractAddress)
const queryResults = useQuery({
- queryKey: ["proposals", contractAddress, status],
- queryFn: async () => {
- const dao = daoData as BaseDAO
- const proposals = await getProposals(dao)
-
- if (!status) {
- return proposals
- }
-
- return proposals.filter(
- proposalData => proposalData.getStatus((cycleInfo as CycleInfo).currentLevel).status === status
- )
- },
+ queryKey: ["proposals", contractAddress],
+ queryFn: async () => getProposals(daoData as BaseDAO),
refetchInterval: 30000,
- enabled: !!daoData && !!cycleInfo
+ enabled: !!daoData
})
- useEffect(() => {
- if (queryResults.data) {
- setProposalData(queryResults.data)
+ const data = useMemo(() => {
+ const proposals = queryResults.data ?? []
+ if (!status) {
+ return proposals
+ }
+ if (!cycleInfo) {
+ return []
}
- }, [queryResults.data])
+ return proposals.filter(proposal => proposal.getStatus((cycleInfo as CycleInfo).currentLevel).status === status)
+ }, [queryResults.data, status, cycleInfo])
return {
- data: proposalData,
- isLoading: isLoading || queryResults.isLoading,
+ data,
+ isLoading: queryResults.isLoading,
error: error || queryResults.error
}
}