-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7a2eb23
commit 11cd5b8
Showing
2 changed files
with
67 additions
and
237 deletions.
There are no files selected for viewing
145 changes: 10 additions & 135 deletions
145
plugins/lockToVote/components/proposal/description.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,159 +1,34 @@ | ||
import { Action } from "@/utils/types"; | ||
import { Proposal } from "@/plugins/lockToVote/utils/types"; | ||
import { whatsabi } from "@shazow/whatsabi"; | ||
import { ReactNode, useCallback, useEffect, useState } from "react"; | ||
import { usePublicClient } from "wagmi"; | ||
import { Address, decodeFunctionData } from "viem"; | ||
import { Else, If, IfCase, IfNot, Then } from "@/components/if"; | ||
import { PleaseWaitSpinner } from "@/components/please-wait"; | ||
import { AddressText } from "@/components/text/address"; | ||
import { isAddress } from "@/utils/evm"; | ||
import * as DOMPurify from "dompurify"; | ||
import { PUB_CHAIN, PUB_ETHERSCAN_API_KEY } from "@/constants"; | ||
import { ActionCard } from "@/components/actions/action"; | ||
import { If } from "@/components/if"; | ||
|
||
const DEFAULT_PROPOSAL_SUMMARY = "(No description available)"; | ||
|
||
type FunctionData = { | ||
args: readonly unknown[] | undefined; | ||
functionName: string; | ||
to: Address; | ||
}; | ||
const DEFAULT_PROPOSAL_METADATA_SUMMARY = "(No description available)"; | ||
|
||
export default function ProposalDescription(proposal: Proposal) { | ||
const publicClient = usePublicClient({ chainId: PUB_CHAIN.id }); | ||
const [decodedActions, setDecodedActions] = useState<FunctionData[]>([]); | ||
const proposalActions = proposal?.actions || []; | ||
|
||
const getFunctionData = async (action: Action) => { | ||
if (!publicClient) return; | ||
|
||
const abiLoader = new whatsabi.loaders.EtherscanABILoader({ | ||
apiKey: PUB_ETHERSCAN_API_KEY, | ||
}); | ||
|
||
const { abi } = await whatsabi.autoload(action.to, { | ||
provider: publicClient, | ||
abiLoader, | ||
followProxies: true, | ||
}); | ||
|
||
return decodeFunctionData({ | ||
abi, | ||
data: action.data as Address, | ||
}); | ||
}; | ||
|
||
const fetchActionData = useCallback(async () => { | ||
const decodedActions = await Promise.all( | ||
proposalActions.map(async (action) => { | ||
let functionData: any; | ||
if (action.data != "0x") { | ||
functionData = await getFunctionData(action); | ||
} else { | ||
functionData = { functionName: "transfer", args: [action.value] }; | ||
} | ||
return { ...functionData, to: action.to } as FunctionData; | ||
}) | ||
); | ||
setDecodedActions(decodedActions); | ||
}, [proposal]); | ||
|
||
useEffect(() => { | ||
fetchActionData(); | ||
}, [proposal.actions]); | ||
|
||
return ( | ||
<div className="pt-2"> | ||
<div | ||
className="pb-6" | ||
dangerouslySetInnerHTML={{ | ||
__html: proposal.summary | ||
? DOMPurify.sanitize(proposal.summary) | ||
: DEFAULT_PROPOSAL_SUMMARY, | ||
: DEFAULT_PROPOSAL_METADATA_SUMMARY, | ||
}} | ||
/> | ||
<h2 className="flex-grow text-2xl text-neutral-900 font-semibold pt-10 pb-3"> | ||
Actions | ||
</h2> | ||
<div className="flex flex-row space-between"> | ||
<IfNot condition={proposalActions.length}> | ||
<div className=""> | ||
<If not={proposal.actions.length}> | ||
<p className="pt-2">The proposal has no actions</p> | ||
</IfNot> | ||
<If condition={proposalActions.length && !decodedActions?.length}> | ||
<PleaseWaitSpinner /> | ||
</If> | ||
{decodedActions?.map?.((action, i) => ( | ||
<ActionCard | ||
key={`${i}-${action.to}-${action.functionName}`} | ||
action={action} | ||
idx={i} | ||
/> | ||
{proposal.actions?.map?.((action, i) => ( | ||
<div className="mb-3" key={i}> | ||
<ActionCard action={action} idx={i} /> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
// This should be encapsulated as soon as ODS exports this widget | ||
const Card = function ({ children }: { children: ReactNode }) { | ||
return ( | ||
<div | ||
className="p-4 lg:p-6 w-full flex flex-col space-y-6 | ||
box-border border border-neutral-100 | ||
focus:outline-none focus:ring focus:ring-primary | ||
bg-neutral-0 rounded-xl" | ||
> | ||
{children} | ||
</div> | ||
); | ||
}; | ||
|
||
const ActionCard = function ({ | ||
action, | ||
idx, | ||
}: { | ||
action: FunctionData; | ||
idx: number; | ||
}) { | ||
return ( | ||
<Card> | ||
<div className="flex flex-row space-between"> | ||
<div className=""> | ||
<h3>Target contract</h3> | ||
<p> | ||
<AddressText>{action.to}</AddressText> | ||
</p> | ||
</div> | ||
<div className="w-7 h-7 text-center border border-primary-600 text-primary-500 rounded-lg ml-auto"> | ||
{idx + 1} | ||
</div> | ||
</div> | ||
|
||
<div> | ||
<h3>Function name</h3> | ||
<p> | ||
<code>{action.functionName}</code> | ||
</p> | ||
</div> | ||
|
||
<div> | ||
<h3>Parameters</h3> | ||
<ul className="list-disc pl-4"> | ||
{action?.args?.length && | ||
action?.args?.map((arg: any, j: number) => ( | ||
<li key={`arg-${j}`}> | ||
<IfCase condition={isAddress(arg)}> | ||
<Then> | ||
<AddressText>{arg.toString()}</AddressText> | ||
</Then> | ||
<Else> | ||
<code>{arg.toString()}</code> | ||
</Else> | ||
</IfCase> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
</Card> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters