Skip to content
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

Release mobile proposal page, queue & execute proposals, and wording & spacing updates #245

Merged
merged 5 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
app/graphql/**/generated
lib/graphql/**/generated
1 change: 1 addition & 0 deletions .secretlintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.trunk
.env*.local
.next
node_modules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ export default function ExecutionCode({ calls }: Props) {

return (
<div>
<h3 className="my-8 flex justify-center text-3xl font-medium">
<h3 className="my-8 hidden justify-center text-3xl font-medium md:flex">
Execution Code
</h3>
<Card>
<Card className="flex flex-col gap-6">
<h3 className="text-center text-[32px]/none font-medium md:hidden">
Execution Code
</h3>
{formattedCalls.map((call, index) => (
<div key={index} className="break-words">
{index > 0 && <hr className="my-4" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ProposalActionTitle } from "./proposal-action-title";
export const DisconnectedState = () => {
return (
<Card>
<div className="flex flex-col gap-[25px] ">
<div className="flex w-full flex-col items-center gap-[25px]">
<ProposalActionTitle />
<span>Please connect your wallet to participate in governance</span>
<ConnectButton fullwidth theme="primary" />
Expand Down
300 changes: 207 additions & 93 deletions src/app/proposals/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { stateToStatusColorMap } from "@/lib/interfaces/proposal.interface";
import {
Avatar,
BlockExplorerLink,
Card,
Loader,
MarkdownView,
Status,
Expand All @@ -19,7 +20,64 @@ import ExecutionCode from "./_components/execution-code.component";
import Participants from "./_components/participants.component";
import { Countdown, ProposalCurrentVotes } from "@/components/index";
import { ensureChainId } from "@/lib/helpers/ensureChainId";
import { ProposalState } from "@/lib/graphql";
import { Proposal, ProposalState } from "@/lib/graphql";
import { CELO_BLOCK_TIME } from "@/config/config.constants";

const ProposalCountdown = ({ proposal }: { proposal: Proposal }) => {
const { data: currentBlock } = useBlockNumber({
watch: true,
chainId: ensureChainId(useAccount().chainId),
});

const endBlock = useBlock({
blockNumber: BigInt(proposal.endBlock),
query: {
enabled: true,
},
});

const votingDeadline = useMemo(() => {
if (currentBlock) {
// If the end block is already mined, we can fetch the timestamp
if (Number(currentBlock) >= proposal.endBlock && endBlock.data) {
return new Date(Number(endBlock.data.timestamp) * 1000);
} else {
// If the end block is not mined yet, we estimate the time
return new Date(
Date.now() +
// Estimation of ~5 seconds per block
(proposal.endBlock - Number(currentBlock)) * CELO_BLOCK_TIME,
);
}
}
}, [currentBlock, endBlock.data, proposal.endBlock]);

const timeLockDeadLine = useMemo(() => {
if (proposal.state === ProposalState.Queued && proposal.proposalQueued[0]) {
return new Date(Number(proposal.proposalQueued[0].eta) * 1000);
}
}, [proposal]);

if (timeLockDeadLine && timeLockDeadLine.getTime() >= new Date().getTime()) {
return (
<Countdown
endTimestamp={timeLockDeadLine.getTime()}
updateIntervalInMs={1000}
/>
);
}

if (proposal.state === ProposalState.Active && votingDeadline) {
return (
<Countdown
endTimestamp={votingDeadline.getTime()}
updateIntervalInMs={1000}
/>
);
}

return null;
};

const Page = ({ params: { id } }: { params: { id: string } }) => {
const { proposal } = useProposal(BigInt(id));
Expand All @@ -43,8 +101,6 @@ const Page = ({ params: { id } }: { params: { id: string } }) => {
}, [proposal]);

const votingDeadline = useMemo(() => {
const CELO_BLOCK_TIME = 5000; // 5 seconds

if (proposal && currentBlock) {
// If the end block is already mined, we can fetch the timestamp
if (Number(currentBlock) >= proposal.endBlock && endBlock.data) {
Expand Down Expand Up @@ -75,115 +131,173 @@ const Page = ({ params: { id } }: { params: { id: string } }) => {
}

return (
<main className="flex flex-col">
<div className="mb-4 mt-6 flex items-center justify-between">
<Status
text={proposal.state.toString()}
type={stateToStatusColorMap[proposal.state]}
/>
<div className="lg:hidden">
{timeLockDeadLine &&
timeLockDeadLine.getTime() >= new Date().getTime() && (
<>
<main className="flex flex-col gap-8 pt-8 md:hidden">
<Card>
<div className="flex flex-col items-start">
<Status
text={proposal.state.toString()}
type={stateToStatusColorMap[proposal.state]}
/>
<h1 className="mb-4 mt-2 text-[32px]/none font-medium">
{proposal.metadata?.title}
</h1>
<ProposalCountdown proposal={proposal} />
</div>
<div className="flex flex-col gap-4 pt-6">
<div className="flex place-items-center gap-x2 font-inter">
<Avatar address={proposal.proposer.id} />
by{" "}
<span className="font-medium">
<WalletAddressWithCopy address={proposal.proposer.id} />
</span>
</div>
<div className="flex flex-col">
<span className="font-inter">Proposed on:</span>
<span className="font-inter font-medium">
{proposedOn && (
<BlockExplorerLink
className=" no-underline "
type="block"
item={proposal.startBlock}
>
{format(proposedOn, "MMMM do, yyyy")}
</BlockExplorerLink>
)}
</span>
</div>
<div className="flex flex-col">
<span className="font-inter">Voting deadline:</span>
<span className="font-inter font-medium">
{votingDeadline && (
<BlockExplorerLink
className=" no-underline"
type="block"
item={proposal.endBlock}
>
{format(votingDeadline, "MMMM do, yyyy")}{" "}
</BlockExplorerLink>
)}
</span>
</div>
</div>
</Card>
{proposal.votes ? (
<ProposalCurrentVotes proposal={proposal} className="md:mb-x6" />
) : (
<Loader isCenter />
)}
<ProposalActions proposal={proposal} />
<Suspense fallback={<Loader isCenter />}>
<Card className="flex flex-col gap-6">
<h2 className="text-center text-[32px]/none font-medium">
Proposal Description
</h2>
<MarkdownView markdown={proposal.metadata?.description} />
</Card>
</Suspense>
{proposal.calls && <ExecutionCode calls={proposal.calls} />}
{proposal.votes && <Participants votes={proposal.votes} />}
</main>

<main className="hidden md:flex md:flex-col">
<div className="mb-4 mt-6 flex items-center justify-between">
<Status
text={proposal.state.toString()}
type={stateToStatusColorMap[proposal.state]}
/>
</div>
<div className="flex flex-col gap-x1 md:grid md:grid-cols-7">
<div className="md:col-span-4 md:col-start-1">
<h1 className="text-[56px]/none font-medium">
<Suspense fallback={<Loader isCenter />}>
{proposal.metadata?.title}
</Suspense>
</h1>
</div>
<div className="md:col-span-3 md:col-start-5">
<div className="hidden lg:flex">
{timeLockDeadLine &&
timeLockDeadLine.getTime() >= new Date().getTime() && (
<Countdown
endTimestamp={timeLockDeadLine.getTime()}
updateIntervalInMs={1000}
/>
)}
</div>
{proposal.state === ProposalState.Active && votingDeadline && (
<Countdown
endTimestamp={timeLockDeadLine.getTime()}
endTimestamp={votingDeadline.getTime()}
updateIntervalInMs={1000}
/>
)}
</div>
</div>
</div>
<div className="flex flex-col gap-x1 md:grid md:grid-cols-7">
<div className="md:col-span-4 md:col-start-1">
<h1 className="text-[56px]/none font-medium">
<div className="mt-4 flex flex-wrap place-items-center justify-start gap-x6 font-inter lg:mt-12 ">
<div className="flex place-items-center gap-x2">
<Suspense fallback={<Loader isCenter />}>
{proposal.metadata?.title}
<Avatar address={proposal.proposer.id} />
by{" "}
<span className="font-medium">
<WalletAddressWithCopy address={proposal.proposer.id} />
</span>
</Suspense>
</h1>
</div>
<div className="md:col-span-3 md:col-start-5">
<div className="hidden lg:flex">
{timeLockDeadLine &&
timeLockDeadLine.getTime() >= new Date().getTime() && (
<Countdown
endTimestamp={timeLockDeadLine.getTime()}
updateIntervalInMs={1000}
/>
)}
</div>
{proposal.state === ProposalState.Active && votingDeadline && (
<Countdown
endTimestamp={votingDeadline.getTime()}
updateIntervalInMs={1000}
/>
)}
</div>
</div>
<div className="mt-4 flex flex-wrap place-items-center justify-start gap-x6 font-inter lg:mt-12 ">
<div className="flex place-items-center gap-x2">
<Suspense fallback={<Loader isCenter />}>
<Avatar address={proposal.proposer.id} />
by{" "}
<span className="font-medium">
<WalletAddressWithCopy address={proposal.proposer.id} />
<div className="flex place-items-center gap-x2">
<span className="font-light">Proposed on:</span>
<span className="">
<Suspense fallback={<Loader isCenter />}>
{proposedOn && (
<BlockExplorerLink
className="no-underline"
type="block"
item={proposal.startBlock}
>
{format(proposedOn, "MMMM do, yyyy 'at' hh:mm a")}
</BlockExplorerLink>
)}
</Suspense>
</span>
</Suspense>
</div>
<div className="flex place-items-center gap-x2">
<span className="font-light">Proposed on:</span>
<span className="">
<Suspense fallback={<Loader isCenter />}>
{proposedOn && (
</div>
<div className="flex place-items-center gap-x2">
<span className="font-light">Voting deadline:</span>
<span className="">
{votingDeadline && (
<BlockExplorerLink
className=" no-underline "
className=" no-underline"
type="block"
item={proposal.startBlock}
item={proposal.endBlock}
>
{format(proposedOn, "MMMM do, yyyy 'at' hh:mm a")}
{format(votingDeadline, "MMMM do, yyyy 'at' hh:mm a")}{" "}
</BlockExplorerLink>
)}
</Suspense>
</span>
</span>
</div>
</div>
<div className="flex place-items-center gap-x2">
<span className="font-light">Voting deadline:</span>
<span className="">
{votingDeadline && (
<BlockExplorerLink
className=" no-underline"
type="block"
item={proposal.endBlock}
>
{format(votingDeadline, "MMMM do, yyyy 'at' hh:mm a")}{" "}
</BlockExplorerLink>
<div className="mt-14 flex flex-col place-items-start gap-y-16 md:flex-row md:justify-between md:gap-1">
<div className="w-full max-w-2xl flex-1">
{proposal.votes ? (
<ProposalCurrentVotes proposal={proposal} className="md:mb-x6" />
) : (
<Loader isCenter />
)}
</span>
</div>
</div>
<div className="mt-14 flex flex-col place-items-start gap-y-16 md:flex-row md:justify-between md:gap-1">
<div className="w-full max-w-2xl flex-1">
{proposal.votes ? (
<ProposalCurrentVotes proposal={proposal} className="md:mb-x6" />
) : (
<Loader isCenter />
)}
<div className="my-x6 md:hidden">
<ProposalActions proposal={proposal} />
<h3 className="my-8 flex justify-center text-3xl font-medium">
Proposal Description
</h3>
<Suspense fallback={<Loader isCenter />}>
<MarkdownView markdown={proposal.metadata?.description} />
</Suspense>
{proposal.calls && <ExecutionCode calls={proposal.calls} />}
</div>
<h3 className="my-8 flex justify-center text-3xl font-medium">
Proposal Description
</h3>
<Suspense fallback={<Loader isCenter />}>
<MarkdownView markdown={proposal.metadata?.description} />
</Suspense>
{proposal.calls && <ExecutionCode calls={proposal.calls} />}
</div>
<div className="flex flex-col gap-x11 md:max-w-[350px]">
<div className="hidden md:block">
<ProposalActions proposal={proposal} />
<div className="flex flex-col gap-x11 md:max-w-[350px]">
<div className="hidden md:block">
<ProposalActions proposal={proposal} />
</div>
{proposal.votes && <Participants votes={proposal.votes} />}
</div>
{proposal.votes && <Participants votes={proposal.votes} />}
</div>
</div>
</main>
</main>
</>
);
};

Expand Down
Loading
Loading