diff --git a/.changeset/brown-pianos-sit.md b/.changeset/brown-pianos-sit.md new file mode 100644 index 000000000..09df3b5c0 --- /dev/null +++ b/.changeset/brown-pianos-sit.md @@ -0,0 +1,5 @@ +--- +"fuels-wallet": patch +--- + +- New "Review Errors" option in Hamburger Menu when new errors are detected. diff --git a/.changeset/dull-mugs-hunt.md b/.changeset/dull-mugs-hunt.md new file mode 100644 index 000000000..0b43e61aa --- /dev/null +++ b/.changeset/dull-mugs-hunt.md @@ -0,0 +1,5 @@ +--- +"fuels-wallet": patch +--- + +- Removed Error Floating Button diff --git a/packages/app/playwright/e2e/ReportError.test.ts b/packages/app/playwright/e2e/ReportError.test.ts index abd87f8f0..1a912524e 100644 --- a/packages/app/playwright/e2e/ReportError.test.ts +++ b/packages/app/playwright/e2e/ReportError.test.ts @@ -1,13 +1,7 @@ import type { Browser, BrowserContext, Page } from '@playwright/test'; import test, { chromium, expect } from '@playwright/test'; -import { - getButtonByText, - getByAriaLabel, - hasText, - reload, - visit, -} from '../commons'; +import { getByAriaLabel, hasText, reload, visit } from '../commons'; import { mockData } from '../mocks'; test.describe('ReportError', () => { @@ -51,7 +45,7 @@ test.describe('ReportError', () => { expect(errorsAfterReporting.length).toBe(0); }); - test('should show floating error button when there is a error in the database', async () => { + test('should show Review Error in menu when there is a error in the database', async () => { await visit(page, '/'); await page.evaluate(async () => { await window.fuelDB.errors.clear(); @@ -73,10 +67,8 @@ test.describe('ReportError', () => { }); await reload(page); - const floatingButton = await page.waitForSelector( - '[data-testid="ErrorFloatingButton"]', - { state: 'visible' } - ); + await getByAriaLabel(page, 'Menu').click(); + const floatingButton = page.locator(`[data-key="hasErrors"]`); expect(floatingButton.isVisible).toBeTruthy(); }); @@ -101,11 +93,8 @@ test.describe('ReportError', () => { }); }); await reload(page); - ( - await page.waitForSelector('[data-testid="ErrorFloatingButton"]', { - state: 'visible', - }) - ).click(); + await getByAriaLabel(page, 'Menu').click(); + page.locator(`[data-key="hasErrors"]`).click(); await hasText(page, /Unexpected error/i); // report error @@ -136,11 +125,8 @@ test.describe('ReportError', () => { }); }); await reload(page); - ( - await page.waitForSelector('[data-testid="ErrorFloatingButton"]', { - state: 'visible', - }) - ).click(); + await getByAriaLabel(page, 'Menu').click(); + page.locator(`[data-key="hasErrors"]`).click(); await hasText(page, /Unexpected error/i); // report error @@ -174,11 +160,8 @@ test.describe('ReportError', () => { }); }); await reload(page); - ( - await page.waitForSelector('[data-testid="ErrorFloatingButton"]', { - state: 'visible', - }) - ).click(); + await getByAriaLabel(page, 'Menu').click(); + page.locator(`[data-key="hasErrors"]`).click(); await hasText(page, /Unexpected error/i); // report error @@ -195,11 +178,8 @@ test.describe('ReportError', () => { console.error(new Error('Test Error')); }); await reload(page); - ( - await page.waitForSelector('[data-testid="ErrorFloatingButton"]', { - state: 'visible', - }) - ).click(); + await getByAriaLabel(page, 'Menu').click(); + page.locator(`[data-key="hasErrors"]`).click(); await hasText(page, /Unexpected error/i); const errorsAfterReporting = await getPageErrors(page); @@ -242,11 +222,8 @@ test.describe('ReportError', () => { }); }); await reload(page); - ( - await page.waitForSelector('[data-testid="ErrorFloatingButton"]', { - state: 'visible', - }) - ).click(); + await getByAriaLabel(page, 'Menu').click(); + page.locator(`[data-key="hasErrors"]`).click(); await hasText(page, /Unexpected error/i); await reload(page); const errorsAfterReporting = await getPageErrors(page); diff --git a/packages/app/src/systems/Core/components/Layout/TopBar.tsx b/packages/app/src/systems/Core/components/Layout/TopBar.tsx index fb2fc6689..523e950f6 100644 --- a/packages/app/src/systems/Core/components/Layout/TopBar.tsx +++ b/packages/app/src/systems/Core/components/Layout/TopBar.tsx @@ -10,6 +10,7 @@ import { NetworkDropdown } from '~/systems/Network/components'; import { useNetworks } from '~/systems/Network/hooks'; import { useOverlay } from '~/systems/Overlay'; +import { useReportError } from '~/systems/Error'; import { useLayoutContext } from './Layout'; export enum TopBarType { @@ -32,6 +33,7 @@ function InternalTopBar({ onBack }: TopBarProps) { const overlay = useOverlay(); const { isLoading, title, isHome } = useLayoutContext(); const { selectedNetwork, handlers } = useNetworks(); + const { hasErrorsToReport } = useReportError(); return ( @@ -61,7 +63,8 @@ function InternalTopBar({ onBack }: TopBarProps) { )} - + + {hasErrorsToReport && } } @@ -127,6 +130,17 @@ const styles = { minHeight: '50px', transition: 'none', }), + menuContainer: cssObj({ + position: 'relative', + }), + badge: cssObj({ + position: 'absolute', + top: -6, + right: -2, + fontSize: 8, + color: '$intentsError10 !important', + transform: 'translate(25%, -25%)', + }), topbarIcon: cssObj({ px: '$0 !important', color: '$intentsBase8 !important', diff --git a/packages/app/src/systems/Error/components/ErrorFloatingButton/ErrorFloatingButton.tsx b/packages/app/src/systems/Error/components/ErrorFloatingButton/ErrorFloatingButton.tsx deleted file mode 100644 index 1e54d3275..000000000 --- a/packages/app/src/systems/Error/components/ErrorFloatingButton/ErrorFloatingButton.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { cssObj } from '@fuel-ui/css'; -import { Box, Icon, IconButton } from '@fuel-ui/react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { Pages } from '~/systems/Core'; -import { useReportError } from '~/systems/Error'; - -export function ErrorFloatingButton() { - const { errors, hasErrorsToReport } = useReportError(); - const navigate = useNavigate(); - const location = useLocation(); - const hidden = - !hasErrorsToReport || - !errors.length || - location.pathname === Pages.errors(); - - return ( - - navigate(Pages.errors())} - aria-label="Click to visualize unreviewed errrors" - iconSize={20} - size="sm" - disabled={hidden} - data-testid="ErrorFloatingButton" - icon={Icon.is('AlertTriangle')} - /> - - ); -} - -const styles = { - alertContainer: cssObj({ - padding: '$2', - position: 'fixed', - bottom: 0, - right: 25, - zIndex: '$10', - borderRadius: '$full', - transform: 'translateY(50px)', - transition: 'transform 0.3s ease-in-out', - '&.show': { - transform: 'translateY(-20px)', - }, - }), - button: cssObj({ - borderRadius: '$full', - borderWidth: 0, - }), -}; diff --git a/packages/app/src/systems/Error/components/ErrorFloatingButton/index.ts b/packages/app/src/systems/Error/components/ErrorFloatingButton/index.ts deleted file mode 100644 index 753133da4..000000000 --- a/packages/app/src/systems/Error/components/ErrorFloatingButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ErrorFloatingButton'; diff --git a/packages/app/src/systems/Error/components/index.tsx b/packages/app/src/systems/Error/components/index.tsx index 9ddc76ac6..3d37579d3 100644 --- a/packages/app/src/systems/Error/components/index.tsx +++ b/packages/app/src/systems/Error/components/index.tsx @@ -1,3 +1,2 @@ export * from './Providers'; export * from './ThrowError'; -export * from './ErrorFloatingButton'; diff --git a/packages/app/src/systems/Error/hooks/useReportError.tsx b/packages/app/src/systems/Error/hooks/useReportError.tsx index bdbaeba31..b1ad8835b 100644 --- a/packages/app/src/systems/Error/hooks/useReportError.tsx +++ b/packages/app/src/systems/Error/hooks/useReportError.tsx @@ -41,7 +41,7 @@ export function useReportError() { }; return { - hasErrorsToReport, + hasErrorsToReport: !!hasErrorsToReport || !!errors.length, isLoadingSendOnce, errors, handlers: { diff --git a/packages/app/src/systems/Sidebar/components/Menu/Menu.stories.tsx b/packages/app/src/systems/Sidebar/components/Menu/Menu.stories.tsx index 5fd74f639..5fcb7d1c8 100644 --- a/packages/app/src/systems/Sidebar/components/Menu/Menu.stories.tsx +++ b/packages/app/src/systems/Sidebar/components/Menu/Menu.stories.tsx @@ -1,6 +1,6 @@ import { Box } from '@fuel-ui/react'; -import type { MenuItemObj, MenuProps } from './Menu'; +import type { MenuItemObj, MenuProps } from './types'; import { Menu } from './Menu'; export default { diff --git a/packages/app/src/systems/Sidebar/components/Menu/Menu.tsx b/packages/app/src/systems/Sidebar/components/Menu/Menu.tsx index cb976c8a4..4dfb75983 100644 --- a/packages/app/src/systems/Sidebar/components/Menu/Menu.tsx +++ b/packages/app/src/systems/Sidebar/components/Menu/Menu.tsx @@ -1,27 +1,12 @@ import { cssObj } from '@fuel-ui/css'; -import type { Icons } from '@fuel-ui/react'; -import { Box, Icon, Menu as RootMenu } from '@fuel-ui/react'; +import { Badge, Box, Icon, Menu as RootMenu, Text } from '@fuel-ui/react'; import { motion } from 'framer-motion'; import type { Key } from 'react'; -import { useState } from 'react'; +import { memo, useState } from 'react'; import { useMatch, useNavigate, useResolvedPath } from 'react-router-dom'; import { store } from '~/store'; import { coreStyles } from '~/systems/Core/styles'; - -export type MenuItemObj = { - key: string; - icon: Icons; - label: string; - path?: string; - ahref?: string; - submenu?: MenuItemObj[]; - onPress?: () => void; -}; - -type MenuItemContentProps = { - item: MenuItemObj; - isOpened?: boolean; -}; +import type { MenuItemContentProps, MenuItemObj, MenuProps } from './types'; function commonActions( item: MenuItemObj, @@ -63,6 +48,7 @@ function MenuItemContent({ item, isOpened }: MenuItemContentProps) { return ( + {item.badge && } (null); const navigate = useNavigate(); @@ -134,6 +116,8 @@ export function Menu({ items }: MenuProps) { ); } +export const Menu = memo(_Menu); + const styles = { root: cssObj({ ...coreStyles.scrollable('$intentsBase2'), @@ -186,6 +170,7 @@ const styles = { }), routeContent: cssObj({ flex: 1, + position: 'relative', }), submenu: cssObj({ position: 'relative', @@ -229,4 +214,12 @@ const styles = { transform: 'translateY(-50%)', }, }), + badge: cssObj({ + position: 'absolute', + top: 2, + left: 2, + fontSize: 8, + color: '$intentsError10 !important', + transform: 'translate(25%, -25%)', + }), }; diff --git a/packages/app/src/systems/Sidebar/components/Menu/types.ts b/packages/app/src/systems/Sidebar/components/Menu/types.ts new file mode 100644 index 000000000..4c5dc386e --- /dev/null +++ b/packages/app/src/systems/Sidebar/components/Menu/types.ts @@ -0,0 +1,21 @@ +import type { Icons } from '@fuel-ui/react'; + +export type MenuItemObj = { + key: string; + icon: Icons; + label: string; + path?: string; + ahref?: string; + submenu?: MenuItemObj[]; + onPress?: () => void; + badge?: boolean; +}; + +export type MenuItemContentProps = { + item: MenuItemObj; + isOpened?: boolean; +}; + +export type MenuProps = { + items: MenuItemObj[]; +}; diff --git a/packages/app/src/systems/Sidebar/components/Sidebar/Sidebar.tsx b/packages/app/src/systems/Sidebar/components/Sidebar/Sidebar.tsx index 894b4dd30..e5c7449e0 100644 --- a/packages/app/src/systems/Sidebar/components/Sidebar/Sidebar.tsx +++ b/packages/app/src/systems/Sidebar/components/Sidebar/Sidebar.tsx @@ -1,15 +1,22 @@ import { cssObj } from '@fuel-ui/css'; import { Box, Drawer, Icon, IconButton, Text } from '@fuel-ui/react'; -import { forwardRef } from 'react'; +import { forwardRef, useMemo } from 'react'; import { APP_VERSION } from '~/config'; import { useOverlay } from '~/systems/Overlay'; +import { useReportError } from '~/systems/Error'; import { Menu } from '..'; import { sidebarItems } from '../../constants'; import { ThemeToggler } from '../ThemeToggler'; function SidebarContent() { const overlay = useOverlay(); + const { hasErrorsToReport } = useReportError(); + + const menuItems = useMemo( + () => sidebarItems(hasErrorsToReport), + [hasErrorsToReport] + ); return ( <> @@ -25,7 +32,7 @@ function SidebarContent() { onPress={overlay.close} /> - + Version: {APP_VERSION}{' '} diff --git a/packages/app/src/systems/Sidebar/constants/sidebarItems.ts b/packages/app/src/systems/Sidebar/constants/sidebarItems.ts index ef181a86f..2c4b1d392 100644 --- a/packages/app/src/systems/Sidebar/constants/sidebarItems.ts +++ b/packages/app/src/systems/Sidebar/constants/sidebarItems.ts @@ -1,116 +1,128 @@ import { store } from '~/store'; import { Pages } from '~/systems/Core'; -import type { MenuItemObj } from '../components'; +import type { MenuItemObj } from '../components/Menu/types'; -export const sidebarItems = (): Array => [ - { - key: 'wallet', - icon: 'Wallet', - label: 'Wallet', - path: Pages.wallet(), - }, - { - key: 'history', - icon: 'History', - label: 'Transaction History', - path: Pages.txs(), - }, - { - key: 'networks', - icon: 'BrandStackshare', - label: 'Networks Management', - onPress() { - store.openNetworksList(); - }, - }, - { - key: 'accounts', - icon: 'Users', - label: 'Account Management', - onPress() { - store.openAccountList(); - }, - }, - { - key: 'connected-apps', - icon: 'PlugConnected', - label: 'Connected Apps', - path: Pages.settingsConnectedApps(), - }, - { - key: 'settings', - icon: 'Settings', - label: 'Settings', - submenu: [ +export const sidebarItems = (hasErrors: boolean): Array => + ( + [ { - key: 'assets', - icon: 'Coins', - label: 'Assets', - path: Pages.assets(), + key: 'wallet', + icon: 'Wallet', + label: 'Wallet', + path: Pages.wallet(), }, { - key: 'view-seed-phrase', - icon: 'Lock', - label: 'View Seed Phrase', - onPress() { - store.openViewSeedPhrase(); - }, + key: 'history', + icon: 'History', + label: 'Transaction History', + path: Pages.txs(), }, { - key: 'change-password', - icon: 'Lock', - label: 'Change Password', - path: Pages.settingsChangePassword(), + key: 'networks', + icon: 'BrandStackshare', + label: 'Networks Management', + onPress() { + store.openNetworksList(); + }, }, { - key: 'logout', - icon: 'Logout', - label: 'Logout', + key: 'accounts', + icon: 'Users', + label: 'Account Management', onPress() { - store.openAccountsLogout(); + store.openAccountList(); }, }, - ], - }, - { - key: 'support', - icon: 'HelpCircle', - label: 'Support', - submenu: [ { - key: 'discord', - icon: 'BrandDiscordFilled', - label: 'Fuel Discord', - ahref: 'https://discord.com/invite/xfpK4Pe', + key: 'connected-apps', + icon: 'PlugConnected', + label: 'Connected Apps', + path: Pages.settingsConnectedApps(), }, + hasErrors + ? { + key: 'hasErrors', + icon: 'AlertTriangle', + label: 'Review Errors', + path: Pages.errors(), + badge: true, + } + : undefined, { - key: 'forum', - icon: 'MessageCircle', - label: 'Forum', - ahref: 'https://forum.fuel.network/c/fuel-wallet/15', + key: 'settings', + icon: 'Settings', + label: 'Settings', + submenu: [ + { + key: 'assets', + icon: 'Coins', + label: 'Assets', + path: Pages.assets(), + }, + { + key: 'view-seed-phrase', + icon: 'Lock', + label: 'View Seed Phrase', + onPress() { + store.openViewSeedPhrase(); + }, + }, + { + key: 'change-password', + icon: 'Lock', + label: 'Change Password', + path: Pages.settingsChangePassword(), + }, + { + key: 'logout', + icon: 'Logout', + label: 'Logout', + onPress() { + store.openAccountsLogout(); + }, + }, + ], }, { - key: 'github', - icon: 'BrandGithubFilled', - label: 'Github', - ahref: 'https://github.com/FuelLabs/fuels-wallet', + key: 'support', + icon: 'HelpCircle', + label: 'Support', + submenu: [ + { + key: 'discord', + icon: 'BrandDiscordFilled', + label: 'Fuel Discord', + ahref: 'https://discord.com/invite/xfpK4Pe', + }, + { + key: 'forum', + icon: 'MessageCircle', + label: 'Forum', + ahref: 'https://forum.fuel.network/c/fuel-wallet/15', + }, + { + key: 'github', + icon: 'BrandGithubFilled', + label: 'Github', + ahref: 'https://github.com/FuelLabs/fuels-wallet', + }, + { + key: 'bugs', + icon: 'Bug', + label: 'Report a Bug', + /** This page isn't created yet */ + ahref: 'https://github.com/FuelLabs/fuels-wallet/issues/new/choose', + }, + ], }, { - key: 'bugs', - icon: 'Bug', - label: 'Report a Bug', - /** This page isn't created yet */ - ahref: 'https://github.com/FuelLabs/fuels-wallet/issues/new/choose', + key: 'lock-wallet', + icon: 'Lock', + label: 'Lock Wallet', + onPress() { + store.lock(); + }, }, - ], - }, - { - key: 'lock-wallet', - icon: 'Lock', - label: 'Lock Wallet', - onPress() { - store.lock(); - }, - }, -]; + ] as Array + ).filter((item) => item !== undefined) as Array; diff --git a/packages/app/src/systems/Unlock/guards/UnlockGuard.tsx b/packages/app/src/systems/Unlock/guards/UnlockGuard.tsx index 25bb9c31c..de6a20b9d 100644 --- a/packages/app/src/systems/Unlock/guards/UnlockGuard.tsx +++ b/packages/app/src/systems/Unlock/guards/UnlockGuard.tsx @@ -1,6 +1,5 @@ import { Outlet } from 'react-router-dom'; -import { ErrorFloatingButton } from '~/systems/Error/components/ErrorFloatingButton'; import { useUnlock } from '../hooks'; import { UnlockPage } from '../pages'; @@ -8,12 +7,7 @@ export function UnlockGuard() { const { isUnlocked } = useUnlock(); if (isUnlocked) { - return ( - <> - - - - ); + return ; } return ;