Skip to content

Commit

Permalink
Dev/issue 104 governance dash (#11)
Browse files Browse the repository at this point in the history
* dev:
- add governance and staking routes
- add basic header nav
- add governance page component

* wip:
- add nav
- connect nav state to path
- prep gov client

* wip:
- break govclient into actions
- create proposal page
- definte/format prop data

* wip: mock voting

* wip: clean up formatting

* wip: add other gov query hooks, create combo query

* wip: move staking pool endpoint to api config

* wip: update vote progress bar to include other type of votes.  Update card and tally components

* wip: tweak progress bars.

* wip: switch back to xion (using kava for test data)

* wip: update file names to follow industry standard best practices.  add transaction lifecycle methods.

* wip: fix tally status bars. add end date.

* feat: add in proposal details. restore network info

* chore: clean up loose comment stubs

* chore: linting round 1

* chore: linting

* feat: add pagination and filtering of props

* chore: fix lint build error

* feat: voting widget empty state

* feat: lift proposal normalize util

* feat: complete tallying widget

* feat: switch to treasury contract

* feat: restore legacy config

* fix: remove governance flag in config

* fix: remove treasury contract for txs

* fix: remove treasury contract from submitVote action

* fix: remove all traces of treasury contract

* fix: redelgate message

---------

Co-authored-by: Justin <[email protected]>
  • Loading branch information
burnt-sun and justinbarry authored Nov 19, 2024
1 parent 50d0bb1 commit 6837815
Show file tree
Hide file tree
Showing 39 changed files with 4,280 additions and 2,699 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ The main part of the business logic that queries and interacts with XION
is in:

- [./src/features/staking/lib](./src/features/staking/lib)

4,321 changes: 1,656 additions & 2,665 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@burnt-labs/abstraxion": "^1.0.0-alpha.43",
"@burnt-labs/abstraxion": "^1.0.0-alpha.52",
"@burnt-labs/constants": "^0.1.0-alpha.6",
"@burnt-labs/ui": "^0.1.0-alpha.7",
"@cosmjs/cosmwasm-stargate": "^0.32.2",
"@cosmjs/proto-signing": "^0.32.2",
"@cosmjs/stargate": "^0.32.2",
"@mui/base": "^5.0.0-beta.38",
"@tanstack/react-query": "^4.36.1",
"@vercel/analytics": "^1.2.2",
"axios": "^1.7.7",
"bignumber.js": "^9.1.2",
"cosmjs-types": "^0.9.0",
"next": "14.1.0",
Expand Down
16 changes: 16 additions & 0 deletions src/app/governance/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Metadata } from "next";
import { Suspense } from "react";

import GovernancePage from "@/features/governance/components/GovernancePage";

export const metadata: Metadata = {
title: "XION Governance",
};

export default function Page() {
return (
<Suspense>
<GovernancePage />
</Suspense>
);
}
16 changes: 16 additions & 0 deletions src/app/governance/proposal/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Metadata } from "next";
import { Suspense } from "react";

import ProposalPage from "@/features/governance/components/ProposalPage";

export const metadata: Metadata = {
title: "XION Governance Proposal",
};

export default function Page() {
return (
<Suspense>
<ProposalPage />
</Suspense>
);
}
27 changes: 19 additions & 8 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { AbstraxionProvider } from "@burnt-labs/abstraxion";
import "@burnt-labs/abstraxion/dist/index.css";
import "@burnt-labs/ui/dist/index.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Analytics } from "@vercel/analytics/react";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
Expand All @@ -22,6 +23,14 @@ const abstraxionConfig = {
stake: true,
};

const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});

