diff --git a/src/app/transactions/layout.tsx b/src/app/(dashboard)/layout.tsx similarity index 76% rename from src/app/transactions/layout.tsx rename to src/app/(dashboard)/layout.tsx index bbeb139..74e0aaa 100644 --- a/src/app/transactions/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -7,5 +7,5 @@ type Props = { } export default function Layout({ children }: Props) { - return {children} + return {children} } diff --git a/src/app/(dashboard)/page.tsx b/src/app/(dashboard)/page.tsx new file mode 100644 index 0000000..0862046 --- /dev/null +++ b/src/app/(dashboard)/page.tsx @@ -0,0 +1,46 @@ +'use client' +import { lazy } from 'react' +import { ChartSkeleton, VisibilityChart } from 'src/components/skeletons' +import { LatestTransactions } from 'src/components/transactions/latest-transactions' +import CustomWidgets from 'src/components/widgets/custom-widgets' +import { DashboardContent } from 'src/layouts/dashboard' + +// Lazy load chart components for better performance +const InflowOutflowCharts = lazy(() => import('src/components/chart/inflow-outflow-charts')) +const TokenVolumePieChart = lazy(() => import('src/components/chart/pie-charts')) +const GasUsageChart = lazy(() => import('src/components/chart/gas-usage-chart')) +import BridgePerformanceChart from 'src/components/chart/bridge-performance-chart' +import CumulativeNetInflow from 'src/components/chart/cumulative-net-inflow' +import TopTokens from 'src/components/widgets/top-tokens' + +// ---------------------------------------------------------------------- + +export default function Page() { + return ( + + + + + + + + {/* Latest Transactions - also use visibility system */} + }> + + + + {/* Bottom charts - render only when visible */} + }> + + + + }> + + + + {/* }> */} + + {/* */} + + ) +} diff --git a/src/app/profile/page.tsx b/src/app/(dashboard)/profile/page.tsx similarity index 98% rename from src/app/profile/page.tsx rename to src/app/(dashboard)/profile/page.tsx index ec614eb..25d4bb1 100644 --- a/src/app/profile/page.tsx +++ b/src/app/(dashboard)/profile/page.tsx @@ -1,4 +1,6 @@ 'use client' + +import { Suspense } from 'react' import { Box, Button, @@ -28,6 +30,7 @@ import { import '@mysten/dapp-kit/dist/index.css' import '@rainbow-me/rainbowkit/styles.css' import { useState as useReactState } from 'react' +import { SplashScreen } from 'src/components/loading-screen' type WalletActionButtonProps = { label: string @@ -93,7 +96,7 @@ function WalletActionButton({ const SUI_LOGO_PATH = '/assets/icons/brands/sui.svg' const ETH_LOGO_PATH = '/assets/icons/brands/eth.svg' -export default function Page() { +function ProfileContent() { const searchParams = useSearchParams() const [suiAddress, setSuiAddress] = useState(searchParams?.get('suiAddress') || '') @@ -335,3 +338,11 @@ export default function Page() { ) } + +export default function Page() { + return ( + }> + + + ) +} diff --git a/src/app/transactions/[tx]/page.tsx b/src/app/(dashboard)/transactions/[tx]/page.tsx similarity index 100% rename from src/app/transactions/[tx]/page.tsx rename to src/app/(dashboard)/transactions/[tx]/page.tsx diff --git a/src/app/transactions/page.tsx b/src/app/(dashboard)/transactions/page.tsx similarity index 100% rename from src/app/transactions/page.tsx rename to src/app/(dashboard)/transactions/page.tsx diff --git a/src/app/loading.tsx b/src/app/loading.tsx deleted file mode 100644 index eb8c308..0000000 --- a/src/app/loading.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { LoadingScreen } from 'src/components/loading-screen' - -// ---------------------------------------------------------------------- - -export default function Loading() { - return -} diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index 39ee758..0000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client' -import { Suspense, lazy } from 'react' -import { ChartSkeleton, VisibilityChart } from 'src/components/skeletons' -import { LatestTransactions } from 'src/components/transactions/latest-transactions' -import CustomWidgets from 'src/components/widgets/custom-widgets' -import { DashboardContent, DashboardLayout } from 'src/layouts/dashboard' - -// Lazy load chart components for better performance -const InflowOutflowCharts = lazy(() => import('src/components/chart/inflow-outflow-charts')) -const TokenVolumePieChart = lazy(() => import('src/components/chart/pie-charts')) -const GasUsageChart = lazy(() => import('src/components/chart/gas-usage-chart')) -import BridgePerformanceChart from 'src/components/chart/bridge-performance-chart' -import CumulativeNetInflow from 'src/components/chart/cumulative-net-inflow' -import TopTokens from 'src/components/widgets/top-tokens' - -// ---------------------------------------------------------------------- - -export default function Page() { - return ( - - - - - - - - - {/* Latest Transactions - also use visibility system */} - }> - - - - {/* Bottom charts - render only when visible */} - }> - - - - }> - - - - {/* }> */} - - {/* */} - - - ) -} diff --git a/src/app/profile/layout.tsx b/src/app/profile/layout.tsx deleted file mode 100644 index bbeb139..0000000 --- a/src/app/profile/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { DashboardLayout } from 'src/layouts/dashboard' - -// ---------------------------------------------------------------------- - -type Props = { - children: React.ReactNode -} - -export default function Layout({ children }: Props) { - return {children} -} diff --git a/src/app/transactions/loading.tsx b/src/app/transactions/loading.tsx deleted file mode 100644 index eb8c308..0000000 --- a/src/app/transactions/loading.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { LoadingScreen } from 'src/components/loading-screen' - -// ---------------------------------------------------------------------- - -export default function Loading() { - return -} diff --git a/src/auth/guard/auth-guard.tsx b/src/auth/guard/auth-guard.tsx index 6b420ae..5268e3f 100644 --- a/src/auth/guard/auth-guard.tsx +++ b/src/auth/guard/auth-guard.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect, useCallback } from 'react' +import { useState, useEffect, useCallback, Suspense } from 'react' import { paths } from 'src/routes/paths' import { useRouter, usePathname, useSearchParams } from 'src/routes/hooks' @@ -17,7 +17,7 @@ type Props = { children: React.ReactNode } -export function AuthGuard({ children }: Props) { +function AuthGuardContent({ children }: Props) { const router = useRouter() const pathname = usePathname() @@ -74,3 +74,11 @@ export function AuthGuard({ children }: Props) { return <>{children} } + +export function AuthGuard({ children }: Props) { + return ( + }> + {children} + + ) +} diff --git a/src/auth/guard/guest-guard.tsx b/src/auth/guard/guest-guard.tsx index 7eeab05..ace2bef 100644 --- a/src/auth/guard/guest-guard.tsx +++ b/src/auth/guard/guest-guard.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState, useEffect, Suspense } from 'react' import { useRouter, useSearchParams } from 'src/routes/hooks' @@ -16,7 +16,7 @@ type Props = { children: React.ReactNode } -export function GuestGuard({ children }: Props) { +function GuestGuardContent({ children }: Props) { const router = useRouter() const searchParams = useSearchParams() @@ -51,3 +51,11 @@ export function GuestGuard({ children }: Props) { return <>{children} } + +export function GuestGuard({ children }: Props) { + return ( + }> + {children} + + ) +} diff --git a/src/components/loading-screen/index.ts b/src/components/loading-screen/index.ts index eacb730..b5a7db6 100644 --- a/src/components/loading-screen/index.ts +++ b/src/components/loading-screen/index.ts @@ -1,3 +1 @@ export * from './splash-screen' - -export * from './loading-screen' diff --git a/src/components/loading-screen/loading-screen.tsx b/src/components/loading-screen/loading-screen.tsx deleted file mode 100644 index 767fdc8..0000000 --- a/src/components/loading-screen/loading-screen.tsx +++ /dev/null @@ -1,39 +0,0 @@ -'use client' - -import type { BoxProps } from '@mui/material/Box' - -import Box from '@mui/material/Box' -import Portal from '@mui/material/Portal' -import LinearProgress from '@mui/material/LinearProgress' - -// ---------------------------------------------------------------------- - -type Props = BoxProps & { - portal?: boolean -} - -export function LoadingScreen({ portal, sx, ...other }: Props) { - const content = ( - - - - ) - - if (portal) { - return {content} - } - - return content -} diff --git a/src/components/logo/logo.tsx b/src/components/logo/logo.tsx index 3411695..a6e84eb 100644 --- a/src/components/logo/logo.tsx +++ b/src/components/logo/logo.tsx @@ -5,7 +5,6 @@ import type { BoxProps } from '@mui/material/Box' import { forwardRef } from 'react' import Box from '@mui/material/Box' -import { useTheme } from '@mui/material/styles' import { RouterLink } from 'src/routes/components' @@ -16,68 +15,41 @@ import { CONFIG } from 'src/config-global' export type LogoProps = BoxProps & { href?: string - isSingle?: boolean disableLink?: boolean - isLarge?: boolean + single?: boolean // Use small/single logo } export const Logo = forwardRef( - ( - { - isLarge, - width, - href = '/', - height, - isSingle = true, - disableLink = false, - className, - sx, - ...other - }, - ref, - ) => { - const theme = useTheme() - - const fullLogo = ( - - ) - - const singleLogo = ( - Single logo - ) + ({ href = '/', disableLink = false, single = false, className, sx, ...other }, ref) => { + const logoSrc = single + ? '/assets/icons/brands/single-logo.svg' + : `${CONFIG.assetsDir}/sui.svg` return ( - {isLarge ? fullLogo : singleLogo} + ) }, diff --git a/src/components/skeletons/index.ts b/src/components/skeletons/index.ts index 7e9eb49..f0a36dc 100644 --- a/src/components/skeletons/index.ts +++ b/src/components/skeletons/index.ts @@ -1,3 +1,4 @@ export { default as ChartSkeleton } from './chart-skeleton' export { default as VisibilityChart } from './visibility-chart' export { BridgePerformanceChartLoading } from './bridge-performance-chart-loading' +export { TransactionDetailSkeleton } from './transaction-detail-skeleton' diff --git a/src/components/skeletons/transaction-detail-skeleton.tsx b/src/components/skeletons/transaction-detail-skeleton.tsx new file mode 100644 index 0000000..54db950 --- /dev/null +++ b/src/components/skeletons/transaction-detail-skeleton.tsx @@ -0,0 +1,139 @@ +import { Box, Card, CardHeader, Divider, Skeleton, Stack } from '@mui/material' + +export function TransactionDetailSkeleton() { + return ( + + + {/* Transaction Summary Column Skeleton */} + + + + + + + + + + + + {[...Array(6)].map((_, i) => ( + + + + + ))} + + + + + + {/* Vertical divider on medium+ screens */} + + + {/* Timeline Column Skeleton */} + + + + {[...Array(3)].map((_, index) => ( + + {/* Timeline dot and connector */} + + + {index < 2 && ( + + )} + + {/* Timeline content */} + + + + + {[...Array(4)].map((_, i) => ( + + ))} + + + {[...Array(4)].map((_, i) => ( + + ))} + + + + + ))} + + + + + ) +} diff --git a/src/components/transactions/transaction-view.tsx b/src/components/transactions/transaction-view.tsx index d438ae9..236e2f6 100644 --- a/src/components/transactions/transaction-view.tsx +++ b/src/components/transactions/transaction-view.tsx @@ -8,7 +8,7 @@ import { } from '@mui/lab' import { Box, Card, CardHeader, Divider, Grid, Link, Stack, Typography } from '@mui/material' import { formatDistanceToNow } from 'date-fns' -import Loading from 'src/app/loading' +import { TransactionDetailSkeleton } from 'src/components/skeletons' import { formatExplorerUrl, truncateAddress } from 'src/config/helper' import { getNetwork, NETWORK } from 'src/hooks/get-network-storage' import { useRouter } from 'src/routes/hooks' @@ -30,7 +30,7 @@ export function TransactionView({ tx }: { tx: string }) { }>(`${endpoints.transaction}/${tx}?network=${network}`, fetcher) if (isLoading) { - return + return } if (!isLoading && !data) { diff --git a/src/layouts/components/filter-popover.tsx b/src/layouts/components/filter-popover.tsx new file mode 100644 index 0000000..9317d72 --- /dev/null +++ b/src/layouts/components/filter-popover.tsx @@ -0,0 +1,154 @@ +'use client' + +import { useState } from 'react' +import { Box, IconButton, Popover, Typography, Badge, Divider, Stack } from '@mui/material' +import { useTheme, alpha } from '@mui/material/styles' +import { Iconify } from 'src/components/iconify' +import { ChartSelect } from 'src/components/chart' +import { MultiSelect } from 'src/components/selectors/multi-select' +import { getTokensList } from 'src/utils/types' +import { useGlobalContext } from 'src/provider/global-provider' +import { getNetwork } from 'src/hooks/get-network-storage' +import { TIME_PERIODS, TimePeriod } from 'src/config/helper' + +export function FilterPopover() { + const theme = useTheme() + const network = getNetwork() + const { timePeriod, setTimePeriod, selectedTokens, setSelectedTokens } = useGlobalContext() + const [anchorEl, setAnchorEl] = useState(null) + + const open = Boolean(anchorEl) + + const handleOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget) + } + + const handleClose = () => { + setAnchorEl(null) + } + + // Check if any non-default filters are applied (defaults: 'Last Month' and ['All']) + const hasActiveFilters = + timePeriod !== 'Last Month' || + (selectedTokens.length > 0 && !(selectedTokens.length === 1 && selectedTokens[0] === 'All')) + + return ( + <> + + + + + + + + + Filters + + + + {/* Time Period */} + + + Time Period + + setTimePeriod(newVal as TimePeriod)} + slotProps={{ + button: { width: '100%', justifyContent: 'space-between' }, + }} + /> + + + + + {/* Token Filter */} + + + Tokens + + ({ + name: i.ticker, + icon: i.icon, + })), + ]} + allOption="All" + value={selectedTokens} + onChange={setSelectedTokens} + slotProps={{ + button: { width: '100%', justifyContent: 'space-between', mr: 0 }, + }} + /> + + + + + ) +} diff --git a/src/layouts/components/nav-tabs.tsx b/src/layouts/components/nav-tabs.tsx new file mode 100644 index 0000000..f01eba0 --- /dev/null +++ b/src/layouts/components/nav-tabs.tsx @@ -0,0 +1,242 @@ +'use client' + +import { usePathname } from 'next/navigation' +import { + Tab, + Tabs, + Box, + Collapse, + List, + ListItemButton, + ListItemIcon, + ListItemText, + IconButton, +} from '@mui/material' +import { useTheme, alpha } from '@mui/material/styles' +import { RouterLink } from 'src/routes/components' +import { paths } from 'src/routes/paths' +import { SvgColor } from 'src/components/svg-color' +import { CONFIG } from 'src/config-global' +import { Iconify } from 'src/components/iconify' + +// Use the same icon helper as config-nav-dashboard +const icon = (name: string) => ( + +) + +const NAV_ITEMS = [ + { + label: 'Bridge Dashboard', + path: '/', + icon: icon('ic-analytics'), + }, + { + label: 'Bridge Transactions', + path: paths.transactions.root, + icon: icon('ic-parameter'), + }, + { + label: 'Profile', + path: paths.profile.root, + icon: icon('ic-course'), + }, +] + +// Desktop Navigation Tabs +export function NavTabs() { + const theme = useTheme() + const pathname = usePathname() + + // Determine active tab based on pathname + const activeTab = NAV_ITEMS.findIndex(item => { + if (item.path === '/') { + return pathname === '/' + } + return pathname?.startsWith(item.path) ?? false + }) + + return ( + + {NAV_ITEMS.map(item => ( + + + {item.icon} + + {item.label} + + } + component={RouterLink} + href={item.path} + /> + ))} + + ) +} + +// Mobile Navigation Toggle Button +interface MobileNavToggleProps { + open: boolean + onToggle: () => void +} + +export function MobileNavToggle({ open, onToggle }: MobileNavToggleProps) { + const theme = useTheme() + + return ( + + + + ) +} + +// Mobile Navigation Menu (expandable content) +interface MobileNavMenuProps { + open: boolean + onClose: () => void +} + +export function MobileNavMenu({ open, onClose }: MobileNavMenuProps) { + const theme = useTheme() + const pathname = usePathname() + + const isActive = (path: string) => { + if (path === '/') { + return pathname === '/' + } + return pathname?.startsWith(path) ?? false + } + + return ( + + + {NAV_ITEMS.map(item => { + const active = isActive(item.path) + return ( + + + + {item.icon} + + + + {active && ( + + )} + + ) + })} + + {/* Bottom divider */} + + + ) +} diff --git a/src/layouts/dashboard/layout.tsx b/src/layouts/dashboard/layout.tsx index 66b9dfb..495aaa5 100644 --- a/src/layouts/dashboard/layout.tsx +++ b/src/layouts/dashboard/layout.tsx @@ -1,246 +1,139 @@ 'use client' -import type { NavSectionProps } from 'src/components/nav-section' -import type { Theme, SxProps, Breakpoint } from '@mui/material/styles' +import { useState } from 'react' +import type { Theme, SxProps } from '@mui/material/styles' -import Alert from '@mui/material/Alert' import { useTheme } from '@mui/material/styles' -import { iconButtonClasses } from '@mui/material/IconButton' - -import { useBoolean } from 'src/hooks/use-boolean' - -import { _contacts, _notifications } from 'src/_mock' +import { Box, AppBar, Toolbar, Container, useMediaQuery } from '@mui/material' import { Logo } from 'src/components/logo' -import { useSettingsContext } from 'src/components/settings' - -import { Main } from './main' -import { NavMobile } from './nav-mobile' -import { layoutClasses } from '../classes' -import { NavVertical } from './nav-vertical' -import { NavHorizontal } from './nav-horizontal' -import { _account } from '../config-nav-account' -import { _workspaces } from '../config-nav-workspace' -import { MenuButton } from '../components/menu-button' -import { LayoutSection } from '../core/layout-section' -import { HeaderSection } from '../core/header-section' -import { StyledDivider, useNavColorVars } from './styles' -import { navData as dashboardNavData } from '../config-nav-dashboard' import { NetworkPopover } from '../components/network-popover' -import { Grid } from '@mui/material' -import { ChartSelect } from 'src/components/chart' -import { MultiSelect } from 'src/components/selectors/multi-select' -import { getTokensList } from 'src/utils/types' -import { useGlobalContext } from 'src/provider/global-provider' -import { getNetwork } from 'src/hooks/get-network-storage' -import { TIME_PERIODS, TimePeriod } from 'src/config/helper' +import { FilterPopover } from '../components/filter-popover' +import { NavTabs, MobileNavToggle, MobileNavMenu } from '../components/nav-tabs' +import { layoutClasses } from '../classes' +import { varAlpha } from 'src/theme/styles' + // ---------------------------------------------------------------------- export type DashboardLayoutProps = { sx?: SxProps children: React.ReactNode - header?: { - sx?: SxProps - } - data?: { - nav?: NavSectionProps['data'] - } disableTimelines?: boolean } -export function DashboardLayout({ - sx, - children, - header, - data, - disableTimelines, -}: DashboardLayoutProps) { - const network = getNetwork() - - const { timePeriod, setTimePeriod, selectedTokens, setSelectedTokens } = useGlobalContext() - +export function DashboardLayout({ sx, children, disableTimelines }: DashboardLayoutProps) { const theme = useTheme() - - const mobileNavOpen = useBoolean() - - const settings = useSettingsContext() - - const navColorVars = useNavColorVars(theme, settings) - - const layoutQuery: Breakpoint = 'lg' - - const navData = data?.nav ?? dashboardNavData - - const isNavMini = settings.navLayout === 'mini' - const isNavHorizontal = settings.navLayout === 'horizontal' - const isNavVertical = isNavMini || settings.navLayout === 'vertical' + const isMobile = useMediaQuery(theme.breakpoints.down('md')) + const [mobileMenuOpen, setMobileMenuOpen] = useState(false) return ( - - This is an info Alert. - - ), - bottomArea: isNavHorizontal ? ( - - ) : null, - leftArea: ( - <> - {/* -- Nav mobile -- */} - - - {/* -- Logo -- */} - {isNavHorizontal && ( - - )} - {/* -- Divider -- */} - {/* {isNavHorizontal && ( - - )} */} - - ), - rightArea: ( - <> - {!disableTimelines && ( - <> - setTimePeriod(newVal as TimePeriod)} - /> - ({ - name: i.ticker, - icon: i.icon, - })), - ]} - allOption="All" - value={selectedTokens} - onChange={setSelectedTokens} - /> - - )} - - - ), - }} - /> - } - /** ************************************** - * Sidebar - *************************************** */ - sidebarSection={ - isNavHorizontal ? null : ( - - settings.onUpdateField( - 'navLayout', - settings.navLayout === 'vertical' ? 'mini' : 'vertical', - ) - } - /> - ) - } - /** ************************************** - * Footer - *************************************** */ - footerSection={null} - /** ************************************** - * Style - *************************************** */ - cssVars={{ - ...navColorVars.layout, - '--layout-transition-easing': 'linear', - '--layout-transition-duration': '120ms', - '--layout-nav-mini-width': '88px', - '--layout-nav-vertical-width': '200px', - '--layout-nav-horizontal-height': '64px', - '--layout-dashboard-content-pt': theme.spacing(1), - '--layout-dashboard-content-pb': theme.spacing(8), - '--layout-dashboard-content-px': theme.spacing(5), - }} + -
{children}
-
+ {/* Header */} + + + {/* Main toolbar row */} + + {/* Left section - fixed width */} + + {isMobile && ( + setMobileMenuOpen(!mobileMenuOpen)} + /> + )} + + + + {/* Center section - Navigation Tabs (desktop only) */} + {!isMobile && ( + + + + )} + + {/* Spacer for mobile */} + {isMobile && } + + {/* Right section - fixed width */} + + {!disableTimelines && } + + + + + + {/* Mobile Navigation Menu (expandable) */} + {isMobile && ( + setMobileMenuOpen(false)} /> + )} + + + {/* Main Content */} + + + {children} + + +
) } diff --git a/src/layouts/dashboard/main.tsx b/src/layouts/dashboard/main.tsx index 3afd6ce..edaf4d5 100644 --- a/src/layouts/dashboard/main.tsx +++ b/src/layouts/dashboard/main.tsx @@ -1,46 +1,14 @@ 'use client' -import type { BoxProps } from '@mui/material/Box' -import type { Breakpoint } from '@mui/material/styles' import type { ContainerProps } from '@mui/material/Container' -import Box from '@mui/material/Box' -import { useTheme } from '@mui/material/styles' import Container from '@mui/material/Container' import { layoutClasses } from 'src/layouts/classes' - import { useSettingsContext } from 'src/components/settings' // ---------------------------------------------------------------------- -type MainProps = BoxProps & { - isNavHorizontal: boolean -} - -export function Main({ children, isNavHorizontal, sx, ...other }: MainProps) { - return ( - - {children} - - ) -} - -// ---------------------------------------------------------------------- - type DashboardContentProps = ContainerProps & { disablePadding?: boolean } @@ -49,15 +17,11 @@ export function DashboardContent({ sx, children, disablePadding, - maxWidth = 'lg', + maxWidth = 'xl', ...other }: DashboardContentProps) { - const theme = useTheme() - const settings = useSettingsContext() - const layoutQuery: Breakpoint = 'lg' - return ( {slots?.topArea ?? ( - + )} diff --git a/src/layouts/dashboard/nav-vertical.tsx b/src/layouts/dashboard/nav-vertical.tsx index 3ba7c77..9f759af 100644 --- a/src/layouts/dashboard/nav-vertical.tsx +++ b/src/layouts/dashboard/nav-vertical.tsx @@ -39,7 +39,7 @@ export function NavVertical({ <> {slots?.topArea ?? ( - + )}