Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ jobs:
COVERAGE_THRESHOLD_LINES: 97.29
COVERAGE_THRESHOLD_FUNCTIONS: 94.33
COVERAGE_THRESHOLD_STATEMENTS: 97.18
COVERAGE_THRESHOLD_BRANCHES: 94.25
COVERAGE_THRESHOLD_BRANCHES: 94.16
COVERAGE_INCLUDE_PATH: src/domains/portfolio
with:
timeout_minutes: 10
Expand Down Expand Up @@ -763,7 +763,7 @@ jobs:
COVERAGE_THRESHOLD_LINES: 100
COVERAGE_THRESHOLD_FUNCTIONS: 99.62
COVERAGE_THRESHOLD_STATEMENTS: 100
COVERAGE_THRESHOLD_BRANCHES: 100
COVERAGE_THRESHOLD_BRANCHES: 99.84
COVERAGE_INCLUDE_PATH: src/domains/transaction/components/[T-Z]*
uses: nick-invision/retry@v2
with:
Expand Down
255 changes: 183 additions & 72 deletions src/app/components/NavigationBar/NavigationBar.blocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { generatePath, NavLink, useLocation, useNavigate } from "react-router-do
import cn from "classnames";
import { NavigationBarFullProperties, NavigationBarLogoOnlyProperties } from "./NavigationBar.contracts";
import { Button } from "@/app/components/Button";
import { DropdownOption } from "@/app/components/Dropdown/Dropdown.contracts";
import { DropdownRoot, DropdownToggle, DropdownContent, DropdownListItem } from "@/app/components/SimpleDropdown";
import { Divider } from "@/app/components/Divider";
import { Icon } from "@/app/components/Icon";
Expand All @@ -14,7 +13,7 @@ import { UserMenu } from "@/app/components/NavigationBar/components/UserMenu/Use
import { NotificationsDropdown } from "@/app/components/Notifications";
import { ServerStatusIndicator } from "@/app/components/ServerStatusIndicator";
import { Tooltip } from "@/app/components/Tooltip";
import { getNavigationMenu } from "@/app/constants/navigation";

import { useNavigationContext } from "@/app/contexts";
import { useActiveProfile, useBreakpoint, useInputFocus } from "@/app/hooks";
import { ReceiveFunds } from "@/domains/wallet/components/ReceiveFunds";
Expand Down Expand Up @@ -225,14 +224,6 @@ export const NavigationBarFull: React.FC<NavigationBarFullProperties> = ({
.filter((wallet) => wallet.network().id() === activeNetwork.id());
}, [profile, isProfileRestored, activeNetwork]);

const navigationMenu = useMemo(() => getNavigationMenu(t, location.pathname), [t, location.pathname]);
const handleSelectMenuItem = useCallback(
({ value }: DropdownOption) => {
navigate(String(value));
},
[navigate],
);