export default function RootLayout({
children,
}: {
Expand All @@ -30,14 +39,16 @@ export default function RootLayout({
return (
<html lang="en">
<body>
<AbstraxionProvider config={abstraxionConfig}>
<CoreProvider>
<StakingProvider>
<BaseWrapper>{children}</BaseWrapper>
<Analytics />
</StakingProvider>
</CoreProvider>
</AbstraxionProvider>
<QueryClientProvider client={queryClient}>
<AbstraxionProvider config={abstraxionConfig}>
<CoreProvider>
<StakingProvider>
<BaseWrapper>{children}</BaseWrapper>
<Analytics />
</StakingProvider>
</CoreProvider>
</AbstraxionProvider>
</QueryClientProvider>
<ToastContainer closeOnClick />
</body>
</html>
Expand Down
1 change: 1 addition & 0 deletions src/app/staking/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "../page";
11 changes: 11 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,14 @@ const NETWORK_TYPE = getEnvStringOrThrow(
);

export const IS_TESTNET = NETWORK_TYPE === "testnet";

export interface NavItem {
href: string;
isRootLink?: boolean;
label: string;
}

export const mainNavItems: NavItem[] = [
{ href: "/staking", isRootLink: true, label: "Staking" },
{ href: "/governance", label: "Governance" },
];
51 changes: 51 additions & 0 deletions src/features/core/components/TooltipPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useEffect, useRef, useState } from "react";

import InfoIcon from "../../governance/components/InfoIcon";

interface TooltipPopoverProps {
content: string;
icon?: React.ReactNode;
}

export const TooltipPopover: React.FC<TooltipPopoverProps> = ({
content,
icon = <InfoIcon className="h-4 w-4 cursor-help" />,
}) => {
const [showTooltip, setShowTooltip] = useState(false);
const tooltipRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
tooltipRef.current &&
!tooltipRef.current.contains(event.target as Node)
) {
setShowTooltip(false);
}
};

document.addEventListener("mousedown", handleClickOutside);

return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);

return (
<div className="relative" ref={tooltipRef}>
<div
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
>
{icon}
</div>
{showTooltip && (
<div className="absolute bottom-full left-0 mb-2 w-[200px] rounded-2xl bg-[#1a1a1a] shadow">
<div className="p-4">
<p className="text-sm text-white">{content}</p>
</div>
</div>
)}
</div>
);
};
36 changes: 22 additions & 14 deletions src/features/core/components/base-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import { Abstraxion, useModal } from "@burnt-labs/abstraxion";
import Link from "next/link";

import { BASE_PATH, IS_TESTNET } from "@/config";
import { BASE_PATH, IS_TESTNET, mainNavItems } from "@/config";

import NavAccount from "./nav-account";
import NavLink from "./nav-link";

export default function RootLayout({
children,
Expand All @@ -21,22 +22,29 @@ export default function RootLayout({
style={{ borderBottom: "1px solid #333" }}
>
<div className="page-container m-auto flex h-[80px] flex-row items-center justify-between px-[16px]">
<div className="flex flex-row items-center">
<Link className="cursor-pointer" href="/">
<div className="flex w-[200px] flex-row items-center">
<Link className="flex cursor-pointer items-center" href="/">
<img alt="Xion Logo" src={`${BASE_PATH}/xion-logo.svg`} />
<span
className={[
"ml-[8px] translate-y-[4px] rounded-[4px] p-[4px] text-[12px] uppercase",
IS_TESTNET
? "bg-chain-testnetBg text-chain-testnetFg"
: "bg-chain-mainnetBg text-chain-mainnetFg",
].join(" ")}
>
{IS_TESTNET ? "Testnet" : "Mainnet"}
</span>
</Link>
<span
className={[
"ml-[8px] translate-y-[4px] rounded-[4px] p-[4px] text-[12px] uppercase",
IS_TESTNET
? "bg-chain-testnetBg text-chain-testnetFg"
: "bg-chain-mainnetBg text-chain-mainnetFg",
].join(" ")}
>
{IS_TESTNET ? "Testnet" : "Mainnet"}
</span>
</div>
<NavAccount />
<div className="hidden flex-1 flex-row items-center justify-center md:flex">
{mainNavItems.map((item) => (
<NavLink key={item.href} {...item} />
))}
</div>
<div className="flex w-[200px] items-center justify-end">
<NavAccount />
</div>
</div>
</nav>
{children}
Expand Down
14 changes: 14 additions & 0 deletions src/features/core/components/nav-account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import {
useModal,
} from "@burnt-labs/abstraxion";

import { mainNavItems } from "@/config";
import { useCore } from "@/features/core/context/hooks";
import { setPopupOpenId } from "@/features/core/context/reducer";
import AddressShort from "@/features/staking/components/address-short";

import { wallet } from "../lib/icons";
import { Button, ClipboardCopy, FloatingDropdown } from "./base";
import NavLink from "./nav-link";

const Account = () => (
<span className="flex flex-row items-center gap-[8px] rounded-[8px] bg-bg-600 px-[16px] py-[18px]">
Expand All @@ -21,6 +25,11 @@ const NavAccount = () => {
const [, setShowAbstraxion] = useModal();
const { data, isConnected } = useAbstraxionAccount();
const { logout } = useAbstraxionSigningClient();
const { core } = useCore();

const closeDropdown = () => {
core.dispatch(setPopupOpenId(null));
};

return (
<div className="cursor-pointer">
Expand All @@ -42,6 +51,11 @@ const NavAccount = () => {
<ClipboardCopy textToCopy={data.bech32Address} />
</div>
</div>
<div className="relative inline-flex flex-col items-center gap-8 px-0">
{mainNavItems.map((item) => (
<NavLink key={item.href} onClick={closeDropdown} {...item} />
))}
</div>
<Button
className="w-full flex-1 py-[8px] uppercase"
onClick={() => {
Expand Down
36 changes: 36 additions & 0 deletions src/features/core/components/nav-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Link from "next/link";
import { usePathname } from "next/navigation";

import type { NavItem } from "@/config";

type NavLinkProps = NavItem & {
className?: string;
onClick?: () => void;
};

const NavLink: React.FC<NavLinkProps> = ({
className = "",
href,
isRootLink,
label,
onClick,
}) => {
const pathname = usePathname();

const isActive =
pathname.startsWith(href) || (isRootLink && pathname === "/");

return (
<Link
className={`mx-4 text-[16px] text-lg font-bold font-semibold uppercase text-white ${
isActive ? "border-b-2 border-white" : ""
} ${className}`}
href={href}
onClick={onClick}
>
{label}
</Link>
);
};

export default NavLink;
23 changes: 23 additions & 0 deletions src/features/core/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import axios from "axios";
import type { AxiosRequestConfig, AxiosResponse } from "axios";
import BigNumber from "bignumber.js";

import { REST_URL } from "@/constants";

export const sortUtil = (a: unknown, b: unknown, isAsc: boolean) => {
if (typeof a !== typeof b) {
return 0;
Expand All @@ -25,3 +29,22 @@ export const sortUtil = (a: unknown, b: unknown, isAsc: boolean) => {

return 0;
};

export async function fetchFromAPI<T>(
endpoint: string,
config: AxiosRequestConfig = {},
): Promise<T> {
try {
const response: AxiosResponse<T> = await axios.get(
`${REST_URL}${endpoint}`,
{
...config,
},
);

return response.data;
} catch (error) {
console.error(`Failed to fetch from API: ${endpoint}`, error);
throw error;
}
}
24 changes: 24 additions & 0 deletions src/features/governance/components/BreadCrumbNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Link from "next/link";

interface BreadCrumbProps {
proposalId: string;
}

export function BreadCrumbNav({ proposalId }: BreadCrumbProps) {
return (
<div className="flex flex-row justify-between text-left">
<div>
<Link
className="font-['Akkurat LL'] text-sm font-normal uppercase leading-tight text-white underline"
href="/governance/"
>
Governance
</Link>
<span className="font-['Akkurat LL'] text-sm font-normal uppercase leading-tight text-white">
{" "}
&gt; Proposal {proposalId}
</span>
</div>
</div>
);
}
28 changes: 28 additions & 0 deletions src/features/governance/components/ChevronIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";

interface ChevronIconProps {
className?: string;
}

const ChevronIcon: React.FC<ChevronIconProps> = ({ className = "" }) => (
<svg
className={className}
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<g id="icon">
<path
d="M14 8L10 12L14 16"
id="Arrow"
stroke="white"
strokeOpacity="0.4"
strokeWidth="2"
/>
</g>
</svg>
);

export default ChevronIcon;
Loading

0 comments on commit 6837815

Please sign in to comment.