Skip to content
Open
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
105 changes: 105 additions & 0 deletions e2e-tests/popup/common/home-tabs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { popup, popupExpect } from '../../fixtures';

popup.describe('Popup UI: Home tabs', () => {
popup(
'should stay on the Tokens tab after coming back from token details',
async ({ popupPage, unlockVault }) => {
await unlockVault();
await new Promise(r => setTimeout(r, 2000));

// Reach Home the way that used to stamp the stale tab index onto the
// history entry: open an NFT and come back.
await popupPage.getByText('NFTs').click();
await new Promise(r => setTimeout(r, 2000));

await popupPage
.getByTestId('nft-token-card')
.filter({ hasText: 'west' })
.click();
await popupExpect(
popupPage.getByRole('heading', { name: 'west' })
).toBeVisible();

await popupPage.getByText('Back').click();
await popupExpect(popupPage.getByTitle('NFTs')).toBeVisible();

// Switch to Tokens, open a token, come back.
await popupPage.getByText('Tokens').click();
await popupExpect(popupPage.getByTitle('Tokens')).toBeVisible();

await popupPage.getByText('Casper', { exact: true }).first().click();
await popupExpect(
popupPage.getByRole('heading', { name: 'Token' })
).toBeVisible();

await popupPage.getByText('Back').click();

await popupExpect(popupPage.getByTitle('Tokens')).toBeVisible();
}
);

popup(
'should stay on the Activity tab after coming back from deploy details',
async ({ popupPage, unlockVault }) => {
await unlockVault();
await new Promise(r => setTimeout(r, 2000));

await popupPage.getByText('Activity').click();
await new Promise(r => setTimeout(r, 2000));

await popupPage.getByTestId('deploy-plate').first().click();
await popupPage.getByText('Back').click();

await popupExpect(popupPage.getByTitle('Activity')).toBeVisible();

await popupPage.getByText('Tokens').click();
await popupPage.getByText('Casper', { exact: true }).first().click();
await popupExpect(
popupPage.getByRole('heading', { name: 'Token' })
).toBeVisible();

await popupPage.getByText('Back').click();

await popupExpect(popupPage.getByTitle('Tokens')).toBeVisible();
}
);

popup(
'should keep the active tab when the navigation menu is opened and closed',
async ({ popupPage, unlockVault }) => {
await unlockVault();
await new Promise(r => setTimeout(r, 2000));

await popupPage.getByText('NFTs').click();
await popupExpect(popupPage.getByTitle('NFTs')).toBeVisible();

await popupPage.getByTestId('menu-open-icon').click();
await popupPage.getByTestId('menu-close-icon').click();

await popupExpect(popupPage.getByTitle('NFTs')).toBeVisible();
}
);

popup(
'should keep deploy details rendered when the navigation menu is toggled',
async ({ popupPage, unlockVault }) => {
await unlockVault();
await new Promise(r => setTimeout(r, 2000));

await popupPage.getByText('Activity').click();
await new Promise(r => setTimeout(r, 2000));

await popupPage.getByTestId('deploy-plate').first().click();
await popupExpect(
popupPage.getByText('Timestamp', { exact: true })
).toBeVisible();

await popupPage.getByTestId('menu-open-icon').click();
await popupPage.getByTestId('menu-close-icon').click();

await popupExpect(
popupPage.getByText('Timestamp', { exact: true })
).toBeVisible();
}
);
});
5 changes: 4 additions & 1 deletion src/apps/popup/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { HashRouter, Route, Routes } from 'react-router-dom';

import { useUserActivityTracker } from '@src/hooks/use-user-activity-tracker';

import { HomeTabProvider } from '@popup/hooks/use-home-tab';
import { AccountSettingsPage } from '@popup/pages/account-settings';
import { AddContactPage } from '@popup/pages/add-contact';
import { AddWatchAccount } from '@popup/pages/add-watch-account';
Expand Down Expand Up @@ -63,7 +64,9 @@ export function AppRouter() {

return (
<HashRouter>
<AppRoutes />
<HomeTabProvider>
<AppRoutes />
</HomeTabProvider>
</HashRouter>
);
}
Expand Down
50 changes: 50 additions & 0 deletions src/apps/popup/hooks/use-home-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, {
ReactNode,
createContext,
useContext,
useMemo,
useState
} from 'react';

