diff --git a/src/App.tsx b/src/App.tsx index e6f15c76d..ac1d8eb9c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,4 @@ -import { lazy, Suspense, useEffect, useMemo, useState } from 'react' -import type { PropsWithChildren } from 'react' +import { lazy, Suspense, useCallback, useEffect, useState } from 'react' import { lockwalletOptions } from '@joinmarket-webui/joinmarket-api-ts/@tanstack/react-query' import { token } from '@joinmarket-webui/joinmarket-api-ts/jm' import { QueryClientProvider, useMutation, useQuery } from '@tanstack/react-query' @@ -13,6 +12,7 @@ import { Outlet, Route, RouterProvider, + useOutletContext, type NavigateFunction, } from 'react-router-dom' import { toast } from 'sonner' @@ -66,8 +66,23 @@ const clearAuthAndQueryCache = () => { queryClient.clear() } -const ProtectedRoute = ({ authenticated, children }: PropsWithChildren<{ authenticated: boolean }>) => { - return authenticated ? <>{children} : +const useWalletFileName = () => useStore(authStore, (state) => state.state?.walletFileName) + +const useAuthenticated = () => + useStore(authStore, (state) => state.state?.walletFileName !== undefined && state.state?.auth?.token !== undefined) + +const ProtectedRoute = () => { + const walletFileName = useWalletFileName() + return useAuthenticated() ? ( + + + {walletFileName && } + + + + ) : ( + + ) } type LockWalletDialogContext = { @@ -76,20 +91,50 @@ type LockWalletDialogContext = { t: TFunction<'translation', undefined> } -function App() { - const walletFileName = useStore(authStore, (state) => state.state?.walletFileName) - const hasAuthToken = useStore(authStore, (state) => state.state?.auth?.token !== undefined) - const { enabled: isDeveloperMode } = useDeveloperMode() - const authenticated = useMemo(() => walletFileName !== undefined && hasAuthToken, [walletFileName, hasAuthToken]) +type LockWalletActionContextValue = { + onLockWallet: (navigate: NavigateFunction, t: TFunction<'translation', undefined>) => Promise +} - const jmSession = useStore(jmSessionStore, (state) => state.state) +const LoginRoute = () => { + return useAuthenticated() ? : +} + +const CreateWalletRoute = () => { + return useAuthenticated() ? : +} + +const ImportWalletRoute = () => { + return useAuthenticated() ? : +} + +const DevSetupRoute = () => { + return useDeveloperMode().enabled && isDebugFeatureEnabled('devSetupPage') ? ( + }> + + + ) : ( + + ) +} + +const DevErrorExampleRoute = () => { + return useDeveloperMode().enabled && isDebugFeatureEnabled('devErrorExamplePage') ? ( + }> + + + ) : ( + + ) +} +const useWalletLockController = () => { + const walletFileName = useWalletFileName() + const jmSession = useStore(jmSessionStore, (state) => state.state) const makerRunning = jmSession?.maker_running === true const coinjoinInProgress = jmSession?.coinjoin_in_process === true || (jmSession?.schedule?.length || 0) > 0 - const client = useApiClient() - const lockWalletQuery = useQuery( + const { refetch: refetchLockWallet, isFetching: isLockingWallet } = useQuery( { ...lockwalletOptions({ client, @@ -99,12 +144,12 @@ function App() { }, queryClient, ) - const lockWalletMutation = useMutation( + const { mutateAsync: lockWalletMutateAsync } = useMutation( { scope: { id: 'lock-wallet' }, mutationFn: withMutationDelay( async () => { - return await lockWalletQuery.refetch({ throwOnError: true }) + return await refetchLockWallet({ throwOnError: true }) }, { throttle: 210, @@ -117,137 +162,148 @@ function App() { const [lockWalletDialogContext, setLockWalletDialogContext] = useState() - const doOnLogout = async (navigate: NavigateFunction) => { + const doOnLogout = useCallback(async (navigate: NavigateFunction) => { clearAuthAndQueryCache() await navigate(routes.login) + }, []) + + const doOnLockWalletConfirm = useCallback( + async (navigate: NavigateFunction, t: TFunction<'translation', undefined>) => { + if (!walletFileName) return + + try { + await lockWalletMutateAsync() + toast.success( + t('wallets.wallet_preview.alert_wallet_locked_successfully', { + walletName: walletDisplayName(walletFileName), + }), + ) + setLockWalletDialogContext(undefined) + await doOnLogout(navigate) + } catch (error: unknown) { + const reason = (error instanceof Error ? error.message : undefined) || t('global.errors.reason_unknown') + toast.error(t('global.errors.error_reloading_wallet_failed', { reason })) + console.error('Failed to lock wallet:', error) + } + }, + [doOnLogout, lockWalletMutateAsync, walletFileName], + ) + + const doOnLockWallet = useCallback( + async (navigate: NavigateFunction, t: TFunction<'translation', undefined>) => { + if (!walletFileName) return + + if (makerRunning || coinjoinInProgress) { + setLockWalletDialogContext({ + open: true, + navigate, + t, + }) + } else { + await doOnLockWalletConfirm(navigate, t) + } + }, + [coinjoinInProgress, makerRunning, doOnLockWalletConfirm, walletFileName], + ) + + return { + walletFileName, + doOnLogout, + doOnLockWallet, + doOnLockWalletConfirm, + lockWalletDialogContext, + setLockWalletDialogContext, + isLockingWallet, + makerRunning, + coinjoinInProgress, } +} - const doOnLockWallet = async (navigate: NavigateFunction, t: TFunction<'translation', undefined>) => { - if (!walletFileName) return +const ProtectedNavbarRoute = () => { + const controller = useWalletLockController() - if (makerRunning || coinjoinInProgress) { - setLockWalletDialogContext({ - open: true, - navigate, - t, - }) - } else { - await doOnLockWalletConfirm(navigate, t) - } + if (!controller.walletFileName) { + return } - const doOnLockWalletConfirm = async (navigate: NavigateFunction, t: TFunction<'translation', undefined>) => { - if (!walletFileName) return - try { - await lockWalletMutation.mutateAsync() - toast.success( - t('wallets.wallet_preview.alert_wallet_locked_successfully', { walletName: walletDisplayName(walletFileName) }), - ) - setLockWalletDialogContext(undefined) - await doOnLogout(navigate) - } catch (error: unknown) { - const reason = (error instanceof Error ? error.message : undefined) || t('global.errors.reason_unknown') - toast.error(t('global.errors.error_reloading_wallet_failed', { reason })) - console.error('Failed to lock wallet:', error) - } + return ( + + + + ) +} + +const walletFileNameOrRedirect = (Component: React.FC<{ walletFileName: WalletFileName }>) => { + const Wrapped: React.FC = () => { + const walletFileName = useWalletFileName() + return walletFileName ? : } + return +} - const router = createBrowserRouter( - createRoutesFromElements( - } errorElement={}> - : } /> - : } - /> - : } - /> - {isDeveloperMode && isDebugFeatureEnabled('devSetupPage') && ( - }> - - - } - /> - )} - {isDeveloperMode && isDebugFeatureEnabled('devErrorExamplePage') && ( - }> - - - } - /> - )} - - - - {walletFileName && ( - <> - - - )} - - - - - } - > - }> - } /> - - - - - } - > - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } - /> - {isDeveloperMode && isDebugFeatureEnabled('devPage') && ( - }> - - - } - /> - )} - - - } /> - , - ), +const SettingsRoute = () => { + const walletFileName = useWalletFileName() + const { onLockWallet } = useOutletContext() + + return walletFileName ? ( + + ) : ( + ) +} + +const DevPageRoute = () => { + const walletFileName = useWalletFileName() + return useDeveloperMode().enabled && isDebugFeatureEnabled('devPage') && walletFileName ? ( + }> + + + ) : ( + + ) +} + +const router = createBrowserRouter( + createRoutesFromElements( + } errorElement={}> + } /> + } /> + } /> + } /> + } /> + }> + }> + + + }> + + + + + + + } /> + } /> + } /> + + + } /> + + + } /> + , + ), +) + +function App() { + const walletFileName = useWalletFileName() + const controller = useWalletLockController() + const lockWalletDialogContext = controller.lockWalletDialogContext + return ( @@ -255,18 +311,17 @@ function App() { - {walletFileName && ( - <> - - - )} + + {walletFileName && } {lockWalletDialogContext && ( setLockWalletDialogContext(undefined)} - onConfirm={() => doOnLockWalletConfirm(lockWalletDialogContext?.navigate, lockWalletDialogContext?.t)} - makerRunning={makerRunning} - coinjoinInProgress={coinjoinInProgress} + open={lockWalletDialogContext.open} + onOpenChange={() => controller.setLockWalletDialogContext(undefined)} + onConfirm={() => + controller.doOnLockWalletConfirm(lockWalletDialogContext.navigate, lockWalletDialogContext.t) + } + makerRunning={controller.makerRunning} + coinjoinInProgress={controller.coinjoinInProgress} /> )}