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

Dashboard page #35

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
60 changes: 60 additions & 0 deletions components/dashboard/dao-resources.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="grid grid-cols-[repeat(auto-fill,_minmax(250px,_1fr))] gap-6 sm:grid-cols-[repeat(auto-fill,_minmax(350px,_1fr))] md:gap-6">
{resources.map((resource) => (
<Card
key={resource.link}
className="flex flex-col justify-between gap-y-4 !rounded-2xl border border-neutral-200 bg-neutral-0 p-6 shadow-neutral-md"
>
<Heading size="h2">{resource.name}</Heading>
<div className="flex grow flex-col justify-start">
<p className="text-neutral-500">{resource.description}</p>
</div>
<span className="flex">
<Button
href={resource.link}
variant={resource.primary ? "primary" : "secondary"}
{...(resource.internal ? {} : { rel: "noopener noreferrer", target: "_blank" })}
>
{resource.cta}
</Button>
</span>
</Card>
))}
</div>
);
};
109 changes: 109 additions & 0 deletions components/dashboard/header-dao.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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 (
<header className="relative flex w-full justify-center">
{/* Radial gradients */}
<section className="bg-ellipse-34 absolute top-[70px] right-[80px] -z-10 size-[180px] rounded-full blur-[120px] sm:right-[80px] sm:size-[320px]" />
<section className="bg-ellipse-35 absolute left-[68px] top-[170px] -z-10 size-[250px] rounded-full blur-[80px] sm:size-[400px]" />
<section className="bg-ellipse-36 absolute right-[400px] top-[153px] -z-10 hidden size-[540px] rounded-full blur-[120px] lg:block" />

<div className="flex w-full max-w-screen-xl flex-col gap-y-6">
<div className="flex flex-col gap-y-8">
<div className="md:w-4/5">
<Heading>{PUB_APP_NAME} DAO</Heading>
<p className="text-xl leading-normal text-neutral-600 md:text-2xl">
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.
</p>
</div>
</div>
<div className="flex flex-col gap-x-20 gap-y-4 sm:flex-row md:w-4/5">
{/* Proposal count */}
{/* {totalProposalCountFetched && !totalProposalsError && (
<div className="flex flex-col">
<span className="text-2xl text-neutral-800 md:text-3xl">
{formatterUtils.formatNumber(totalProposals, { format: NumberFormat.GENERIC_SHORT })}
</span>
<span className="text-xl text-neutral-500">{totalProposals === 1 ? "Proposal" : "Proposals"}</span>
</div>
)}
{totalProposalsLoading && (
<div className="flex w-24 flex-col justify-between gap-y-3 pb-1 pt-3">
<StateSkeletonBar size="2xl" className="h-[30px] !bg-neutral-100 py-4" width={"65%"} />
<StateSkeletonBar size="xl" className="!bg-neutral-100" width={"100%"} />
</div>
)} */}
{/*
{totalVotingPowerFetched && totalVotingPower && (
<div className="flex flex-col">
<span className="text-3xl text-neutral-800 md:text-3xl">
{formatterUtils.formatNumber(formatUnits(totalVotingPower, PUB_TOKEN_DECIMALS), {
format: NumberFormat.TOKEN_AMOUNT_SHORT,
})}
</span>
<span className="text-xl text-neutral-500">Locked voting power</span>
</div>
)}

{totalVotingPowerLoading && (
<div className="flex w-24 flex-col justify-between gap-y-3 pb-1 pt-3">
<StateSkeletonBar size="2xl" className="h-[30px] !bg-neutral-100 py-4" width={"65%"} />
<StateSkeletonBar size="xl" className="!bg-neutral-100" width={"100%"} />
</div>
)} */}

{/* {totalTokensFetched && totalTokensLocked != null && (
<div className="flex flex-col">
<div className="flex items-center gap-x-1">
<span className="text-2xl text-neutral-800 md:text-3xl">
{formatterUtils.formatNumber(formatUnits(totalTokensLocked, PUB_TOKEN_DECIMALS), {
format: NumberFormat.TOKEN_AMOUNT_SHORT,
maxFractionDigits: 2,
})}
</span>
<span className="text-xl leading-tight text-neutral-500 md:text-2xl">{PUB_TOKEN_SYMBOL}</span>
</div>
<span className="text-xl text-neutral-500">{`Total tokens locked`}</span>
</div>
)}
{totalTokensLockedLoading && (
<div className="flex w-24 flex-col justify-between gap-y-3 pb-1 pt-3">
<StateSkeletonBar size="2xl" className="h-[30px] !bg-neutral-100 py-4" width={"65%"} />
<StateSkeletonBar size="xl" className="!bg-neutral-100" width={"100%"} />
</div>
)} */}
</div>
</div>
</header>
);
};
56 changes: 56 additions & 0 deletions components/dashboard/latest-proposals.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<section className="flex flex-1 flex-col gap-y-4">
<div className="flex flex-col gap-y-2">
<Heading size="h2">Latest proposals</Heading>
</div>
<If condition={proposalCount}>
<Then>
<DataList.Container SkeletonElement={ProposalDataListItemSkeleton}>
{Array.from(Array(proposalCount || 0)?.keys())
.reverse()
?.map((proposalIndex) => <ProposalCard key={proposalIndex} proposalIndex={proposalIndex} />)}
</DataList.Container>
<DataList.Pagination />
</Then>
<Else>
<MissingContentView>
No proposals have been created yet.
<br />
Here you will see the list of proposals initiated by the Security Council.
</MissingContentView>
</Else>
</If>
</section>
);
};
12 changes: 12 additions & 0 deletions components/dashboard/latest-tweets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PUB_TWITTER_ACCOUNT } from "@/constants";
import { Heading } from "@aragon/ods";
import { TwitterFeed } from "./twitter-feed";

