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

Chore: Refactor navbar #103

Merged
merged 9 commits into from
Apr 10, 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
Binary file modified bun.lockb
Binary file not shown.
54 changes: 32 additions & 22 deletions components/WalletContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useWeb3Modal } from "@web3modal/wagmi/react";
import { Avatar, Card, Link } from "@aragon/ods";
import { useAccount, useEnsName, useEnsAvatar } from "wagmi";
import { type Address } from "viem";
import { PUB_ALCHEMY_API_KEY } from "@/constants";
import { formatHexString } from "@/utils/evm";
import { normalize } from "viem/ens";
import { Avatar } from "@aragon/ods";
import { useWeb3Modal } from "@web3modal/wagmi/react";
import classNames from "classnames";
import Blockies from "react-blockies";
import { createClient, http } from "viem";
import { createConfig } from "wagmi";
import { normalize } from "viem/ens";
import { createConfig, useAccount, useEnsAvatar, useEnsName } from "wagmi";
import { mainnet } from "wagmi/chains";
import { PUB_ALCHEMY_API_KEY } from "@/constants";

const config = createConfig({
chains: [mainnet],
Expand All @@ -20,34 +20,44 @@ const config = createConfig({
},
});

// TODO: update with ODS wallet module - [https://linear.app/aragon/issue/RD-198/create-ods-walletmodule]
const WalletContainer = () => {
const { address } = useAccount();
const result = useEnsName({
const { open } = useWeb3Modal();
const { address, isConnected } = useAccount();

const { data: ensName } = useEnsName({
config,
chainId: mainnet.id,
address: address,
});
const avatarResult = useEnsAvatar({

const { data: ensAvatar } = useEnsAvatar({
config,
name: normalize(result.data!),
name: normalize(ensName!),
chainId: mainnet.id,
gatewayUrls: ["https://cloudflare-ipfs.com"],
query: { enabled: !!ensName },
});
const { open } = useWeb3Modal();

return (
<Card
className="
absolute right-0 top-0 m-2 flex cursor-pointer items-center !rounded-full border
border-neutral-200 px-1 py-1 hover:drop-shadow md:relative md:right-auto md:top-auto md:m-0
"
<button
className={classNames(
"shrink-none flex h-12 items-center rounded-full border border-neutral-100 bg-neutral-0 leading-tight text-neutral-500",
{ "px-1 md:px-0 md:pl-4 md:pr-1": isConnected },
{ "px-4": !isConnected }
)}
onClick={() => open()}
>
<Link className="mx-3 !text-sm" variant="neutral">
{result.data ? result.data : formatHexString(address as Address)}
</Link>
<Avatar src={avatarResult.data ? avatarResult.data : "/profile.jpg"} size="md" alt="Profile picture" />
</Card>
{isConnected && address && (
<div className="flex items-center gap-3">
<span className="hidden md:block">{ensName ?? formatHexString(address)}</span>
{!!ensAvatar && <Avatar src={ensAvatar ?? "/profile.jpg"} alt="Profile picture" size="md" />}
{ensAvatar == null && <Blockies className="rounded-full" size={10} seed={address} />}
</div>
)}

{!isConnected && <span>Connect</span>}
</button>
);
};

Expand Down
29 changes: 0 additions & 29 deletions components/header.tsx

This file was deleted.

20 changes: 10 additions & 10 deletions components/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import Header from "@/components/header";
import Sidebar from "@/components/sidebar";
import { type ReactNode } from "react";
import { Navbar } from "./nav/navbar";

export function Layout({ children }: { children: ReactNode }) {
export const Layout: React.FC<{ children: ReactNode }> = (props) => {
return (
<div className="flex h-screen">
<Sidebar />
<div className="flex w-full flex-col items-center overflow-y-auto">
<div className="w-full p-3 md:w-4/5 lg:w-2/3 2xl:w-3/5">
<Header />
{children}
<div className="flex flex-col items-center gap-20">
<div className="flex w-full flex-col items-center">
<Navbar />
<div className="flex w-full flex-col items-center px-4 py-6 md:w-4/5 md:p-6 lg:w-2/3 xl:py-10 2xl:w-3/5">
{props.children}
</div>
</div>

{/* Footer */}
</div>
);
}
};
65 changes: 65 additions & 0 deletions components/nav/navLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import classNames from "classnames";
import Link from "next/link";
import { useRouter } from "next/router";
import { type ParsedUrlQuery } from "querystring";
import { resolveQueryParam } from "@/utils/query";

interface INavLinkProps {
id: string;
label: string;
path: string;
}

export const NavLink: React.FC<INavLinkProps> = (props) => {
const { id, label, path } = props;
const { pathname, query } = useRouter();
const pluginId = resolvePluginId(pathname, query);

const isHome = path === "/";
const isPluginPathname = pathname === "/plugins/[id]";

let selected;
if (isHome) {
// strict comparison for home page
selected = pathname === path;
} else if (isPluginPathname) {
// compare resolved pluginId from query params with plugin id
selected = pluginId === id;
} else {
// check if current path starts with tab path so that
// the nav item is selected when the user is on a nested page
selected = pathname.startsWith(path);
}

return (
<Link href={path} aria-current={selected ? "page" : undefined}>
<div
className={classNames("py-3", {
"border-b-2 border-b-primary-400": selected,
})}
>
<span
className={classNames("text-neutral-500", {
"text-neutral-800": selected,
})}
>
{label}
</span>
</div>
</Link>
);
};

/**
* Resolves the plugin ID from the given pathname and query parameters.
*
* @param pathname - The current pathname.
* @param queryParams - The parsed query parameters.
* @returns The resolved plugin ID or null if the pathname is not "/plugins/[id]"
* or the ID is not found in the query parameters.
*/
function resolvePluginId(pathname: string, queryParams: ParsedUrlQuery): string | null {
if (pathname !== "/plugins/[id]") return null;

return resolveQueryParam(queryParams.id) || null;
}
50 changes: 50 additions & 0 deletions components/nav/navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import WalletContainer from "@/components/WalletContainer";
import { plugins } from "@/plugins";
import { AvatarIcon, IconType } from "@aragon/ods";
import Image from "next/image";
import Link from "next/link";
import { NavLink } from "./navLink";

export const Navbar: React.FC = () => {
const NavLinks = [
{ path: "/", id: "dashboard", name: "Dashboard" },
...plugins.map((p) => ({
id: p.id,
name: p.title,
path: `/plugins/${p.id}/#/`,
})),
];

return (
<nav className="sticky top-0 w-full flex-col gap-2 border-b border-b-neutral-100 bg-neutral-0 p-3 md:px-6 md:pb-0 md:pt-5 lg:gap-3">
<div className="flex w-full items-center justify-between">
<Link href="/" className="flex items-center gap-x-3">
<Image src="/logo-bw-lg.png" width="60" height="60" className="w-9 shrink-0" alt="Aragonette" />
<span className="py-1 text-lg font-semibold leading-tight text-neutral-700 md:text-xl">Aragonette</span>
</Link>

<div className="flex items-center gap-x-2">
<div className="shrink-0">
<WalletContainer />
</div>

{/* Nav Trigger */}
<button className="rounded-full border border-neutral-100 bg-neutral-0 p-1 md:hidden">
<AvatarIcon size="lg" icon={IconType.MENU} />
</button>
</div>
</div>

{/* Tab wrapper */}
<ul className="-mb-0.25 hidden gap-x-10 md:flex lg:pl-14">
{NavLinks.map((tab) => {
return (
<li key={tab.id}>
<NavLink label={tab.name} path={tab.path} id={tab.id} />
</li>
);
})}
</ul>
</nav>
);
};
Loading
Loading