const network = useMemo(
() => profile.availableNetworks().find((network) => network.id() === selectedWallet?.network.id()),
[selectedWallet, profile],
Expand All @@ -241,82 +232,202 @@ export const NavigationBarFull: React.FC<NavigationBarFullProperties> = ({
const isMenuItemDisabled = (id: string) =>
["tokens", "votes", "exchange"].includes(id) && profile.wallets().count() === 0;

const renderNavigationMenu = () => (
<>
<ul className="hidden h-12 items-center gap-0.5 xl:flex" data-testid="NavigationBar__menu">
{navigationMenu.map((menuItem, index) => {
if (isMenuItemDisabled(menuItem.id)) {
return (
<li key={index} className="flex">
<Tooltip content={menuItem.disabledMessage}>
const isActive = (pathCheck: string) => location.pathname.includes(pathCheck);

const navLinkBaseClass =
"ring-focus focus:outline-hidden relative flex h-fit items-center rounded border px-2 py-1 text-sm font-semibold leading-[17px] transition-all duration-200 hover:bg-theme-secondary-200 hover:text-theme-secondary-900 dim-hover:bg-theme-dim-700 dark:hover:bg-theme-dark-700 dark:hover:text-theme-dark-50";

const renderNavigationMenu = () => {
const hideExchange = import.meta.env.VITE_HIDE_EXCHANGE_TAB === "true";

return (
<>
<ul className="hidden h-12 items-center gap-0.5 xl:flex" data-testid="NavigationBar__menu">
{/* Dashboard */}
<li key="dashboard" className="flex">
<NavLink
to={generatePath(ProfilePaths.Dashboard, { profileId: profile.id() })}
title={t("COMMON.PORTFOLIO")}
className={cn(navLinkBaseClass, {
"border-theme-primary-200 bg-theme-secondary-200 text-theme-primary-600 dim:border-theme-dim-700 dim:bg-theme-dim-950 dim:text-theme-dim-50 dark:border-theme-dark-700 dark:bg-theme-dark-950 dark:text-theme-dark-50":
isActive("/dashboard"),
"border-transparent bg-transparent text-theme-secondary-700 dim:text-theme-dim-200 dark:text-theme-dark-200":
!isActive("/dashboard"),
})}
>
{t("COMMON.PORTFOLIO")}
</NavLink>
</li>

{/* Tokens */}
<li key="tokens" className="flex">
{isMenuItemDisabled("tokens") ? (
<Tooltip content={t("COMMON.TOKENS_DISABLED")}>
<span className="cursor-pointer border-transparent bg-transparent px-2 py-1 text-sm font-semibold leading-[17px] text-theme-secondary-500 dim:text-theme-dim-500 dark:text-theme-dark-500">
{" "}
{t("COMMON.TOKENS")}{" "}
</span>
</Tooltip>
) : (
<NavLink
to={generatePath(ProfilePaths.Tokens, { profileId: profile.id() })}
title={t("COMMON.TOKENS")}
className={cn(navLinkBaseClass, {
"border-theme-primary-200 bg-theme-secondary-200 text-theme-primary-600 dim:border-theme-dim-700 dim:bg-theme-dim-950 dim:text-theme-dim-50 dark:border-theme-dark-700 dark:bg-theme-dark-950 dark:text-theme-dark-50":
isActive("/tokens"),
"border-transparent bg-transparent text-theme-secondary-700 dim:text-theme-dim-200 dark:text-theme-dark-200":
!isActive("/tokens"),
})}
>
{t("COMMON.TOKENS")}
</NavLink>
)}
</li>

{/* Exchange (conditional) */}
{!hideExchange && (
<li key="exchange" className="flex">
{isMenuItemDisabled("exchange") ? (
<Tooltip content={t("COMMON.EXCHANGE_DISABLED")}>
<span className="cursor-pointer border-transparent bg-transparent px-2 py-1 text-sm font-semibold leading-[17px] text-theme-secondary-500 dim:text-theme-dim-500 dark:text-theme-dark-500">
{" "}
{menuItem.title}{" "}
{t("COMMON.EXCHANGE")}{" "}
</span>
</Tooltip>
</li>
);
}

return (
<li key={index} className="flex">
<NavLink
to={menuItem.mountPath(profile.id())}
title={menuItem.title}
className={cn(
"ring-focus focus:outline-hidden relative flex h-fit items-center rounded border px-2 py-1 text-sm font-semibold leading-[17px] transition-all duration-200 hover:bg-theme-secondary-200 hover:text-theme-secondary-900 dim-hover:bg-theme-dim-700 dark:hover:bg-theme-dark-700 dark:hover:text-theme-dark-50",
{
) : (
<NavLink
to={generatePath(ProfilePaths.Exchange, { profileId: profile.id() })}
title={t("COMMON.EXCHANGE")}
className={cn(navLinkBaseClass, {
"border-theme-primary-200 bg-theme-secondary-200 text-theme-primary-600 dim:border-theme-dim-700 dim:bg-theme-dim-950 dim:text-theme-dim-50 dark:border-theme-dark-700 dark:bg-theme-dark-950 dark:text-theme-dark-50":
menuItem.isActive,
isActive("/exchange"),
"border-transparent bg-transparent text-theme-secondary-700 dim:text-theme-dim-200 dark:text-theme-dark-200":
!menuItem.isActive,
},
)}
>
{menuItem.title}
</NavLink>
!isActive("/exchange"),
})}
>
{t("COMMON.EXCHANGE")}
</NavLink>
)}
</li>
);
})}
</ul>
<div
data-testid="NavigationBar__menu-toggle"
className="mr-auto flex content-center items-center xl:hidden"
>
<DropdownRoot>
<DropdownToggle>
{(isOpen) => (
<button
type="button"
className="focus:outline-hidden flex h-7 cursor-pointer items-center rounded text-theme-secondary-700 focus:ring-2 focus:ring-theme-primary-400 dark:text-theme-dark-200"
)}

{/* Contacts */}
<li key="contacts" className="flex">
<NavLink
to={generatePath(ProfilePaths.Contacts, { profileId: profile.id() })}
title={t("COMMON.CONTACTS")}
className={cn(navLinkBaseClass, {
"border-theme-primary-200 bg-theme-secondary-200 text-theme-primary-600 dim:border-theme-dim-700 dim:bg-theme-dim-950 dim:text-theme-dim-50 dark:border-theme-dark-700 dark:bg-theme-dark-950 dark:text-theme-dark-50":
isActive("/contacts"),
"border-transparent bg-transparent text-theme-secondary-700 dim:text-theme-dim-200 dark:text-theme-dark-200":
!isActive("/contacts"),
})}
>
{t("COMMON.CONTACTS")}
</NavLink>
</li>

{/* Votes */}
<li key="votes" className="flex">
{isMenuItemDisabled("votes") ? (
<Tooltip content={t("COMMON.VOTES_DISABLED")}>
<span className="cursor-pointer border-transparent bg-transparent px-2 py-1 text-sm font-semibold leading-[17px] text-theme-secondary-500 dim:text-theme-dim-500 dark:text-theme-dark-500">
{" "}
{t("COMMON.VOTES")}{" "}
</span>
</Tooltip>
) : (
<NavLink
to={generatePath(ProfilePaths.Votes, { profileId: profile.id() })}
title={t("COMMON.VOTES")}
className={cn(navLinkBaseClass, {
"border-theme-primary-200 bg-theme-secondary-200 text-theme-primary-600 dim:border-theme-dim-700 dim:bg-theme-dim-950 dim:text-theme-dim-50 dark:border-theme-dark-700 dark:bg-theme-dark-950 dark:text-theme-dark-50":
isActive("/votes"),
"border-transparent bg-transparent text-theme-secondary-700 dim:text-theme-dim-200 dark:text-theme-dark-200":
!isActive("/votes"),
})}
>
<Icon size="lg" name={isOpen ? "MenuOpen" : "Menu"} />
</button>
{t("COMMON.VOTES")}
</NavLink>
)}
</DropdownToggle>
<DropdownContent>
<ul data-testid="dropdown__options">
{navigationMenu.map((menuItem, index) => (
</li>
</ul>

{/* Mobile dropdown */}
<div
data-testid="NavigationBar__menu-toggle"
className="mr-auto flex content-center items-center xl:hidden"
>
<DropdownRoot>
<DropdownToggle>
{(isOpen) => (
<button
type="button"
className="focus:outline-hidden flex h-7 cursor-pointer items-center rounded text-theme-secondary-700 focus:ring-2 focus:ring-theme-primary-400 dark:text-theme-dark-200"
>
<Icon size="lg" name={isOpen ? "MenuOpen" : "Menu"} />
</button>
)}
</DropdownToggle>
<DropdownContent>
<ul data-testid="dropdown__options">
<DropdownListItem
key={menuItem.id}
disabled={isMenuItemDisabled(menuItem.id)}
data-testid={`dropdown__option--${index}`}
key="dashboard"
data-testid="dropdown__option--0"
onClick={() =>
handleSelectMenuItem({
label: menuItem.title,
value: menuItem.id,
} as DropdownOption)
navigate(generatePath(ProfilePaths.Dashboard, { profileId: profile.id() }))
}
>
{menuItem.title}
{t("COMMON.PORTFOLIO")}
</DropdownListItem>
))}
</ul>
</DropdownContent>
</DropdownRoot>
</div>
</>
);
<DropdownListItem
key="tokens"
disabled={isMenuItemDisabled("tokens")}
data-testid="dropdown__option--1"
onClick={() =>
navigate(generatePath(ProfilePaths.Tokens, { profileId: profile.id() }))
}
>
{t("COMMON.TOKENS")}
</DropdownListItem>
{!hideExchange && (
<DropdownListItem
key="exchange"
disabled={isMenuItemDisabled("exchange")}
data-testid="dropdown__option--2"
onClick={() =>
navigate(generatePath(ProfilePaths.Exchange, { profileId: profile.id() }))
}
>
{t("COMMON.EXCHANGE")}
</DropdownListItem>
)}
<DropdownListItem
key="contacts"
data-testid="dropdown__option--3"
onClick={() =>
navigate(generatePath(ProfilePaths.Contacts, { profileId: profile.id() }))
}
>
{t("COMMON.CONTACTS")}
</DropdownListItem>
<DropdownListItem
key="votes"
disabled={isMenuItemDisabled("votes")}
data-testid="dropdown__option--4"
onClick={() =>
navigate(generatePath(ProfilePaths.Votes, { profileId: profile.id() }))
}
>
{t("COMMON.VOTES")}
</DropdownListItem>
</ul>
</DropdownContent>
</DropdownRoot>
</div>
</>
);
};

const userInitials = useMemo(() => {
if (!isProfileRestored) {
Expand Down
18 changes: 2 additions & 16 deletions src/app/components/NavigationBar/NavigationBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import userEvent from "@testing-library/user-event";
import React, { useEffect } from "react";

import { NavigationBar } from "./NavigationBar";
import * as navigation from "@/app/constants/navigation";
import { translations } from "@/app/i18n/common/i18n";
import * as environmentHooks from "@/app/hooks/env";
import { useNavigationContext } from "@/app/contexts";
Expand All @@ -28,19 +27,6 @@ vi.spyOn(environmentHooks, "useActiveProfile").mockImplementation(() =>
mockedTestEnvironment.profiles().findById(getMainsailProfileId()),
);

vi.spyOn(navigation, "getNavigationMenu").mockReturnValue([
{
id: "dashboard",
mountPath: (profileId: string) => `/profiles/${profileId}/dashboard`,
title: "Portfolio",
},
{
id: "test",
mountPath: () => "/test",
title: "test",
},
]);

const ContainerWithFixedFormButtons = ({ children }) => {
const { setHasFixedFormButtons } = useNavigationContext();
useEffect(() => {
Expand Down Expand Up @@ -187,9 +173,9 @@ describe("NavigationBar", () => {
it("should handle menu click", async () => {
const { router } = render(<NavigationBar />);

await userEvent.click(screen.getByText("test"));
await userEvent.click(screen.getByText(translations.PORTFOLIO));

expect(router.state.location.pathname).toBe("/test");
expect(router.state.location.pathname).toContain("/dashboard");
});

it("should handle menu click in small screen variant", async () => {
Expand Down
Loading
Loading