From aa4dc57b293db72eb0e99708d911f447cc0893f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Tranta?= Date: Tue, 4 Feb 2025 15:38:07 +0100 Subject: [PATCH] feat(suite): display the left menu sidebar in welcome state --- .../suite-desktop-core/e2e/support/bridge.ts | 4 +- .../onboarding/onboardingActions.ts | 6 +- .../pageActions/settings/settingsActions.ts | 2 +- .../e2e/tests/bridge-tor/spawn-tor.test.ts | 4 +- .../Preloader/__tests__/Preloader.test.tsx | 3 + .../suite/layouts/LoggedOutLayout.tsx | 65 +++---- .../SuiteLayout/Sidebar/Navigation.tsx | 12 +- .../Sidebar/QuickActions/QuickActions.tsx | 33 ++-- .../layouts/SuiteLayout/Sidebar/Sidebar.tsx | 5 +- .../layouts/WelcomeLayout/WelcomeLayout.tsx | 171 +++++++----------- .../reducers/suite/desktopUpdateReducer.ts | 2 + 11 files changed, 137 insertions(+), 170 deletions(-) diff --git a/packages/suite-desktop-core/e2e/support/bridge.ts b/packages/suite-desktop-core/e2e/support/bridge.ts index e5ecd31ac5e..09c0e6372c0 100644 --- a/packages/suite-desktop-core/e2e/support/bridge.ts +++ b/packages/suite-desktop-core/e2e/support/bridge.ts @@ -14,12 +14,12 @@ export const expectBridgeToBeStopped = async (request: APIRequestContext) => { }).rejects.toThrow('ECONNREFUSED'); }; -// We wait for `@welcome/title` or `@dashboard/graph` since +// We wait for `@welcome-layout/body` or `@dashboard/graph` since // one or the other will be display depending on the state of the app // due to previously run tests. And both means the same for the porpoise of this test. // Bridge should be ready to check `/status` endpoint. export const waitForAppToBeInitialized = async (suite: any) => await Promise.race([ - expect(suite.window.getByTestId('@welcome/title')).toBeVisible(), + expect(suite.window.getByTestId('@welcome-layout/body')).toBeVisible(), expect(suite.window.getByTestId('@dashboard/graph')).toBeVisible(), ]); diff --git a/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts b/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts index 32acc39d604..fd389ffbc94 100644 --- a/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts +++ b/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts @@ -18,7 +18,7 @@ export class OnboardingActions { readonly pin: PinActions; readonly tutorial: TutorialActions; - readonly welcomeTitle: Locator; + readonly welcomeBody: Locator; readonly onboardingContinueButton: Locator; readonly onboardingViewOnlySkipButton: Locator; readonly onboardingViewOnlyEnableButton: Locator; @@ -52,7 +52,7 @@ export class OnboardingActions { this.tutorial = new TutorialActions(page); this.pin = new PinActions(page); - this.welcomeTitle = this.page.getByTestId('@welcome/title'); + this.welcomeBody = this.page.getByTestId('@welcome-layout/body'); this.onboardingContinueButton = this.page.getByTestId('@onboarding/exit-app-button'); this.onboardingViewOnlySkipButton = this.page.getByTestId('@onboarding/viewOnly/skip'); this.onboardingViewOnlyEnableButton = this.page.getByTestId('@onboarding/viewOnly/enable'); @@ -83,7 +83,7 @@ export class OnboardingActions { @step() async verifySuiteIsLoaded() { - await expect(this.welcomeTitle, 'expect Suite to load in under 30s').toBeVisible({ + await expect(this.welcomeBody, 'expect Suite to load in under 30s').toBeVisible({ timeout: 30_000, }); } diff --git a/packages/suite-desktop-core/e2e/support/pageActions/settings/settingsActions.ts b/packages/suite-desktop-core/e2e/support/pageActions/settings/settingsActions.ts index dede25fbd70..a1dc69ede77 100644 --- a/packages/suite-desktop-core/e2e/support/pageActions/settings/settingsActions.ts +++ b/packages/suite-desktop-core/e2e/support/pageActions/settings/settingsActions.ts @@ -88,7 +88,7 @@ export class SettingsActions { '@settings/early-access-confirm-button', ); this.earlyAccessSkipButton = this.page.getByTestId('@settings/early-access-skip-button'); - this.settingsCloseButton = this.page.getByTestId('@settings/menu/close'); + this.settingsCloseButton = this.page.getByTestId('@suite/menu/suite-start'); this.modal = this.page.getByTestId('@modal'); this.modalCloseButton = this.page.getByTestId('@modal/close-button'); this.deviceLabelInput = this.page.getByTestId('@settings/device/label-input'); diff --git a/packages/suite-desktop-core/e2e/tests/bridge-tor/spawn-tor.test.ts b/packages/suite-desktop-core/e2e/tests/bridge-tor/spawn-tor.test.ts index 478451e62c6..57bbb9dd35a 100644 --- a/packages/suite-desktop-core/e2e/tests/bridge-tor/spawn-tor.test.ts +++ b/packages/suite-desktop-core/e2e/tests/bridge-tor/spawn-tor.test.ts @@ -55,7 +55,7 @@ test.describe.skip('Tor loading screen', { tag: ['@group=suite', '@desktopOnly'] state: 'visible', }); - await suite.window.waitForSelector('[data-testid="@welcome/title"]', { timeout }); + await suite.window.waitForSelector('[data-testid="@welcome-layout/body"]', { timeout }); suite.electronApp.close(); }); @@ -83,7 +83,7 @@ test.describe.skip('Tor loading screen', { tag: ['@group=suite', '@desktopOnly'] state: 'visible', }); - await suite.window.waitForSelector('[data-testid="@welcome/title"]', { timeout }); + await suite.window.waitForSelector('[data-testid="@welcome-layout/body"]', { timeout }); networkAnalyzer.stop(); const requests = networkAnalyzer.getRequests(); requests.forEach(request => { diff --git a/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx b/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx index f7d1f250f87..ea3325a720a 100644 --- a/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx +++ b/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx @@ -1,6 +1,7 @@ import * as envUtils from '@trezor/env-utils'; import { DeepPartial } from '@trezor/type-utils'; +import { desktopUpdateInitialState } from 'src/reducers/suite/desktopUpdateReducer'; import { configureStore } from 'src/support/tests/configureStore'; import { findByTestId, renderWithProviders } from 'src/support/tests/hooksHelper'; @@ -85,7 +86,9 @@ const getInitialState = ({ suite, router, device }: any = {}) => ({ discovery: [], accountSearch: {}, settings: {}, + blockchain: {}, }, + desktopUpdate: desktopUpdateInitialState, router: { app: 'suite-index', loaded: true, diff --git a/packages/suite/src/components/suite/layouts/LoggedOutLayout.tsx b/packages/suite/src/components/suite/layouts/LoggedOutLayout.tsx index 395c2b57125..3b21b9938a3 100644 --- a/packages/suite/src/components/suite/layouts/LoggedOutLayout.tsx +++ b/packages/suite/src/components/suite/layouts/LoggedOutLayout.tsx @@ -1,6 +1,6 @@ import { ReactNode, useRef, useState } from 'react'; -import { ElevationContext, ElevationUp, NewModal } from '@trezor/components'; +import { ElevationContext, ElevationDown, ElevationUp, NewModal } from '@trezor/components'; import { GuideButton, GuideRouter } from 'src/components/guide'; import { useLayoutSize } from 'src/hooks/suite'; @@ -10,7 +10,6 @@ import { LayoutContext, LayoutContextPayload } from 'src/support/suite/LayoutCon import { ModalContextProvider } from 'src/support/suite/ModalContext'; import { Metadata } from '../Metadata'; -import { TrafficLightOffset } from '../TrafficLightOffset'; import { AppWrapper, Body, @@ -19,6 +18,7 @@ import { PageWrapper, Wrapper, } from './SuiteLayout/SuiteLayout'; +import { LoggedOutSidebar } from './WelcomeLayout/WelcomeLayout'; import { ModalSwitcher } from '../modals/ModalSwitcher/ModalSwitcher'; interface LoggedOutLayout { @@ -36,38 +36,39 @@ export const LoggedOutLayout = ({ children }: LoggedOutLayout) => { return ( - - - - - - - + + + + + + - - - - - {layoutHeader} - - {children} - - - - - + + + + + + + + {layoutHeader} + + {children} + + + + + - {!isMobileLayout && } - - - - - - + {!isMobileLayout && } + + + + + ); }; diff --git a/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx b/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx index a5a565a1724..fe34cd63076 100644 --- a/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx +++ b/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx @@ -2,13 +2,14 @@ import { FC } from 'react'; import styled from 'styled-components'; +import { Route } from '@suite-common/suite-types'; import { spacingsPx } from '@trezor/theme'; import { NavigationItem, NavigationItemProps } from './NavigationItem'; import { NotificationDropdown } from './NotificationDropdown'; import { useResponsiveContext } from '../../../../../support/suite/ResponsiveContext'; -const Nav = styled.nav<{ $isSidebarCollapsed: boolean }>` +export const Nav = styled.nav<{ $isSidebarCollapsed: boolean }>` display: flex; flex-direction: column; gap: ${spacingsPx.xxs}; @@ -18,6 +19,13 @@ const Nav = styled.nav<{ $isSidebarCollapsed: boolean }>` ${({ $isSidebarCollapsed }) => $isSidebarCollapsed && `align-items: center;`} `; +export const SETTINGS_ROUTES: Route['name'][] = [ + 'settings-index', + 'settings-device', + 'settings-coins', + 'settings-debug', +] as const; + const navItems: Array }> = [ { nameId: 'TR_DASHBOARD', @@ -34,7 +42,7 @@ const navItems: Array` display: flex; @@ -25,21 +24,21 @@ const ActionsContainer = styled.div<{ $isSidebarCollapsed: boolean }>` `; type QuickActionsProps = { - showUpdateBannerNotification: boolean; + isSidebarCollapsed: boolean; + showUpdateBannerNotification?: boolean; }; -export const QuickActions = ({ showUpdateBannerNotification }: QuickActionsProps) => { - const { isSidebarCollapsed } = useResponsiveContext(); - - return ( - - - - - - - - ); -}; +export const QuickActions = ({ + isSidebarCollapsed, + showUpdateBannerNotification, +}: QuickActionsProps) => ( + + + + + + + +); diff --git a/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Sidebar.tsx b/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Sidebar.tsx index abff6d072dc..1f21b56f91d 100644 --- a/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Sidebar.tsx +++ b/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Sidebar.tsx @@ -37,6 +37,8 @@ const Content = styled.div` flex-direction: column; `; +export const SIDEBAR_MIN_WIDTH = 84; + export const Sidebar = () => { const [closedNotificationDevice, setClosedNotificationDevice] = useState(false); const [closedNotificationSuite, setClosedNotificationSuite] = useState(false); @@ -75,7 +77,7 @@ export const Sidebar = () => { { )} diff --git a/packages/suite/src/components/suite/layouts/WelcomeLayout/WelcomeLayout.tsx b/packages/suite/src/components/suite/layouts/WelcomeLayout/WelcomeLayout.tsx index 0ce17179713..2116db2c6c0 100644 --- a/packages/suite/src/components/suite/layouts/WelcomeLayout/WelcomeLayout.tsx +++ b/packages/suite/src/components/suite/layouts/WelcomeLayout/WelcomeLayout.tsx @@ -1,65 +1,41 @@ import { ReactNode } from 'react'; -import { AnimatePresence, motion } from 'framer-motion'; import styled from 'styled-components'; import { selectBannerMessage } from '@suite-common/message-system'; import { - Button, Column, - ElevationContext, ElevationDown, ElevationUp, Row, useElevation, variables, } from '@trezor/components'; -import { isWeb } from '@trezor/env-utils'; -import { TrezorLogo } from '@trezor/product-components'; -import { useOnce } from '@trezor/react-utils'; -import { Elevation, mapElevationToBackground, spacings, spacingsPx } from '@trezor/theme'; -import { SUITE_URL, TREZOR_URL } from '@trezor/urls'; +import { + Elevation, + mapElevationToBackground, + mapElevationToBorder, + spacings, + spacingsPx, +} from '@trezor/theme'; import { GuideButton, GuideRouter } from 'src/components/guide'; -import { Translation } from 'src/components/suite'; // importing directly, otherwise unit tests fail, seems to be a styled-components issue import { MessageSystemBanner } from 'src/components/suite/banners'; import { MAX_ONBOARDING_WIDTH } from 'src/constants/suite/layout'; -import { useGuide } from 'src/hooks/guide'; import { useSelector } from 'src/hooks/suite'; import { TrafficLightOffset } from '../../TrafficLightOffset'; - -const Expander = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - flex: 1; - margin-top: 96px; -`; +import { Nav, SETTINGS_ROUTES } from '../SuiteLayout/Sidebar/Navigation'; +import { NavItem } from '../SuiteLayout/Sidebar/NavigationItem'; +import { QuickActions } from '../SuiteLayout/Sidebar/QuickActions/QuickActions'; +import { SIDEBAR_MIN_WIDTH } from '../SuiteLayout/Sidebar/Sidebar'; const WelcomeWrapper = styled.div<{ $elevation: Elevation }>` background-color: ${mapElevationToBackground}; height: 100%; `; -const MotionWelcome = styled(motion.div)` - height: 100%; - overflow: hidden; - min-width: 380px; - max-width: 660px; -`; - -const LinksContainer = styled.div` - bottom: 0; - display: flex; - margin: ${spacingsPx.xl}; - align-items: center; - flex-flow: row wrap; - gap: ${spacingsPx.md}; -`; - const Content = styled.div<{ $elevation: Elevation }>` display: flex; position: relative; @@ -89,65 +65,42 @@ interface WelcomeLayoutProps { children: ReactNode; } -const Left = () => { - const { elevation } = useElevation(); - - const { isGuideOpen, isGuideOnTop } = useGuide(); +const WelcomeNavColumn = styled.div<{ $elevation: Elevation; $minWidth: number }>` + border-right: solid 1px ${mapElevationToBorder}; + min-width: ${({ $minWidth }) => $minWidth}px; + height: 100%; +`; - // do not animate welcome bar on initial load - const isFirstRender = useOnce(true, false); +export const LoggedOutSidebar = () => { + const { elevation } = useElevation(); return ( - - {(!isGuideOpen || isGuideOnTop) && ( - - - - - - - - - {isWeb() && ( - - )} - - - - - - )} - + + + + + + + + + + ); }; @@ -170,25 +123,23 @@ export const WelcomeLayout = ({ children }: WelcomeLayoutProps) => { const bannerMessage = useSelector(selectBannerMessage); return ( - - - - {bannerMessage && ( - - )} - - - - - - - {children} - - - - - - - + + + {bannerMessage && ( + + )} + + + + + + + {children} + + + + + + ); }; diff --git a/packages/suite/src/reducers/suite/desktopUpdateReducer.ts b/packages/suite/src/reducers/suite/desktopUpdateReducer.ts index eb465b0c20b..64a9edbd3d4 100644 --- a/packages/suite/src/reducers/suite/desktopUpdateReducer.ts +++ b/packages/suite/src/reducers/suite/desktopUpdateReducer.ts @@ -46,6 +46,8 @@ const initialState: DesktopUpdateState = { justUpdatedInteractedWith: false, }; +export const desktopUpdateInitialState = initialState; + const desktopUpdateReducer = ( state: DesktopUpdateState = initialState, action: Action,