import { HomePageTabName } from '@src/constants';

interface HomeTabContextValue {
activeHomeTab: HomePageTabName;
setActiveHomeTab: (tab: HomePageTabName) => void;
}

const HomeTabContext = createContext<HomeTabContextValue | null>(null);

/**
* Owns the active Home tab for the lifetime of the popup session.
*
* Deliberately ephemeral: it is destroyed when the popup closes (and when the
* vault locks), so reopening always starts on Tokens. The tab must not travel
* in react-router `location.state` — state is bound to a history entry, so a
* later `navigate(-1)` would restore whatever tab was intended when that entry
* was created rather than the tab the user actually left (WALLET-1360).
*/
export const HomeTabProvider = ({ children }: { children: ReactNode }) => {
const [activeHomeTab, setActiveHomeTab] = useState<HomePageTabName>(
HomePageTabName.Tokens
);

const value = useMemo(
() => ({ activeHomeTab, setActiveHomeTab }),
[activeHomeTab]
);

return (
<HomeTabContext.Provider value={value}>{children}</HomeTabContext.Provider>
);
};

export const useHomeTab = () => {
const context = useContext(HomeTabContext);

if (!context) {
throw new Error('useHomeTab must be used within HomeTabProvider');
}

return context;
};
22 changes: 3 additions & 19 deletions src/apps/popup/pages/deploy-details/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import React from 'react';

import { DeployDetailsPageContent } from '@popup/pages/deploy-details/content';
import { RouterPath, useTypedLocation, useTypedNavigate } from '@popup/router';
import { useTypedLocation } from '@popup/router';

import {
HeaderPopup,
HeaderSubmenuBarNavLink,
HeaderViewInExplorer,
PopupLayout
} from '@libs/layout';
import { HomePageTabsId } from '@libs/ui/components';