export const LatestTweets = () => {
return (
<div className="flex w-full flex-col gap-y-3 pt-3">
<Heading size="h2">Latest posts from @{PUB_TWITTER_ACCOUNT}</Heading>
<TwitterFeed account={PUB_TWITTER_ACCOUNT} />
</div>
);
};
52 changes: 52 additions & 0 deletions components/dashboard/twitter-feed.tsx
Original file line number Diff line number Diff line change
@@ -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<ITwitterFeed> = ({ 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 (
<div
className={classNames(
"relative inline-block",
"after:absolute after:bottom-0 after:left-0 after:right-0 after:top-0 after:z-[1]",
"after:pointer-events-none after:overflow-hidden after:rounded-[12px] after:border after:border-neutral-100 after:content-['']"
)}
>
<a
className="twitter-timeline"
data-height="498"
data-chrome="noheader nofooter"
href={`https://twitter.com/${account}`}
>
<FeedSkeleton />
</a>
</div>
);
};
45 changes: 45 additions & 0 deletions components/dashboard/twitter-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export const FeedSkeleton = () => {
return (
<div className="h-[522px] divide-y divide-neutral-100 overflow-hidden rounded-xl border border-neutral-100">
<TweetSkeleton />
<TweetSkeleton />
</div>
);
};

const TweetSkeleton = () => {
return (
<div className="w-full bg-neutral-0 p-4">
<div className="flex items-center">
<div className="h-10 w-10 animate-pulse rounded-full bg-neutral-50"></div>
<div className="ml-4">
<div className="h-4 w-24 animate-pulse rounded bg-neutral-50"></div>
<div className="mt-2 h-3 w-16 animate-pulse rounded bg-neutral-50"></div>
</div>
</div>

<div className="pl-10 pr-2">
<div className="mt-4">
<div className="h-4 w-full animate-pulse rounded bg-neutral-50"></div>
<div className="mt-2 h-4 w-3/4 animate-pulse rounded bg-neutral-50"></div>
<div className="mt-2 h-4 w-1/2 animate-pulse rounded bg-neutral-50"></div>
</div>

<div className="mt-4 w-full rounded-2xl border border-neutral-100 p-4">
<div className="flex items-center">
<div className="h-8 w-8 animate-pulse rounded-full bg-neutral-50"></div>
<div className="ml-4">
<div className="h-4 w-20 animate-pulse rounded bg-neutral-50"></div>
<div className="mt-2 h-3 w-14 animate-pulse rounded bg-neutral-50"></div>
</div>
</div>
<div className="mt-4">
<div className="h-4 w-full animate-pulse rounded bg-neutral-50"></div>
<div className="mt-2 h-4 w-3/4 animate-pulse rounded bg-neutral-50"></div>
<div className="mt-2 h-4 w-1/2 animate-pulse rounded bg-neutral-50"></div>
</div>
</div>
</div>
</div>
);
};
2 changes: 1 addition & 1 deletion components/nav/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand Down
3 changes: 3 additions & 0 deletions constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
2 changes: 1 addition & 1 deletion context/Web3Modal.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Loading
Loading