Skip to content

Commit e96be31

Browse files
committed
refactor(wallet): simplify navigation buttons without stored history
1 parent d5e9270 commit e96be31

6 files changed

Lines changed: 34 additions & 185 deletions

File tree

Lines changed: 26 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
// Copyright (c) 2026 IOTA Stiftung
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import {
5-
createContext,
6-
useCallback,
7-
useContext,
8-
useEffect,
9-
useMemo,
10-
useState,
11-
type ReactNode,
12-
} from 'react';
13-
import { useLocation, useNavigate, useNavigationType, NavigationType } from 'react-router-dom';
4+
import { createContext, useContext, useEffect, useRef, useState, type ReactNode } from 'react';
5+
import { useLocation, useNavigationType } from 'react-router-dom';
146
import { NAVBAR_ITEM_PATHS } from './navigation';
157

168
export const HOME_PATH = NAVBAR_ITEM_PATHS.home;
@@ -19,174 +11,44 @@ export const TAB_BAR_PATHS = new Set<string>(
1911
Object.values(NAVBAR_ITEM_PATHS).filter((p) => p !== HOME_PATH),
2012
);
2113

22-
const NAVIGATION_HISTORY_SESSION_KEY = 'navigation-history';
14+
const NavigationDepthContext = createContext<number>(0);
2315

