-
-
- Back to console
-
-
-
- Email alerts
-
- {showUpdateEmail && (
-
- )}
-
-
- Receive alerts when your account has less than 30 days of service runway remaining, so you can top up before
- services are affected.
-
+
+
+
+
+ Back to console
+
+
+
+
+ Email alerts
+
+ {showUpdateEmail ? (
+
+ ) : null}
-
-
{renderContent()}
+
+ Receive alerts when your account has less than 30 days of service runway remaining, so you can top up before
+ services are affected.
+
-
+
+
{renderContent()}
+
);
};
-const NotificationsPage = () => (
-
-
-
-);
-
export default NotificationsPage;
diff --git a/apps/explorer/src/app/console/(console)/page.tsx b/apps/explorer/src/app/console/(console)/page.tsx
new file mode 100644
index 00000000..e735e9d2
--- /dev/null
+++ b/apps/explorer/src/app/console/(console)/page.tsx
@@ -0,0 +1,70 @@
+"use client";
+import { LoadingStateCard } from "@filecoin-foundation/ui-filecoin/LoadingStateCard";
+import type { Account } from "@filecoin-pay/types";
+import { useConnection } from "wagmi";
+import { AlertsBanner, FundsSection, OperatorApprovalsSection, RailsSection } from "@/components/UserConsole";
+import { AccountNotFound, ErrorState } from "@/components/UserConsole/States";
+import { useAccountDetails } from "@/hooks/useAccountDetails";
+import { useNotificationStatus } from "@/hooks/useNotificationStatus";
+import { getNetworkFromChainId, isNotificationsEligibleNetwork } from "@/utils/network";
+
+type AccountSectionsProps = {
+ account: Account | null | undefined;
+ // React Query guarantees error is non-null exactly when the query has failed,
+ // so it doubles as the error flag.
+ error: Error | null;
+ isLoading: boolean;
+ subscribed: boolean;
+ userAddress: string;
+};
+
+const AccountSections = ({ account, error, isLoading, subscribed, userAddress }: AccountSectionsProps) => {
+ if (isLoading) {
+ return
;
+ }
+
+ if (!account) {
+ return error ?
:
;
+ }
+
+ return (
+ <>
+
+
+
+
+ {/* A failed background refetch still leaves the last good account on screen. */}
+ {error ?
: null}
+ >
+ );
+};
+
+const UserConsole = () => {
+ const { address, chainId } = useConnection();
+ const walletNetwork = getNetworkFromChainId(chainId);
+
+ const { data: notificationStatus, isError: isNotificationStatusError } = useNotificationStatus(address);
+ const isSubscribed = notificationStatus?.subscribed === true;
+ const showAlertsBanner = isNotificationsEligibleNetwork(walletNetwork) && !isSubscribed && !isNotificationStatusError;
+
+ const accountQuery = useAccountDetails(address ?? "", { networkOverride: walletNetwork });
+
+ return (
+
+ {showAlertsBanner ?
: null}
+
+ {/* The (console) layout gates on a connected wallet, so address is set here. */}
+ {address ? (
+
+ ) : null}
+
+ );
+};
+
+export default UserConsole;
diff --git a/apps/explorer/src/app/console/notifications/verify/page.tsx b/apps/explorer/src/app/console/notifications/verify/page.tsx
index e110ea80..9687463e 100644
--- a/apps/explorer/src/app/console/notifications/verify/page.tsx
+++ b/apps/explorer/src/app/console/notifications/verify/page.tsx
@@ -1,5 +1,5 @@
"use client";
-import { PageSection } from "@filecoin-foundation/ui-filecoin/PageSection";
+import { Container } from "@filecoin-foundation/ui-filecoin/Container";
import { Card } from "@filecoin-pay/ui/components/card";
import { useQueryClient } from "@tanstack/react-query";
import { AlertCircle, CheckCircle2, Clock, Link2Off, Loader2 } from "lucide-react";
@@ -7,6 +7,7 @@ import Link from "next/link";
import { useSearchParams } from "next/navigation";
import type { ReactNode } from "react";
import { Suspense, useCallback, useEffect, useRef, useState } from "react";
+import { ConsoleHeader } from "@/components/UserConsole/ConsoleHeader";
const API_URL = process.env.NEXT_PUBLIC_NOTIFICATIONS_API_URL;
@@ -158,106 +159,110 @@ const VerifyContent = () => {
}, [verifyState.type]);
return (
-
-
-
- {verifyState.type === "loading" && }
+
+
+ {verifyState.type === "loading" && }
- {verifyState.type === "success" && (
- }
- color='green'
- title='Alerts are now on'
- description="Your email has been verified. We'll notify you when this account has less than 30 days of service runway remaining."
- actions={
-
-
- Back to Console
-
-
- Manage alerts
-
-
- }
- />
- )}
+ {verifyState.type === "success" && (
+ }
+ color='green'
+ title='Alerts are now on'
+ description="Your email has been verified. We'll notify you when this account has less than 30 days of service runway remaining."
+ actions={
+
+
+ Back to Console
+
+
+ Manage alerts
+
+
+ }
+ />
+ )}
- {verifyState.type === "not-found" && (
- }
- color='amber'
- title='This verification link is no longer available'
- description='It may have expired or already been used. Return to alert settings to request a new verification email.'
- actions={}
- />
- )}
+ {verifyState.type === "not-found" && (
+ }
+ color='amber'
+ title='This verification link is no longer available'
+ description='It may have expired or already been used. Return to alert settings to request a new verification email.'
+ actions={}
+ />
+ )}
- {verifyState.type === "rate-limited" && (
- }
- color='amber'
- title='Too many attempts'
- description='Please wait a few minutes before trying again, then return to alert settings to request a new verification email.'
- actions={}
- />
- )}
+ {verifyState.type === "rate-limited" && (
+ }
+ color='amber'
+ title='Too many attempts'
+ description='Please wait a few minutes before trying again, then return to alert settings to request a new verification email.'
+ actions={}
+ />
+ )}
- {verifyState.type === "error" && (
- }
- color='red'
- title="We couldn't verify your email"
- description={
- verifyState.kind === "network"
- ? "Check your internet connection and try again."
- : "Something went wrong on our end. Try again in a moment, or return to alert settings to request a new verification email."
- }
- detail={verifyState.detail}
- actions={
-
- }
- />
- )}
+ {verifyState.type === "error" && (
+ }
+ color='red'
+ title="We couldn't verify your email"
+ description={
+ verifyState.kind === "network"
+ ? "Check your internet connection and try again."
+ : "Something went wrong on our end. Try again in a moment, or return to alert settings to request a new verification email."
+ }
+ detail={verifyState.detail}
+ actions={
+
+ }
+ />
+ )}
- {verifyState.type === "missing-params" && (
- }
- color='amber'
- title='This verification link is incomplete'
- description="We couldn't find the information needed to verify your email. Return to alert settings to request a new verification email."
- actions={}
- />
- )}
-
-
-
+ {verifyState.type === "missing-params" && (
+ }
+ color='amber'
+ title='This verification link is incomplete'
+ description="We couldn't find the information needed to verify your email. Return to alert settings to request a new verification email."
+ actions={}
+ />
+ )}
+
+
);
};
const VerifyPage = () => (
-
-
-
-
-
- }
- >
-
-
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
);
export default VerifyPage;
diff --git a/apps/explorer/src/app/console/page.tsx b/apps/explorer/src/app/console/page.tsx
deleted file mode 100644
index 9e0096db..00000000
--- a/apps/explorer/src/app/console/page.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-"use client";
-import { LoadingStateCard } from "@filecoin-foundation/ui-filecoin/LoadingStateCard";
-import { PageSection } from "@filecoin-foundation/ui-filecoin/PageSection";
-import { AlertTriangle } from "lucide-react";
-import { useMemo } from "react";
-import { useConnection } from "wagmi";
-import { Balance, ChainSwitcher } from "@/components/shared";
-import {
- AlertsBanner,
- BetaWarning,
- FundsSection,
- OperatorApprovalsSection,
- RailsSection,
-} from "@/components/UserConsole";
-import ConsoleProviders from "@/components/UserConsole/ConsoleProviders";
-import { AccountNotFound, ErrorState, NotConnected, UnsupportedChain } from "@/components/UserConsole/States";
-import { useAccountDetails } from "@/hooks/useAccountDetails";
-import { useNotificationStatus } from "@/hooks/useNotificationStatus";
-import { getNetworkFromChainId, isNotificationsEligibleNetwork, isSupportedChainId } from "@/utils/network";
-
-const UserConsoleContent = () => {
- const { address, isConnected, chainId } = useConnection();
- const walletNetwork = useMemo(() => getNetworkFromChainId(chainId), [chainId]);
- const isUnsupportedChain = isConnected && chainId && !isSupportedChainId(chainId);
-
- const isNotificationsEligible = isNotificationsEligibleNetwork(walletNetwork);
-
- const { data: notificationStatus, isError: notificationStatusError } = useNotificationStatus(address);
- const subscribed = notificationStatus?.subscribed ?? false;
-
- const {
- data: account,
- isLoading,
- isError,
- error,
- } = useAccountDetails(address || "", { networkOverride: walletNetwork });
-
- return (
-
-
-
-
- Filecoin Pay Console
-
- {isConnected && (
-
- {isUnsupportedChain ? (
-
-
- Unsupported Network
-
- ) : (
- <>
-
- {chainId &&
}
- >
- )}
-
- )}
-
-
- {/* Beta Warning */}
-
-
- {/* Alerts Banner — hidden once subscribed */}
- {isConnected && !isUnsupportedChain && isNotificationsEligible && !subscribed && !notificationStatusError && (
-
- )}
-
- {/* Not Connected */}
- {(!isConnected || !address) &&
}
-
- {/* Unsupported Chain */}
- {isUnsupportedChain &&
}
-
- {/* Only show content if connected to supported chain */}
- {isConnected && !isUnsupportedChain && (
- <>
- {/* Loading */}
- {isLoading &&
}
-
- {/* Account Not Found */}
- {!isError && !isLoading && !account &&
}
-
- {!isLoading && address && account && (
- <>
-
-
-
- >
- )}
-
- {/* Error */}
- {isError &&
}
- >
- )}
-
-
- );
-};
-
-const UserConsole = () => {
- return (
-
-
-
- );
-};
-
-export default UserConsole;
diff --git a/apps/explorer/src/components/UserConsole/ConsoleHeader.tsx b/apps/explorer/src/components/UserConsole/ConsoleHeader.tsx
new file mode 100644
index 00000000..85eb12a6
--- /dev/null
+++ b/apps/explorer/src/components/UserConsole/ConsoleHeader.tsx
@@ -0,0 +1,31 @@
+import { Container } from "@filecoin-foundation/ui-filecoin/Container";
+import Link from "next/link";
+import type { ReactNode } from "react";
+import Logo from "@/public/foc-logo-dark.svg";
+
+type ConsoleHeaderProps = {
+ /**
+ * Wallet controls for the right-hand side. Only the gated console shell passes
+ * these — the header itself must render without wagmi/RainbowKit providers so
+ * ungated pages (e.g. the email verification landing page) can reuse it.
+ */
+ walletControls?: ReactNode;
+};
+
+export const ConsoleHeader = ({ walletControls }: ConsoleHeaderProps) => (
+
+
+
+
+
+
+
+ {walletControls ? (
+
+ {walletControls}
+
+ ) : null}
+
+
+
+);
diff --git a/apps/explorer/src/components/shared/ConditionalNavigation.tsx b/apps/explorer/src/components/shared/ConditionalNavigation.tsx
new file mode 100644
index 00000000..73ade611
--- /dev/null
+++ b/apps/explorer/src/components/shared/ConditionalNavigation.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { usePathname } from "next/navigation";
+import Navigation from "./Navigation/Navigation";
+
+// Console routes render their own header (see UserConsole/ConsoleHeader), so the
+// global site navigation is suppressed there. Every other route is unaffected.
+function isConsoleRoute(pathname: string | null): boolean {
+ return pathname === "/console" || Boolean(pathname?.startsWith("/console/"));
+}
+
+function ConditionalNavigation() {
+ const pathname = usePathname();
+
+ if (isConsoleRoute(pathname)) return null;
+
+ return
;
+}
+
+export default ConditionalNavigation;
diff --git a/apps/explorer/src/components/shared/SiteLayout.tsx b/apps/explorer/src/components/shared/SiteLayout.tsx
index 4382b5de..75f9274f 100644
--- a/apps/explorer/src/components/shared/SiteLayout.tsx
+++ b/apps/explorer/src/components/shared/SiteLayout.tsx
@@ -3,7 +3,7 @@ import localFont from "next/font/local";
import Script from "next/script";
import type { ReactNode } from "react";
-import { Footer, Navigation, Providers } from "@/components/shared";
+import { ConditionalNavigation, Footer, Providers } from "@/components/shared";
const funnelSans = localFont({
src: "../../fonts/Funnel_Sans/FunnelSans[wght].woff2",
@@ -42,7 +42,7 @@ function SiteLayout({ children }: SiteLayoutProps) {
)}
>
-
+
{children}
diff --git a/apps/explorer/src/components/shared/index.ts b/apps/explorer/src/components/shared/index.ts
index 523e9c0d..eb2ba4fc 100644
--- a/apps/explorer/src/components/shared/index.ts
+++ b/apps/explorer/src/components/shared/index.ts
@@ -1,6 +1,7 @@
import AllowanceDisplay from "./AllowanceDisplay";
import Balance from "./Balance";
import ChainSwitcher from "./ChainSwitcher";
+import ConditionalNavigation from "./ConditionalNavigation";
import CopyableText from "./CopyableText";
import CopyButton from "./CopyButton";
import CustomConnectButton from "./CustomConnectButton";
@@ -25,6 +26,7 @@ export {
AllowanceDisplay,
Balance,
ChainSwitcher,
+ ConditionalNavigation,
CopyableText,
CopyButton,
CustomConnectButton,
From 8c52fb1ed24430719dcb0c8f221b3b9841429622 Mon Sep 17 00:00:00 2001
From: Natalie Bravo
Date: Fri, 21 Aug 2026 17:19:19 -0300
Subject: [PATCH 04/10] feat(explorer): console sidebar and mobile nav drawer
(#325)
Add a console sidebar with Dashboard, Email Alerts, and Pay Explorer items. It renders inside the access gate, so a disconnected wallet still gets the connect state at full width with no sidebar.
Below `lg` the sidebar moves into a right-side sheet opened from a hamburger in the console header. The trigger's `lg:hidden` and the desktop column's hidden `lg:flex` are exact complements, so no viewport shows both or neither. The header now flips from stacked to inline at `md` rather than `sm`, and keeps the trigger on the logo row on narrow tablets.
The drawer closes on sidebar clicks and on any pathname change. Back and forward leave this layout mounted and fire no click to react to, so the click handler alone would strand the drawer over the destination page.
The Email Alerts item carries the notification status that `FundsSection` showed as a chip. An unknown status renders a neutral bell with no ON/OFF label.
---
.../src/app/console/(console)/layout.tsx | 18 +++-
.../console/(console)/notifications/page.tsx | 11 +-
.../src/app/console/(console)/page.tsx | 6 +-
.../components/UserConsole/AlertsStatus.tsx | 32 ------
.../components/UserConsole/ConsoleHeader.tsx | 15 ++-
.../UserConsole/ConsoleNavDrawer.tsx | 44 ++++++++
.../components/UserConsole/ConsoleSidebar.tsx | 101 ++++++++++++++++++
.../UserConsole/FundsSection/index.tsx | 12 +--
.../src/components/UserConsole/index.ts | 1 -
.../src/components/shared/Balance.tsx | 2 +-
.../src/components/shared/ChainSwitcher.tsx | 2 +-
11 files changed, 180 insertions(+), 64 deletions(-)
delete mode 100644 apps/explorer/src/components/UserConsole/AlertsStatus.tsx
create mode 100644 apps/explorer/src/components/UserConsole/ConsoleNavDrawer.tsx
create mode 100644 apps/explorer/src/components/UserConsole/ConsoleSidebar.tsx
diff --git a/apps/explorer/src/app/console/(console)/layout.tsx b/apps/explorer/src/app/console/(console)/layout.tsx
index 4546fb02..9e4f743c 100644
--- a/apps/explorer/src/app/console/(console)/layout.tsx
+++ b/apps/explorer/src/app/console/(console)/layout.tsx
@@ -7,7 +7,9 @@ import Balance from "@/components/shared/Balance";
import ChainSwitcher from "@/components/shared/ChainSwitcher";
import { BetaWarning } from "@/components/UserConsole/BetaWarning";
import { ConsoleHeader } from "@/components/UserConsole/ConsoleHeader";
+import { ConsoleNavDrawer } from "@/components/UserConsole/ConsoleNavDrawer";
import ConsoleProviders from "@/components/UserConsole/ConsoleProviders";
+import { ConsoleSidebar } from "@/components/UserConsole/ConsoleSidebar";
import { NotConnected, UnsupportedChain } from "@/components/UserConsole/States";
import { isSupportedChainId } from "@/utils/network";
@@ -81,13 +83,25 @@ const ConsoleShell = ({ children }: { children: ReactNode }) => {
return (
-
} />
+
}
+ navTrigger={accessState === "ready" ?
: null}
+ />
+ {/* BetaWarning sits above the row so it shows on every console page. */}
-
{children}
+
+
+ {/* Exact complement of the drawer trigger's `lg:hidden`. */}
+
+
+
+
{children}
+
+
diff --git a/apps/explorer/src/app/console/(console)/notifications/page.tsx b/apps/explorer/src/app/console/(console)/notifications/page.tsx
index 6807dc48..aaebe2f7 100644
--- a/apps/explorer/src/app/console/(console)/notifications/page.tsx
+++ b/apps/explorer/src/app/console/(console)/notifications/page.tsx
@@ -3,8 +3,7 @@ import { Button } from "@filecoin-foundation/ui-filecoin/Button";
import { EmptyStateCard } from "@filecoin-foundation/ui-filecoin/EmptyStateCard";
import { WarningCircleIcon } from "@phosphor-icons/react";
import { useMutation } from "@tanstack/react-query";
-import { ArrowLeft, ChevronRight, WifiOff } from "lucide-react";
-import Link from "next/link";
+import { ChevronRight, WifiOff } from "lucide-react";
import type React from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { BaseError, UserRejectedRequestError } from "viem";
@@ -439,14 +438,6 @@ const NotificationsPage = () => {
return (
-
-
- Back to console
-
-
Email alerts
diff --git a/apps/explorer/src/app/console/(console)/page.tsx b/apps/explorer/src/app/console/(console)/page.tsx
index e735e9d2..7a877373 100644
--- a/apps/explorer/src/app/console/(console)/page.tsx
+++ b/apps/explorer/src/app/console/(console)/page.tsx
@@ -14,11 +14,10 @@ type AccountSectionsProps = {
// so it doubles as the error flag.
error: Error | null;
isLoading: boolean;
- subscribed: boolean;
userAddress: string;
};
-const AccountSections = ({ account, error, isLoading, subscribed, userAddress }: AccountSectionsProps) => {
+const AccountSections = ({ account, error, isLoading, userAddress }: AccountSectionsProps) => {
if (isLoading) {
return ;
}
@@ -29,7 +28,7 @@ const AccountSections = ({ account, error, isLoading, subscribed, userAddress }:
return (
<>
-
+
@@ -59,7 +58,6 @@ const UserConsole = () => {
account={accountQuery.data}
error={accountQuery.error}
isLoading={accountQuery.isLoading}
- subscribed={isSubscribed}
userAddress={address}
/>
) : null}
diff --git a/apps/explorer/src/components/UserConsole/AlertsStatus.tsx b/apps/explorer/src/components/UserConsole/AlertsStatus.tsx
deleted file mode 100644
index 00c4f9bb..00000000
--- a/apps/explorer/src/components/UserConsole/AlertsStatus.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-"use client";
-import { Bell, BellOff, Settings } from "lucide-react";
-import Link from "next/link";
-
-interface AlertsStatusProps {
- subscribed: boolean;
-}
-
-export const AlertsStatus = ({ subscribed }: AlertsStatusProps) => {
- if (subscribed) {
- return (
-
-
- Alerts on
-
-
- );
- }
-
- return (
-
-
- Alerts off
-
- );
-};
diff --git a/apps/explorer/src/components/UserConsole/ConsoleHeader.tsx b/apps/explorer/src/components/UserConsole/ConsoleHeader.tsx
index 85eb12a6..6bae7013 100644
--- a/apps/explorer/src/components/UserConsole/ConsoleHeader.tsx
+++ b/apps/explorer/src/components/UserConsole/ConsoleHeader.tsx
@@ -10,18 +10,25 @@ type ConsoleHeaderProps = {
* ungated pages (e.g. the email verification landing page) can reuse it.
*/
walletControls?: ReactNode;
+ /**
+ * Trigger for the mobile navigation drawer. Ungated pages that reuse this
+ * header (e.g. the email verification landing page) pass none.
+ */
+ navTrigger?: ReactNode;
};
-export const ConsoleHeader = ({ walletControls }: ConsoleHeaderProps) => (
+export const ConsoleHeader = ({ walletControls, navTrigger }: ConsoleHeaderProps) => (
-
-
+
+
+ {navTrigger ?
{navTrigger}
: null}
+
{walletControls ? (
-
+
{walletControls}
) : null}
diff --git a/apps/explorer/src/components/UserConsole/ConsoleNavDrawer.tsx b/apps/explorer/src/components/UserConsole/ConsoleNavDrawer.tsx
new file mode 100644
index 00000000..b823556d
--- /dev/null
+++ b/apps/explorer/src/components/UserConsole/ConsoleNavDrawer.tsx
@@ -0,0 +1,44 @@
+"use client";
+import { Button } from "@filecoin-pay/ui/components/button";
+import { Sheet, SheetContent, SheetTitle, SheetTrigger } from "@filecoin-pay/ui/components/sheet";
+import { Menu } from "lucide-react";
+import { usePathname } from "next/navigation";
+import { useCallback, useEffect, useRef, useState } from "react";
+import { ConsoleSidebar } from "@/components/UserConsole/ConsoleSidebar";
+
+export const ConsoleNavDrawer = () => {
+ const [isOpen, setIsOpen] = useState(false);
+ const pathname = usePathname();
+ const previousPathnameRef = useRef(pathname);
+
+ const closeDrawer = useCallback(() => {
+ setIsOpen(false);
+ }, []);
+
+ // Back/forward (including the mobile edge-swipe gesture) changes the route
+ // without any click of ours to react to, and the console layout persists
+ // across those navigations, so the drawer would stay open over the new page.
+ useEffect(() => {
+ if (previousPathnameRef.current === pathname) {
+ return;
+ }
+
+ previousPathnameRef.current = pathname;
+ setIsOpen(false);
+ }, [pathname]);
+
+ return (
+
+
+
+
+
+
+ Console navigation
+
+
+
+ );
+};
diff --git a/apps/explorer/src/components/UserConsole/ConsoleSidebar.tsx b/apps/explorer/src/components/UserConsole/ConsoleSidebar.tsx
new file mode 100644
index 00000000..f0482efa
--- /dev/null
+++ b/apps/explorer/src/components/UserConsole/ConsoleSidebar.tsx
@@ -0,0 +1,101 @@
+"use client";
+import { cn } from "@filecoin-pay/ui/lib/utils";
+import { Bell, BellOff, Compass, LayoutDashboard } from "lucide-react";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import type { ReactNode } from "react";
+import { useConnection } from "wagmi";
+import { useNotificationStatus } from "@/hooks/useNotificationStatus";
+import { getNetworkFromChainId, isNotificationsEligibleNetwork } from "@/utils/network";
+
+type ConsoleSidebarProps = {
+ /**
+ * Called when a nav item is activated. The mobile drawer passes a closer here:
+ * Radix does not know about client-side navigation, so without it the sheet
+ * stays open on top of the page the user just navigated to.
+ */
+ onNavigate?: () => void;
+};
+
+type SidebarLinkProps = {
+ href: string;
+ isActive: boolean;
+ onNavigate?: () => void;
+ children: ReactNode;
+};
+
+/**
+ * `undefined` means the status is not known yet — still loading, the request
+ * failed, or the notifications API is unconfigured. Those render a neutral bell
+ * with no ON/OFF label rather than claiming alerts are off.
+ */
+const AlertsIcon = ({ isSubscribed }: { isSubscribed: boolean | undefined }) => {
+ if (isSubscribed === undefined) {
+ return
;
+ }
+
+ return isSubscribed ?
:
;
+};
+
+const SidebarLink = ({ href, isActive, onNavigate, children }: SidebarLinkProps) => (
+
+ {children}
+
+);
+
+export const ConsoleSidebar = ({ onNavigate }: ConsoleSidebarProps) => {
+ const pathname = usePathname();
+ const { address, chainId } = useConnection();
+
+ const walletNetwork = getNetworkFromChainId(chainId);
+ const isNotificationsEligible = isNotificationsEligibleNetwork(walletNetwork);
+
+ // Also read by the console page; React Query dedupes the two subscriptions.
+ const { data: notificationStatus } = useNotificationStatus(address);
+ const isSubscribed = notificationStatus?.subscribed;
+
+ const isAlertsActive = pathname.startsWith("/console/notifications");
+ const isDashboardActive = pathname === "/console";
+
+ // Chrome (border, responsive visibility) belongs to the caller: this renders
+ // both as the desktop column and inside the mobile drawer.
+ return (
+
+ );
+};
diff --git a/apps/explorer/src/components/UserConsole/FundsSection/index.tsx b/apps/explorer/src/components/UserConsole/FundsSection/index.tsx
index e5ebb051..97909f59 100644
--- a/apps/explorer/src/components/UserConsole/FundsSection/index.tsx
+++ b/apps/explorer/src/components/UserConsole/FundsSection/index.tsx
@@ -2,20 +2,18 @@ import type { Account, UserToken } from "@filecoin-pay/types";
import { Plus } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useConnection } from "wagmi";
-import { AlertsStatus } from "@/components/UserConsole/AlertsStatus";
import { DepositDialog } from "@/components/UserConsole/DepositDialog";
import { WithdrawDialog } from "@/components/UserConsole/WithdrawDialog";
import { useAccountTokens } from "@/hooks/useAccountDetails";
import { EPOCH_DURATION } from "@/utils/constants";
-import { getNetworkFromChainId, isNotificationsEligibleNetwork } from "@/utils/network";
+import { getNetworkFromChainId } from "@/utils/network";
import { FundsEmptyState, FundsErrorState, FundsLoadingState, FundsTable } from "./components";
interface FundsSectionProps {
account: Account;
- subscribed: boolean;
}
-export const FundsSection: React.FC
= ({ account, subscribed }) => {
+export const FundsSection: React.FC = ({ account }) => {
const [depositDialogOpen, setDepositDialogOpen] = useState(false);
const [withdrawDialogOpen, setWithdrawDialogOpen] = useState(false);
const [selectedToken, setSelectedToken] = useState(null);
@@ -23,7 +21,6 @@ export const FundsSection: React.FC = ({ account, subscribed
const { chainId } = useConnection();
const walletNetwork = getNetworkFromChainId(chainId);
- const isNotificationsEligible = isNotificationsEligibleNetwork(walletNetwork);
useEffect(() => {
const intervalId = window.setInterval(() => {
@@ -77,10 +74,7 @@ export const FundsSection: React.FC = ({ account, subscribed
return (
<>
-
-
Funds
- {isNotificationsEligible &&
}
-
+
Funds
diff --git a/apps/explorer/src/components/UserConsole/index.ts b/apps/explorer/src/components/UserConsole/index.ts
index 5ff522d3..c211175f 100644
--- a/apps/explorer/src/components/UserConsole/index.ts
+++ b/apps/explorer/src/components/UserConsole/index.ts
@@ -1,6 +1,5 @@
export { AccountInfo, AccountInfoSkeleton } from "./AccountInfo";
export { AlertsBanner } from "./AlertsBanner";
-export { AlertsStatus } from "./AlertsStatus";
export { ApproveOperatorDialog } from "./ApproveOperatorDialog";
export { BetaWarning } from "./BetaWarning";
export { DepositDialog } from "./DepositDialog";
diff --git a/apps/explorer/src/components/shared/Balance.tsx b/apps/explorer/src/components/shared/Balance.tsx
index 4a1896c4..d1836c7f 100644
--- a/apps/explorer/src/components/shared/Balance.tsx
+++ b/apps/explorer/src/components/shared/Balance.tsx
@@ -67,7 +67,7 @@ const Balance = () => {
return (
-