From 30c11ac56ff18c2e59f811ccb88a2b52cf6d1859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 21 Nov 2024 15:45:32 +0700 Subject: [PATCH 1/3] Adding a dashboard page to the app --- components/dashboard/dao-resources.tsx | 60 +++++++++++++ components/dashboard/header-dao.tsx | 103 ++++++++++++++++++++++ components/dashboard/latest-proposals.tsx | 56 ++++++++++++ components/dashboard/latest-tweets.tsx | 12 +++ components/dashboard/twitter-feed.tsx | 52 +++++++++++ components/dashboard/twitter-skeleton.tsx | 45 ++++++++++ components/nav/navbar.tsx | 2 +- constants.ts | 3 + context/Web3Modal.tsx | 2 +- pages/index.tsx | 28 +++--- 10 files changed, 349 insertions(+), 14 deletions(-) create mode 100644 components/dashboard/dao-resources.tsx create mode 100644 components/dashboard/header-dao.tsx create mode 100644 components/dashboard/latest-proposals.tsx create mode 100644 components/dashboard/latest-tweets.tsx create mode 100644 components/dashboard/twitter-feed.tsx create mode 100644 components/dashboard/twitter-skeleton.tsx diff --git a/components/dashboard/dao-resources.tsx b/components/dashboard/dao-resources.tsx new file mode 100644 index 0000000..9a071d6 --- /dev/null +++ b/components/dashboard/dao-resources.tsx @@ -0,0 +1,60 @@ +import { type IResource } from "@/utils/types"; +import { Button, Card, Heading } from "@aragon/ods"; +import { PUB_BLOG_URL, PUB_FORUM_URL } from "@/constants"; + +type IDashboardResource = IResource & { + cta: string; + primary?: boolean; + internal?: boolean; + description: string; +}; + +const resources: IDashboardResource[] = [ + { + name: "Learn", + link: PUB_BLOG_URL, + description: "Learn more about the protocol and the changes that will impact the ecosystem.", + cta: "Read the blog", + }, + { + name: "Discuss", + link: PUB_FORUM_URL, + description: "Share knowledge, contribute to open discussions and propose new ones.", + cta: "Join forum", + }, + { + name: "Participate", + link: "/plugins/community-proposals/", + description: "Open the list of proposals and cast your vote on them.", + cta: "Open proposals", + internal: true, + primary: true, + }, +]; + +export const DaoResources = () => { + return ( +
+ {resources.map((resource) => ( + + {resource.name} +
+

{resource.description}

+
+ + + +
+ ))} +
+ ); +}; diff --git a/components/dashboard/header-dao.tsx b/components/dashboard/header-dao.tsx new file mode 100644 index 0000000..e75fbaf --- /dev/null +++ b/components/dashboard/header-dao.tsx @@ -0,0 +1,103 @@ +import { PUB_APP_NAME } from "@/constants"; +// import { proposalList } from "@/plugins/snapshot/services/proposals"; +// import { useTotalTokensLocked } from "@/plugins/votingEscrow/hooks/useTotalTokensLocked"; +import { formatterUtils, Heading, NumberFormat, StateSkeletonBar } from "@aragon/ods"; +// import { useInfiniteQuery } from "@tanstack/react-query"; +// import { formatUnits } from "viem"; + +export const HeaderDao = () => { + // const { + // data: totalProposals, + // isLoading: totalProposalsLoading, + // isFetched: totalProposalsFetched, + // error: totalProposalsError, + // } = useInfiniteQuery({ + // ...proposalList({ limit: 6 }), + // select: (data) => data.pages.flat().length, + // }); + + // const { + // data: totalVotingPower, + // isLoading: totalVotingPowerLoading, + // isFetched: totalVotingPowerFetched, + // } = useTotalVotingPower(); + + // const { + // data: totalTokensLocked, + // isLoading: totalTokensLockedLoading, + // isFetched: totalTokensFetched, + // } = useTotalTokensLocked(); + + // const totalProposalCountFetched = totalProposalsFetched && !totalProposalsLoading; + + return ( +
+
+
+
+ {PUB_APP_NAME} DAO +