24-
type RouteKind = 'home' | 'tabBar' | 'other';
25-
26-
interface HistoryEntry {
27-
pathname: string;
28-
depth: number;
29-
}
30-
31-
interface NavigationHistory {
32-
idx: number;
33-
entries: (HistoryEntry | null)[];
34-
}
35-
36-
function getRouteKind(pathname: string, search: string): RouteKind {
37-
const hasMenuParam = new URLSearchParams(search).has('menu');
38-
if (hasMenuParam) return 'other';
39-
if (pathname === HOME_PATH || pathname.startsWith(HOME_PATH + '/')) return 'home';
40-
if (TAB_BAR_PATHS.has(pathname)) return 'tabBar';
41-
return 'other';
42-
}
43-
44-
const FALLBACK_DEPTH_BY_ROUTE_KIND: Record<RouteKind, number> = {
45-
home: 0,
46-
tabBar: 1,
47-
other: 2,
48-
};
49-
50-
function getHistoryIndex(): number {
51-
const idx = window.history.state?.idx;
52-
return typeof idx === 'number' ? idx : 0;
16+
function getRootDepth({ pathname, search }: { pathname: string; search: string }): number | null {
17+
if (new URLSearchParams(search).has('menu')) return null;
18+
if (pathname === HOME_PATH || pathname.startsWith(HOME_PATH + '/')) return 0;
19+
if (TAB_BAR_PATHS.has(pathname)) return 1;
20+
return null;
5321
}
5422

55-
function isHistoryEntry(value: unknown): value is HistoryEntry {
56-
const entry = value as Partial<HistoryEntry> | null;
57-
return typeof entry?.pathname === 'string' && typeof entry.depth === 'number';
58-
}
59-
60-
function readStoredEntries(): (HistoryEntry | null)[] {
61-
try {
62-
const raw = sessionStorage.getItem(NAVIGATION_HISTORY_SESSION_KEY);
63-
if (!raw) return [];
64-
const stored: unknown = JSON.parse(raw);
65-
return Array.isArray(stored) &&
66-
stored.every((item) => item === null || isHistoryEntry(item))
67-
? stored
68-
: [];
69-
} catch {
70-
return [];
71-
}
72-
}
73-
74-
function writeStoredEntries(entries: (HistoryEntry | null)[]) {
75-
try {
76-
sessionStorage.setItem(NAVIGATION_HISTORY_SESSION_KEY, JSON.stringify(entries));
77-
} catch {
78-
return;
79-
}
80-
}
81-
82-
function resolveEntries(
83-
prev: (HistoryEntry | null)[],
84-
idx: number,
85-
pathname: string,
86-
search: string,
87-
navigationType: NavigationType,
88-
): (HistoryEntry | null)[] {
89-
const known = prev[idx];
90-
if (navigationType === NavigationType.Pop && known?.pathname === pathname) return prev;
91-
92-
const routeKind = getRouteKind(pathname, search);
93-
const previous = prev[idx - 1];
94-
let depth = FALLBACK_DEPTH_BY_ROUTE_KIND[routeKind];
95-
if (routeKind === 'home') {
96-
depth = 0;
97-
} else if (routeKind === 'tabBar') {
98-
depth = 1;
99-
} else if (navigationType === NavigationType.Push && previous) {
100-
depth = previous.depth + 1;
101-
} else if (navigationType === NavigationType.Replace && known) {
102-
depth = known.depth;
103-
}
104-
105-
if (navigationType === NavigationType.Push) {
106-
const base = Array.from({ length: idx }, (_, i) => prev[i] ?? null);
107-
return [...base, { pathname, depth }];
108-
}
109-
const next = Array.from({ length: Math.max(prev.length, idx + 1) }, (_, i) => prev[i] ?? null);
110-
next[idx] = { pathname, depth };
111-
return next;
112-
}
113-
114-
interface NavigationStackContextValue {
115-
depth: number;
116-
goBackTo: (path: string) => void;
117-
}
118-
119-
const NavigationStackContext = createContext<NavigationStackContextValue>({
120-
depth: 0,
121-
goBackTo: () => {},
122-
});
123-
12423
export function NavigationStackProvider({ children }: { children: ReactNode }) {
12524
const location = useLocation();
126-
const navigate = useNavigate();
12725
const navigationType = useNavigationType();
128-
const [history, setHistory] = useState<NavigationHistory>(() => {
129-
const idx = getHistoryIndex();
130-
return {
131-
idx,
132-
entries: resolveEntries(
133-
readStoredEntries(),
134-
idx,
135-
location.pathname,
136-
location.search,
137-
NavigationType.Pop,
138-
),
139-
};
140-
});
26+
const [depth, setDepth] = useState(() => getRootDepth(location) ?? 2);
27+
const lastLocationKey = useRef(location.key);
28+
const isDepthUnknown = useRef(getRootDepth(location) === null);
14129

14230
useEffect(() => {
143-
setHistory((prev) => {
144-
const idx = getHistoryIndex();
145-
return {
146-
idx,
147-
entries: resolveEntries(
148-
prev.entries,
149-
idx,
150-
location.pathname,
151-
location.search,
152-
navigationType,
153-
),
154-
};
155-
});
31+
if (lastLocationKey.current === location.key) return;
32+
lastLocationKey.current = location.key;
33+
34+
const rootDepth = getRootDepth(location);
35+
if (rootDepth !== null) {
36+
isDepthUnknown.current = false;
37+
setDepth(rootDepth);
38+
} else if (navigationType === 'PUSH') {
39+
setDepth((prev) => prev + 1);
40+
} else if (navigationType === 'POP') {
41+
const minDepth = isDepthUnknown.current ? 2 : 1;
42+
setDepth((prev) => Math.max(minDepth, prev - 1));
43+
}
15644
// oxlint-disable-next-line react-hooks/exhaustive-deps
15745
}, [location.key, navigationType]);
15846

159-
useEffect(() => {
160-
writeStoredEntries(history.entries);
161-
}, [history.entries]);
162-
163-
const depth = history.entries[history.idx]?.depth ?? 0;
164-
165-
const goBackTo = useCallback(
166-
(path: string) => {
167-
const targetIdx = history.entries.findLastIndex(
168-
(entry, i) => i < history.idx && entry?.pathname === path,
169-
);
170-
if (targetIdx === -1) {
171-
navigate(path, { replace: true });
172-
} else {
173-
navigate(targetIdx - history.idx);
174-
}
175-
},
176-
[history, navigate],
177-
);
178-
179-
const value = useMemo(() => ({ depth, goBackTo }), [depth, goBackTo]);
180-
18147
return (
182-
<NavigationStackContext.Provider value={value}>{children}</NavigationStackContext.Provider>
48+
<NavigationDepthContext.Provider value={depth}>{children}</NavigationDepthContext.Provider>
18349
);
18450
}
18551

18652
export function useNavigationDepth(): number {
187-
return useContext(NavigationStackContext).depth;
188-
}
189-
190-
export function useGoBackTo(): (path: string) => void {
191-
return useContext(NavigationStackContext).goBackTo;
53+
return useContext(NavigationDepthContext);
19254
}

‎apps/wallet/src/ui/app/components/PageTemplate.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ export function PageTemplate({ title, children, isTitleCentered, onBack }: PageT
2424
onBack();
2525
} else if (TAB_BAR_PATHS.has(location.pathname)) {
2626
navigate(HOME_PATH, { replace: true });
27-
} else {
27+
} else if (window.history.state?.idx > 0) {
2828
navigate(-1);
29+
} else {
30+
navigate(HOME_PATH, { replace: true });
2931
}
3032
}, [onBack, navigate, location.pathname]);
3133

‎apps/wallet/src/ui/app/pages/accounts/CreateNewPasskey.tsx‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { useNavigate } from 'react-router-dom';
5-
import { useGoBackTo } from '_components/NavigationStackProvider';
65

