-
Notifications
You must be signed in to change notification settings - Fork 6
fix: governance frontend fixes #1702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
06e511f
2f8a84b
bb10db3
3a38db5
4ae67ce
5c6cb0c
033353d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,10 +7,36 @@ import { useState } from "react"; | |
|
|
||
| import { useDecodeCalldata } from "@/features/governance/hooks/useDecodeCalldata"; | ||
| import { BlankSlate, Button } from "@/shared/components"; | ||
| import { EnsAvatar } from "@/shared/components/design-system/avatars/ens-avatar/EnsAvatar"; | ||
| import { DefaultLink } from "@/shared/components/design-system/links/default-link"; | ||
| import daoConfigByDaoId from "@/shared/dao-config"; | ||
| import type { DaoIdEnum } from "@/shared/types/daos"; | ||
|
|
||
| const ETH_ADDRESS_REGEX = /(0x[0-9a-fA-F]{40})(?![0-9a-fA-F])/g; | ||
|
|
||
| const isEthAddress = (segment: string) => /^0x[0-9a-fA-F]{40}$/.test(segment); | ||
|
|
||
| const CalldataWithEns = ({ text }: { text: string }) => { | ||
| const segments = text.split(ETH_ADDRESS_REGEX).filter(Boolean); | ||
|
|
||
| return ( | ||
| <> | ||
| {segments.map((segment, i) => | ||
| isEthAddress(segment) ? ( | ||
| <EnsAvatar | ||
| key={`ens-${i}`} | ||
| address={segment as `0x${string}`} | ||
| showAvatar={false} | ||
| nameClassName="text-secondary font-mono text-sm font-normal not-italic leading-5" | ||
|
Comment on lines
+26
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Rendering decoded calldata addresses through Useful? React with 👍 / 👎. |
||
| /> | ||
| ) : ( | ||
| <span key={`text-${i}`}>{segment}</span> | ||
| ), | ||
| )} | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| export const ActionsTabContent = ({ | ||
| proposal, | ||
| }: { | ||
|
|
@@ -90,30 +116,40 @@ const ActionItem = ({ | |
| </div> | ||
| <div className="flex w-full flex-col gap-3 p-3"> | ||
| <div className="flex w-full gap-2"> | ||
| <p className="min-w-[88px] font-mono text-sm font-normal not-italic leading-5"> | ||
| <p className="min-w-22 font-mono text-sm font-normal not-italic leading-5"> | ||
| target: | ||
| </p> | ||
| <DefaultLink | ||
| href={`${blockExplorerUrl}/address/${target}`} | ||
| openInNewTab | ||
| className="text-secondary break-all font-mono text-sm font-normal not-italic leading-5" | ||
| className="font-mono text-sm font-normal not-italic leading-5" | ||
| > | ||
| {target} | ||
| {target && ( | ||
| <EnsAvatar | ||
| address={target as `0x${string}`} | ||
| showAvatar={false} | ||
| nameClassName="text-secondary font-mono text-sm font-normal not-italic leading-5" | ||
| /> | ||
| )} | ||
| </DefaultLink> | ||
| </div> | ||
| <div className="flex w-full gap-2"> | ||
| <p className="min-w-[88px] shrink-0 font-mono text-sm font-normal not-italic leading-5"> | ||
| <p className="min-w-22 shrink-0 font-mono text-sm font-normal not-italic leading-5"> | ||
| calldata: | ||
| </p> | ||
| <div className="border-border-contrast relative min-w-0 flex-1 border"> | ||
| <div className="scrollbar-thin max-h-[248px] overflow-y-auto p-3"> | ||
| <p | ||
| className={`text-secondary font-mono text-sm font-normal not-italic leading-5 ${ | ||
| isDecoded && !isLoading ? "whitespace-pre-wrap" : "break-all" | ||
| } ${isLoading ? "animate-pulse" : ""}`} | ||
| > | ||
| {displayCalldata} | ||
| </p> | ||
| <div className="scrollbar-thin max-h-62 overflow-y-auto p-3"> | ||
| {isDecoded && !isLoading && decodedCalldata ? ( | ||
| <div className="text-secondary whitespace-pre-wrap font-mono text-sm font-normal not-italic leading-5"> | ||
| <CalldataWithEns text={decodedCalldata} /> | ||
| </div> | ||
| ) : ( | ||
| <p | ||
| className={`text-secondary break-all font-mono text-sm font-normal not-italic leading-5 ${isLoading ? "animate-pulse" : ""}`} | ||
| > | ||
| {displayCalldata} | ||
| </p> | ||
| )} | ||
| </div> | ||
| {calldata && ( | ||
| <Button | ||
|
|
@@ -129,7 +165,7 @@ const ActionItem = ({ | |
| </div> | ||
| </div> | ||
| <div className="flex w-full gap-2"> | ||
| <p className="min-w-[88px] font-mono text-sm font-normal not-italic leading-5"> | ||
| <p className="min-w-22 font-mono text-sm font-normal not-italic leading-5"> | ||
| value: | ||
| </p> | ||
| <p className="text-secondary font-mono text-sm font-normal not-italic leading-5"> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,16 @@ | ||
| import type { GetProposalQuery } from "@anticapture/graphql-client"; | ||
| import { ExternalLink } from "lucide-react"; | ||
| import Link from "next/link"; | ||
|
|
||
| interface ProposalTimelineProps { | ||
| proposal: NonNullable<GetProposalQuery["proposal"]>; | ||
| blockExplorerUrl?: string; | ||
| } | ||
|
|
||
| export const ProposalTimeline = ({ | ||
| proposal, | ||
| }: { | ||
| proposal: NonNullable<GetProposalQuery["proposal"]>; | ||
| }) => { | ||
| blockExplorerUrl = "https://etherscan.io", | ||
| }: ProposalTimelineProps) => { | ||
| const now = Date.now() / 1000; | ||
| const createdTime = parseInt(proposal.timestamp); | ||
| const startTime = parseInt(proposal.startTimestamp); | ||
|
|
@@ -26,25 +32,59 @@ export const ProposalTimeline = ({ | |
| return "pending"; | ||
| }; | ||
|
|
||
| const proposalStatus = proposal.status.toLowerCase(); | ||
| const isQueued = | ||
| proposalStatus === "queued" || | ||
| proposalStatus === "pending_execution" || | ||
| proposalStatus === "executed"; | ||
|
Comment on lines
+36
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The timeline logic omits Useful? React with 👍 / 👎.
brunod-e marked this conversation as resolved.
|
||
| const isExecuted = proposalStatus === "executed"; | ||
|
|
||
| const timelineItems = [ | ||
| { | ||
| label: "Created", | ||
| timestamp: createdTime, | ||
| date: formatTimestamp(createdTime), | ||
| status: getTimelineItemStatus(createdTime), | ||
| txLink: proposal.txHash | ||
| ? `${blockExplorerUrl}/tx/${proposal.txHash}` | ||
| : undefined, | ||
| }, | ||
| { | ||
| label: startTime <= now ? "Started" : "Starts", | ||
| timestamp: startTime, | ||
| date: formatTimestamp(startTime), | ||
| status: getTimelineItemStatus(startTime), | ||
| txLink: undefined, | ||
| }, | ||
| { | ||
| label: endTime <= now ? "Ended" : "Ends", | ||
| timestamp: endTime, | ||
| date: formatTimestamp(endTime), | ||
| status: getTimelineItemStatus(endTime), | ||
| txLink: undefined, | ||
| }, | ||
| ...(isQueued | ||
| ? [ | ||
| { | ||
| label: "Queued", | ||
| timestamp: endTime + 1, | ||
| date: undefined as string | undefined, | ||
| status: "completed" as const, | ||
| txLink: undefined, | ||
| }, | ||
| ] | ||
| : []), | ||
| ...(isExecuted | ||
| ? [ | ||
| { | ||
| label: "Executed", | ||
| timestamp: endTime + 2, | ||
| date: undefined as string | undefined, | ||
| status: "completed" as const, | ||
| txLink: undefined, | ||
| }, | ||
| ] | ||
| : []), | ||
| ]; | ||
|
|
||
| const getTimelineItemBgColor = (index: number) => { | ||
|
|
@@ -89,11 +129,23 @@ export const ProposalTimeline = ({ | |
| </div> | ||
|
|
||
| {/* Timeline content */} | ||
| <div className="flex flex-col"> | ||
| <div className="flex items-center gap-1.5"> | ||
| <p className={`font-roboto-mono text-[13px]`}> | ||
| <span className="text-primary">{item.label}</span>{" "} | ||
| <span className="text-secondary">on {item.date}</span> | ||
| {item.date && ( | ||
| <span className="text-secondary">on {item.date}</span> | ||
| )} | ||
| </p> | ||
| {item.txLink && ( | ||
| <Link | ||
| href={item.txLink} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="text-secondary hover:text-primary transition-colors" | ||
| > | ||
| <ExternalLink className="size-3" /> | ||
| </Link> | ||
| )} | ||
| </div> | ||
| </div> | ||
| {index < timelineItems.length - 1 && ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
ETH_ADDRESS_REGEXonly checks the right boundary, so it can match the last 40 hex chars inside longer values (for example a 64-hexbytes32hash) and treat them as Ethereum addresses. In decoded calldata that includes hashes or packed bytes, this will render incorrect ENS names and mutate the displayed payload, which can mislead users reviewing proposal actions.Useful? React with 👍 / 👎.