export const DeployDetailsPage = () => {
const location = useTypedLocation();
const navigate = useTypedNavigate();

const { deploy, navigateHome } = location.state;
const { deploy } = location.state;

return (
<PopupLayout
Expand All @@ -26,21 +24,7 @@ export const DeployDetailsPage = () => {
withConnectionStatus
renderSubmenuBarItems={() => (
<>
<HeaderSubmenuBarNavLink
linkType="back"
onClick={
navigateHome
? () => {
navigate(RouterPath.Home, {
state: {
// set the active tab to deploys
activeTabId: HomePageTabsId.Deploys
}
});
}
: undefined
}
/>
<HeaderSubmenuBarNavLink linkType="back" />
<HeaderViewInExplorer deployHash={deploy?.deployHash} />
</>
)}
Expand Down
6 changes: 1 addition & 5 deletions src/apps/popup/pages/home/components/deploys-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ export const DeploysList = () => {
contentTop={SpacingSize.None}
rows={deploys}
renderRow={deploy => (
<DeployPlate
deploy={deploy}
navigateHome={true}
onClick={setActivityPlateYPosition}
/>
<DeployPlate deploy={deploy} onClick={setActivityPlateYPosition} />
)}
marginLeftForItemSeparatorLine={0}
borderBottomPseudoElementRules={activityBottomPseudoElementRules}
Expand Down
25 changes: 12 additions & 13 deletions src/apps/popup/pages/home/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { useEffect, useRef } from 'react';
import React, { useRef } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { shallowEqual, useSelector } from 'react-redux';
import styled from 'styled-components';

import { HomePageTabName, NetworkSetting } from '@src/constants';
import { isSafariBuild } from '@src/utils';

import { RouterPath, useTypedLocation, useTypedNavigate } from '@popup/router';
import { useHomeTab } from '@popup/hooks/use-home-tab';
import { RouterPath, useTypedNavigate } from '@popup/router';

import {
selectActiveNetworkSetting,
Expand Down Expand Up @@ -61,12 +62,18 @@ const Container = styled(TileContainer)`
export function HomePageContent() {
const navigate = useTypedNavigate();
const { t } = useTranslation();
const location = useTypedLocation();
const dismissedAppEventIds = useSelector(
selectDismissedAppEvents,
shallowEqual
);
const state = location.state;

const { activeHomeTab, setActiveHomeTab } = useHomeTab();

const handleTabChange = (tabName: string) => {
// `Tabs` is name-addressed and generic over plain strings; on this page
// the names are exactly the HomePageTabName members rendered below.
setActiveHomeTab(tabName as HomePageTabName);
};

const network = useSelector(selectActiveNetworkSetting);
const activeAccount = useSelector(selectVaultActiveAccount);
Expand All @@ -84,14 +91,6 @@ export function HomePageContent() {
wasExpirationBannerShown.current = true;
}

useEffect(() => {
if (!state?.activeTabId) {
const container = document.querySelector('#ms-container');

container?.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
}
}, [state?.activeTabId]);

const { activeMarketingEvent } =
useGetActiveAppMarketingEvent(dismissedAppEventIds);

Expand Down Expand Up @@ -150,7 +149,7 @@ export function HomePageContent() {
</Tile>
)}
<VerticalSpaceContainer top={SpacingSize.Tiny}>
<Tabs preferActiveTabId={state?.activeTabId}>
<Tabs activeTabName={activeHomeTab} onTabChange={handleTabChange}>
<Tab tabName={HomePageTabName.Tokens}>
<TokensList />
</Tab>
Expand Down
10 changes: 1 addition & 9 deletions src/apps/popup/pages/nft-details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
PopupLayout
} from '@libs/layout';
import { useFetchNftTokens } from '@libs/services/nft-service';
import { HomePageTabsId } from '@libs/ui/components';

import { NftDetailsContent } from './content';

Expand Down Expand Up @@ -45,14 +44,7 @@ export const NftDetailsPage = () => {
withConnectionStatus
renderSubmenuBarItems={() => (
<>
<HeaderSubmenuBarNavLink
linkType="back"
onClick={() =>
navigate(RouterPath.Home, {
state: { activeTabId: HomePageTabsId.NFTs }
})
}
/>
<HeaderSubmenuBarNavLink linkType="back" />
<HeaderViewInExplorer
nftTokenId={tokenId}
contractPackageHash={contractPackageHash}
Expand Down
15 changes: 5 additions & 10 deletions src/apps/popup/pages/stakes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import styled from 'styled-components';
import {
AuctionManagerEntryPoint,
ErrorMessages,
HomePageTabName,
STAKE_COST_MOTES,
StakeSteps,
networkNameToSdkNetworkNameMap
} from '@src/constants';

import { useAccountManager } from '@popup/hooks/use-account-actions-with-events';
import { useHomeTab } from '@popup/hooks/use-home-tab';
import { AmountStep } from '@popup/pages/stakes/amount-step';
import { ConfirmStep } from '@popup/pages/stakes/confirm-step';
import { NoDelegations } from '@popup/pages/stakes/no-delegations';
Expand Down Expand Up @@ -75,7 +77,6 @@ import {
import { buildAuctionTransactions } from '@libs/services/tx-builders';
import {
Button,
HomePageTabsId,
LedgerEventView,
SvgIcon,
TransferSuccessScreen,
Expand Down Expand Up @@ -115,6 +116,7 @@ export const StakesPage = () => {
const isCasper2Network = useSelector(selectIsCasper2Network);
const casperNetworkApiVersion = useSelector(selectCasperNetworkApiVersion);
const { changeActiveAccountSupportsWithEvent } = useAccountManager();
const { setActiveHomeTab } = useHomeTab();

const activeAccount = useSelector(selectVaultActiveAccount);
const isActiveAccountFromLedger = useSelector(
Expand Down Expand Up @@ -524,15 +526,8 @@ export const StakesPage = () => {
askForReviewAfter == null || currentDate > askForReviewAfter;

if (ratedInStore || !shouldAskForReview) {
const homeRoutesState = {
state: {
// set the active tab to deploys
activeTabId: HomePageTabsId.Deploys
}
};

// Navigate "Home" with the pre-defined state
navigate(RouterPath.Home, homeRoutesState);
setActiveHomeTab(HomePageTabName.Activity);
navigate(RouterPath.Home);
} else {
// Navigate to "RateApp" when the application has not been rated in the store,
// and it's time to ask for a review.
Expand Down
Loading
Loading