76
import {
87
AccountsFormType,
@@ -30,7 +29,6 @@ type ImportPasskeyFormValues = z.infer<typeof formSchema>;
3029

3130
export function CreateNewPasskey() {
3231
const navigate = useNavigate();
33-
const goBackTo = useGoBackTo();
3432
useBootstrapSourceFlow();
3533

3634
const [authenticatorAttachment, setAuthenticatorAttachment] =
@@ -85,7 +83,7 @@ export function CreateNewPasskey() {
8583
<PageTemplate
8684
title="Create Passkey Account"
8785
isTitleCentered
88-
onBack={() => goBackTo('/accounts/import-existing')}
86+
onBack={() => navigate('/accounts/import-existing', { replace: true })}
8987
>
9088
<Form
9189
className="flex h-full flex-col justify-between"

‎apps/wallet/src/ui/app/pages/accounts/CreateNewWallet.tsx‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import { ampli } from '_src/shared/analytics/ampli';
55
import { useNavigate } from 'react-router-dom';
6-
import { useGoBackTo } from '_components/NavigationStackProvider';
76
import SecureYourWallet from '_assets/images/onboarding/secure-your-wallet.png';
87
import SecureYourWalletDark from '_assets/images/onboarding/secure-your-wallet-darkmode.png';
98
import { Card, CardType, CardBody, CardAction, CardActionType } from '@iota/apps-ui-kit';
@@ -20,7 +19,6 @@ import { isFirstAccount } from '../../helpers';
2019
export function CreateNewWallet() {
2120
const { theme } = useTheme();
2221
const navigate = useNavigate();
23-
const goBackTo = useGoBackTo();
2422
const [, setAccountsFormValues] = useAccountsFormContext();
2523
const { sourceFlowRef } = useSourceFlow();
2624
const sourceFlow = sourceFlowRef.current;
@@ -80,11 +78,7 @@ export function CreateNewWallet() {
8078
};
8179

8280
return (
83-
<PageTemplate
84-
title="Create a new wallet"
85-
isTitleCentered
86-
onBack={() => goBackTo('/accounts/add-account')}
87-
>
81+
<PageTemplate title="Create a new wallet" isTitleCentered>
8882
<div className="flex h-full w-full flex-col">
8983
<div className="flex w-full flex-1 flex-col justify-center py-md--rs text-center">
9084
<div className="flex flex-col items-center gap-y-4">

‎apps/wallet/src/ui/app/pages/accounts/ImportExistingWallet.tsx‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import { ampli } from '_src/shared/analytics/ampli';
55
import { useNavigate } from 'react-router-dom';
6-
import { useGoBackTo } from '_components/NavigationStackProvider';
76
import ImportAWallet from '_assets/images/onboarding/import-a-wallet.png';
87
import ImportAWalletDark from '_assets/images/onboarding/import-a-wallet-darkmode.png';
98
import { Card, CardType, CardBody, CardAction, CardActionType } from '@iota/apps-ui-kit';
@@ -21,7 +20,6 @@ import { ACCOUNT_FORM_TYPE_TO_AMPLI } from '_src/shared/analytics';
2120
export function ImportExistingWallet() {
2221
const { theme } = useTheme();
2322
const navigate = useNavigate();
24-
const goBackTo = useGoBackTo();
2523
const isPopupOrSidePanel = useAppSelector(
2624
(state) =>
2725
state.app.extensionViewType === ExtensionViewType.Popup ||
@@ -101,11 +99,7 @@ export function ImportExistingWallet() {
10199
const hasManyProfileOptions = profileOptions.length >= 3;
102100

103101
return (
104-
<PageTemplate
105-
title="Import a wallet"
106-
isTitleCentered
107-
onBack={() => goBackTo('/accounts/add-account')}
108-
>
102+
<PageTemplate title="Import a wallet" isTitleCentered>
109103
<div className="flex h-full w-full flex-col">
110104
<div className="flex w-full flex-1 flex-col justify-center gap-4 py-md--rs text-center">
111105
<img

‎apps/wallet/src/ui/app/pages/accounts/ImportPasskeyPage.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { useNavigate, useSearchParams } from 'react-router-dom';
5-
import { useGoBackTo } from '_components/NavigationStackProvider';
65
import { Form } from '../../shared/forms/Form';
76

87
import {
@@ -19,7 +18,7 @@ import { z } from 'zod';
1918
import { useBackgroundClient } from '../../hooks';
2019

2120
export function ImportPasskeyPage() {
22-
const goBackTo = useGoBackTo();
21+
const navigate = useNavigate();
2322
const [searchParams] = useSearchParams();
2423
const accountID = searchParams.get('accountID') || '';
2524
useBootstrapSourceFlow();
@@ -28,7 +27,7 @@ export function ImportPasskeyPage() {
2827
<PageTemplate
2928
title="Import Passkey Account"
3029
isTitleCentered
31-
onBack={() => goBackTo('/accounts/import-existing')}
30+
onBack={() => navigate('/accounts/import-existing', { replace: true })}
3231
>
3332
{accountID ? (
3433
<NicknameSetContent accountID={accountID} />

0 commit comments

Comments
 (0)