+ Welcome to the {PUB_APP_NAME} DAO's Governance app. Use this tool to engage with the community and shape the future direction of the protocol. +

+
+
+
+ {/* Proposal count */} + {/* {totalProposalCountFetched && !totalProposalsError && ( +
+ + {formatterUtils.formatNumber(totalProposals, { format: NumberFormat.GENERIC_SHORT })} + + {totalProposals === 1 ? "Proposal" : "Proposals"} +
+ )} + {totalProposalsLoading && ( +
+ + +
+ )} */} + {/* + {totalVotingPowerFetched && totalVotingPower && ( +
+ + {formatterUtils.formatNumber(formatUnits(totalVotingPower, PUB_TOKEN_DECIMALS), { + format: NumberFormat.TOKEN_AMOUNT_SHORT, + })} + + Locked voting power +
+ )} + + {totalVotingPowerLoading && ( +
+ + +
+ )} */} + + {/* {totalTokensFetched && totalTokensLocked != null && ( +
+
+ + {formatterUtils.formatNumber(formatUnits(totalTokensLocked, PUB_TOKEN_DECIMALS), { + format: NumberFormat.TOKEN_AMOUNT_SHORT, + maxFractionDigits: 2, + })} + + {PUB_TOKEN_SYMBOL} +
+ {`Total tokens locked`} +
+ )} + {totalTokensLockedLoading && ( +
+ + +
+ )} */} +
+
+
+ ); +}; diff --git a/components/dashboard/latest-proposals.tsx b/components/dashboard/latest-proposals.tsx new file mode 100644 index 0000000..618f421 --- /dev/null +++ b/components/dashboard/latest-proposals.tsx @@ -0,0 +1,56 @@ +import { DataList, Heading, ProposalDataListItemSkeleton } from "@aragon/ods"; +import { Else, If, Then } from "../if"; +import { MissingContentView } from "../MissingContentView"; +import { useBlockNumber, useReadContract } from "wagmi"; +import { PUB_CHAIN, PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS } from "@/constants"; +import { TaikoOptimisticTokenVotingPluginAbi } from "@/plugins/optimistic-proposals/artifacts/TaikoOptimisticTokenVotingPlugin.sol"; +import { useEffect } from "react"; +import ProposalCard from "@/plugins/optimistic-proposals/components/proposal"; + +export const LatestProposals = () => { + const { data: blockNumber } = useBlockNumber({ watch: true }); + const { + data: proposalCountResponse, + error: isError, + isLoading, + isFetching: isFetchingNextPage, + refetch, + } = useReadContract({ + address: PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS, + abi: TaikoOptimisticTokenVotingPluginAbi, + functionName: "proposalCount", + chainId: PUB_CHAIN.id, + }); + const proposalCount = Number(proposalCountResponse); + + useEffect(() => { + refetch(); + }, [blockNumber]); + + const entityLabel = proposalCount === 1 ? "Proposal" : "Proposals"; + + return ( +
+
+ Latest proposals +
+ + + + {Array.from(Array(proposalCount || 0)?.keys()) + .reverse() + ?.map((proposalIndex) => )} + + + + + + No proposals have been created yet. +
+ Here you will see the list of proposals initiated by the Security Council. +
+
+
+
+ ); +}; diff --git a/components/dashboard/latest-tweets.tsx b/components/dashboard/latest-tweets.tsx new file mode 100644 index 0000000..d98535b --- /dev/null +++ b/components/dashboard/latest-tweets.tsx @@ -0,0 +1,12 @@ +import { PUB_TWITTER_ACCOUNT } from "@/constants"; +import { Heading } from "@aragon/ods"; +import { TwitterFeed } from "./twitter-feed"; + +export const LatestTweets = () => { + return ( +
+ Latest posts from @{PUB_TWITTER_ACCOUNT} + +
+ ); +}; diff --git a/components/dashboard/twitter-feed.tsx b/components/dashboard/twitter-feed.tsx new file mode 100644 index 0000000..c9a7e14 --- /dev/null +++ b/components/dashboard/twitter-feed.tsx @@ -0,0 +1,52 @@ +import classNames from "classnames"; +import React, { useEffect } from "react"; +import { FeedSkeleton } from "./twitter-skeleton"; + +declare global { + interface Window { + twttr: any; + } +} + +interface ITwitterFeed { + account: string; +} + +export const TwitterFeed: React.FC = ({ account }) => { + useEffect(() => { + const script = document.createElement("script"); + script.src = "https://platform.twitter.com/widgets.js"; + script.async = true; + + script.onload = () => { + if (window?.twttr?.widgets) { + window.twttr.widgets.load(); + } + }; + + document.body.appendChild(script); + + return () => { + document.body.removeChild(script); + }; + }, []); + + return ( +
+ + + +
+ ); +}; diff --git a/components/dashboard/twitter-skeleton.tsx b/components/dashboard/twitter-skeleton.tsx new file mode 100644 index 0000000..029d0a7 --- /dev/null +++ b/components/dashboard/twitter-skeleton.tsx @@ -0,0 +1,45 @@ +export const FeedSkeleton = () => { + return ( +
+ + +
+ ); +}; + +const TweetSkeleton = () => { + return ( +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; diff --git a/components/nav/navbar.tsx b/components/nav/navbar.tsx index d16c42c..a43fa29 100644 --- a/components/nav/navbar.tsx +++ b/components/nav/navbar.tsx @@ -17,7 +17,7 @@ export const Navbar: React.FC = () => { const showAllLinks = address && members.includes(address); const navLinks: INavLink[] = [ - // { path: "/", id: "dashboard", name: "Dashboard" /*, icon: IconType.APP_DASHBOARD*/ }, + { path: "/", id: "dashboard", name: "Dashboard" /*, icon: IconType.APP_DASHBOARD*/ }, ...plugins .filter((link) => showAllLinks || !link.hiddenIfNotSigner) .map((p) => ({ diff --git a/constants.ts b/constants.ts index 8c1bf40..deb5ea8 100644 --- a/constants.ts +++ b/constants.ts @@ -41,9 +41,12 @@ export const DETERMINISTIC_EMERGENCY_PAYLOAD = export const PUB_APP_NAME = "Taiko"; export const PUB_APP_DESCRIPTION = "Taiko's official UI to interact with the DAO smart contract"; export const PUB_TOKEN_SYMBOL = "TKO"; +export const PUB_TOKEN_DECIMALS = 18; export const PUB_PROJECT_LOGO = "/logo-tk.svg"; export const PUB_PROJECT_URL = "https://taiko.xyz/"; export const PUB_BLOG_URL = "https://taiko.xyz/blog"; export const PUB_FORUM_URL = "https://community.taiko.xyz/"; export const PUB_WALLET_ICON = "https://avatars.githubusercontent.com/u/37784886"; + +export const PUB_TWITTER_ACCOUNT = "taikoxyz"; diff --git a/context/Web3Modal.tsx b/context/Web3Modal.tsx index 988853b..c63d584 100644 --- a/context/Web3Modal.tsx +++ b/context/Web3Modal.tsx @@ -1,5 +1,5 @@ import { http, createConfig } from "wagmi"; -import { injected } from "wagmi/connectors"; +// import { injected } from "wagmi/connectors"; import { walletConnect, coinbaseWallet } from "wagmi/connectors"; import { PUB_APP_DESCRIPTION, diff --git a/pages/index.tsx b/pages/index.tsx index bf67953..10989ea 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,20 +1,24 @@ -import { plugins } from "@/plugins"; +import { DaoResources } from "@/components/dashboard/dao-resources"; +import { HeaderDao } from "@/components/dashboard/header-dao"; import { MainSection } from "@/components/layout/main-section"; -import { PleaseWaitSpinner } from "@/components/please-wait"; -import { useEffect } from "react"; -import { useRouter } from "next/router"; +import { LatestProposals } from "@/components/dashboard/latest-proposals"; +import { LatestTweets } from "@/components/dashboard/latest-tweets"; export default function Home() { - const { replace } = useRouter(); - useEffect(() => { - replace("/plugins/" + plugins[0].id); - }, []); - return ( -
- -
+ +
+
+ +
+
+ +
+ +
+
+
); } From c1c58d819ec06901b9b4cd881dd9dbcdd32f4f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 21 Nov 2024 15:49:10 +0700 Subject: [PATCH 2/3] Minor edit --- .../optimistic-proposals/components/proposal/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/optimistic-proposals/components/proposal/index.tsx b/plugins/optimistic-proposals/components/proposal/index.tsx index 3ba7223..77e5fe4 100644 --- a/plugins/optimistic-proposals/components/proposal/index.tsx +++ b/plugins/optimistic-proposals/components/proposal/index.tsx @@ -1,7 +1,6 @@ import Link from "next/link"; import { useProposalVeto } from "@/plugins/optimistic-proposals/hooks/useProposalVeto"; -import { Card, ProposalStatus } from "@aragon/ods"; -import { ProposalDataListItem } from "@aragon/ods"; +import { Card, ProposalStatus, ProposalDataListItem } from "@aragon/ods"; import { PleaseWaitSpinner } from "@/components/please-wait"; import { useProposalStatus } from "../../hooks/useProposalVariantStatus"; import { useAccount } from "wagmi"; @@ -14,6 +13,7 @@ const DEFAULT_PROPOSAL_METADATA_SUMMARY = "(The metadata of the proposal is not type ProposalInputs = { proposalIndex: number; + linkPrefix?: string; }; export default function ProposalCard(props: ProposalInputs) { @@ -25,6 +25,7 @@ export default function ProposalCard(props: ProposalInputs) { const proposalStatus = useProposalStatus(proposal!); const showLoading = getShowProposalLoading(proposal, proposalFetchStatus); const hasVetoed = vetoes?.some((veto) => veto.voter === address); + const prefix = props.linkPrefix ? props.linkPrefix + "/" : ""; if (!proposal && showLoading) { return ( @@ -39,7 +40,7 @@ export default function ProposalCard(props: ProposalInputs) { } else if (!proposal?.title && !proposal?.summary) { // We have the proposal but no metadata yet return ( - + @@ -49,7 +50,7 @@ export default function ProposalCard(props: ProposalInputs) { ); } else if (proposalFetchStatus.metadataReady && !proposal?.title) { return ( - +

From 83ca58e9af37629ecbbca59bb1c4cdbfe6c5a2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 21 Nov 2024 18:01:24 +0700 Subject: [PATCH 3/3] Radial gradient --- components/dashboard/header-dao.tsx | 8 +++++++- tailwind.config.ts | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/components/dashboard/header-dao.tsx b/components/dashboard/header-dao.tsx index e75fbaf..e9d3025 100644 --- a/components/dashboard/header-dao.tsx +++ b/components/dashboard/header-dao.tsx @@ -32,12 +32,18 @@ export const HeaderDao = () => { return (
+ {/* Radial gradients */} +
+
+
+
{PUB_APP_NAME} DAO

- Welcome to the {PUB_APP_NAME} DAO's Governance app. Use this tool to engage with the community and shape the future direction of the protocol. + Welcome to the {PUB_APP_NAME} DAO's Governance app. Use this tool to engage with the community and shape + the future direction of the protocol.

diff --git a/tailwind.config.ts b/tailwind.config.ts index 5c8ba13..34b3586 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -10,6 +10,14 @@ const config: Config = { ], theme: { extend: { + backgroundImage: { + "ellipse-35": + "radial-gradient(50% 50% at 50% 50%, rgba(61, 136, 255, 0.14) 0%, rgba(207, 227, 255, 0.18) 100%)", + "ellipse-36": + "radial-gradient(50% 50% at 50% 50%, rgba(191, 255, 55, 0.11) 0%, rgba(189, 217, 255, 0.23) 100%)", + "ellipse-34": + "radial-gradient(50% 50% at 50% 50%, rgba(105, 164, 250, 0.65) 0%, rgba(207, 227, 255, 0.55) 100%)", + }, colors: { primary: { 50: "rgb(var(--ods-color-primary-50) / )",