Skip to content
Merged
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
42 changes: 41 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { lazy, Suspense, useEffect, useMemo } from 'react'
import { lazy, Suspense, useEffect, useMemo, useState } from 'react'
import type { PropsWithChildren } from 'react'
import { lockwalletOptions } from '@joinmarket-webui/joinmarket-api-ts/@tanstack/react-query'
import { token } from '@joinmarket-webui/joinmarket-api-ts/jm'
Expand Down Expand Up @@ -44,9 +44,11 @@ import { queryClient } from '@/lib/queryClient'
import { setIntervalDebounced, walletDisplayName, type WalletFileName } from '@/lib/utils'
import { authStore } from '@/store/authStore'
import { jamSettingsStore } from '@/store/jamSettingsStore'
import { LockWalletConfirmDialog } from './components/ui/jam/LockWalletConfirmDialog'
import { Spinner } from './components/ui/spinner'
import { WalletJarsDetailsPage } from './components/wallet/WalletJarsDetailsPage'
import { JamSessionInfoContextProvider } from './context/JamSessionInfoContextProvider'
import { jmSessionStore } from './store/jmSessionStore'

const DevSetupPage = lazy(() => import('@/components/dev/DevSetupPage'))
const DevPage = lazy(() => import('@/components/dev/DevPage'))
Expand All @@ -56,11 +58,23 @@ const ProtectedRoute = ({ authenticated, children }: PropsWithChildren<{ authent
return authenticated ? <>{children}</> : <Navigate to={routes.login} replace />
}

type LockWalletDialogContext = {
open: true // always true - otherwise object is `undefined`
navigate: NavigateFunction
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 authenticated = useMemo(() => walletFileName !== undefined && hasAuthToken, [walletFileName, hasAuthToken])
const { clear: clearAuth } = useStore(authStore, (state) => state)

const session = useStore(jmSessionStore, (state) => state.state)

const makerRunning = session?.maker_running === true
const coinjoinInProgress = session?.coinjoin_in_process === true || (session?.schedule?.length || 0) > 0

const client = useApiClient()

const lockWalletQuery = useQuery(
Expand All @@ -75,6 +89,7 @@ function App() {
},
queryClient,
)
const [lockWalletDialogContext, setLockWalletDialogContext] = useState<LockWalletDialogContext>()

const doOnLogout = async (navigate: NavigateFunction) => {
clearAuth()
Expand All @@ -84,11 +99,26 @@ function App() {

const doOnLockWallet = async (navigate: NavigateFunction, t: TFunction<'translation', undefined>) => {
if (!walletFileName) return

if (makerRunning || coinjoinInProgress) {
setLockWalletDialogContext({
open: true,
navigate,
t,
})
} else {
await doOnLockWalletConfirm(navigate, t)
}
}

const doOnLockWalletConfirm = async (navigate: NavigateFunction, t: TFunction<'translation', undefined>) => {
if (!walletFileName) return
try {
await lockWalletQuery.refetch()
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')
Expand Down Expand Up @@ -190,6 +220,16 @@ function App() {
<RefreshApiToken />
<RefreshJmSession />
{walletFileName && <LoadFeeConfigData walletFileName={walletFileName} />}
{lockWalletDialogContext && (
<LockWalletConfirmDialog
open={lockWalletDialogContext?.open}
onOpenChange={() => setLockWalletDialogContext(undefined)}
onConfirm={() => doOnLockWalletConfirm(lockWalletDialogContext?.navigate, lockWalletDialogContext?.t)}
isLocking={lockWalletQuery.isFetching}
makerRunning={makerRunning}
coinjoinInProgress={coinjoinInProgress}
/>
)}
<RouterProvider router={router} />
<Toaster closeButton />
</QueryClientProvider>
Expand Down
27 changes: 4 additions & 23 deletions src/components/layout/AppNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import type { TFunction } from 'i18next'
import { LockKeyholeIcon, LogOutIcon, PackageSearchIcon, SettingsIcon, ShuffleIcon, WalletIcon } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Link, useNavigate, type NavigateFunction } from 'react-router-dom'
import { useStore } from 'zustand'
import { DevBadge } from '@/components/dev/DevBadge'
import { LockWalletConfirmDialog } from '@/components/settings/LockWalletConfirmDialog'
import { Button } from '@/components/ui/button'
import { ThemeToggleButton } from '@/components/ui/jam/ThemeToggleButton'
import { Skeleton } from '@/components/ui/skeleton'
Expand All @@ -15,7 +13,6 @@ import { isDevMode } from '@/constants/debugFeatures'
import { routes } from '@/constants/routes'
import type { RescanInfo } from '@/context/JamSessionInfoContext'
import { cn, shortenStringMiddle } from '@/lib/utils'
import { jamSettingsStore } from '@/store/jamSettingsStore'
import type { AmountSats } from '@/types/global'
import { Spinner } from '../ui/spinner'

Expand Down Expand Up @@ -133,9 +130,6 @@ export function AppNavbar({
}: AppNavbarProps) {
const { t } = useTranslation()
const navigate = useNavigate()
const showLockWalletConfirmation = useStore(jamSettingsStore, (state) => state.state.showLockWalletConfirmation)
const [showLockWalletDialog, setShowLockWalletDialog] = useState(false)
const [isLockingWallet, setIsLockingWallet] = useState(false)

const isSidebarOpen =
sidebarInfo === undefined ? false : sidebarInfo.isMobile ? sidebarInfo.openMobile : sidebarInfo.open
Expand All @@ -154,21 +148,13 @@ export function AppNavbar({

const rescanningRoute = rescanInfo?.rescanning !== true ? undefined : routes.rescan

const handleLockWallet = async () => {
const [isLockingWallet, setIsLockingWallet] = useState(false)
const doOnLockWallet = async () => {
try {
setIsLockingWallet(true)
await onLockWallet(navigate, t)
} finally {
setIsLockingWallet(false)
setShowLockWalletDialog(false)
}
}

const onClickLockWallet = () => {
if (showLockWalletConfirmation) {
setShowLockWalletDialog(true)
} else {
handleLockWallet()
}
}

Expand Down Expand Up @@ -245,9 +231,10 @@ export function AppNavbar({
className="hidden sm:flex"
variant="ghost-navbar"
size="icon"
onClick={onClickLockWallet}
onClick={doOnLockWallet}
aria-label={t('settings.button_lock_wallet')}
title={t('settings.button_lock_wallet')}
disabled={isLockingWallet}
>
<LockKeyholeIcon />
</Button>
Expand All @@ -263,12 +250,6 @@ export function AppNavbar({
</Button>
{sidebarTrigger}
</div>
<LockWalletConfirmDialog
open={showLockWalletDialog}
onOpenChange={setShowLockWalletDialog}
onConfirm={handleLockWallet}
isLocking={isLockingWallet}
/>
</header>
)
}
76 changes: 0 additions & 76 deletions src/components/settings/LockWalletConfirmDialog.tsx

This file was deleted.

32 changes: 4 additions & 28 deletions src/components/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
ArrowLeftRightIcon,
LockKeyholeIcon,
BookKeyIcon,
ShieldAlertIcon,
} from 'lucide-react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
Expand All @@ -35,7 +34,6 @@ import { jamSettingsStore } from '@/store/jamSettingsStore'
import { AccountXpubsDialog } from './AccountXpubsDialog'
import { FeeLimitDialog } from './FeeLimitDialog'
import { LanguageSelector } from './LanguageSelector'
import { LockWalletConfirmDialog } from './LockWalletConfirmDialog'
import { SeedPhraseDialog } from './SeedPhraseDialog'
import { SettingItem, SettingsLink, SettingSwitch } from './SettingsItem'

Expand All @@ -54,26 +52,16 @@ export const SettingsPage = ({ walletFileName, onLockWallet }: SettingPageProps)
const [showSeedDialog, setShowSeedDialog] = useState(false)
const [showXpubsDialog, setShowXpubsDialog] = useState(false)
const [showFeeLimitDialog, setShowFeeLimitDialog] = useState(false)
const [showLockWalletDialog, setShowLockWalletDialog] = useState(false)
const hashedPassword = useStore(authStore, (state) => state.state?.hashed_password)
const { isLogsEnabled } = useFeatures()
const [isLockingWallet, setIsLockingWallet] = useState(false)

const handleLockWallet = async () => {
const [isLockingWallet, setIsLockingWallet] = useState(false)
const doOnLockWallet = async () => {
try {
setIsLockingWallet(true)
await onLockWallet(navigate, t)
} finally {
setIsLockingWallet(false)
setShowLockWalletDialog(false)
}
}

const onClickLockWallet = async () => {
if (jamSettings.state.showLockWalletConfirmation) {
setShowLockWalletDialog(true)
} else {
await handleLockWallet()
}
}

Expand Down Expand Up @@ -156,17 +144,10 @@ export const SettingsPage = ({ walletFileName, onLockWallet }: SettingPageProps)
<SettingItem
icon={LockKeyholeIcon}
title={t('settings.button_lock_wallet')}
action={onClickLockWallet}
action={doOnLockWallet}
disabled={isLockingWallet}
/>
<Separator className="opacity-50" />
<SettingSwitch
icon={ShieldAlertIcon}
title={t('settings.show_lock_confirmation')}
checked={jamSettings.state.showLockWalletConfirmation}
onCheckedChange={(checked) => jamSettings.update({ showLockWalletConfirmation: checked })}
/>
<Separator className="opacity-50" />
<SettingsLink icon={ArrowLeftRightIcon} title={t('settings.button_switch_wallet')} to={routes.switchWallet} />
<Separator className="opacity-50" />
<SettingsLink icon={PackageSearchIcon} title={t('settings.rescan_chain')} to={routes.rescan} />
Expand Down Expand Up @@ -274,12 +255,7 @@ export const SettingsPage = ({ walletFileName, onLockWallet }: SettingPageProps)
)}

<FeeLimitDialog walletFileName={walletFileName} open={showFeeLimitDialog} onOpenChange={setShowFeeLimitDialog} />
<LockWalletConfirmDialog
open={showLockWalletDialog}
onOpenChange={setShowLockWalletDialog}
onConfirm={handleLockWallet}
isLocking={isLockingWallet}
/>

{hashedPassword && (
<>
<SeedPhraseDialog
Expand Down
69 changes: 69 additions & 0 deletions src/components/ui/jam/LockWalletConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { type ComponentProps } from 'react'
import { AlertTriangleIcon } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import type { WithRequiredProperty } from '@/types/global'
import { Spinner } from '../spinner'

type LockWalletConfirmDialogProps = WithRequiredProperty<
Omit<ComponentProps<typeof Dialog>, 'children'>,
'open' | 'onOpenChange'
> & {
onConfirm: () => Promise<void>
isLocking: boolean
makerRunning: boolean
coinjoinInProgress: boolean
}

export const LockWalletConfirmDialog = ({
open,
onOpenChange,
onConfirm,
isLocking,
makerRunning,
coinjoinInProgress,
}: LockWalletConfirmDialogProps) => {
const { t } = useTranslation()

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>{t('wallets.wallet_preview.modal_lock_wallet_title')}</DialogTitle>
</DialogHeader>
{makerRunning && (
<Alert variant="warning">
<AlertTriangleIcon />
<AlertDescription>{t('wallets.wallet_preview.modal_lock_wallet_maker_running_text')}</AlertDescription>
</Alert>
)}
{coinjoinInProgress && (
<Alert variant="warning">
<AlertTriangleIcon />
<AlertDescription>
{t('wallets.wallet_preview.modal_lock_wallet_coinjoin_in_progress_text')}
</AlertDescription>
</Alert>
)}
<p className="text-muted-foreground">{t('wallets.wallet_preview.modal_lock_wallet_alternative_action_text')}</p>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isLocking}>
{t('global.cancel')}
</Button>
<Button variant="default" onClick={onConfirm} disabled={isLocking}>
{isLocking ? (
<>
<Spinner className="motion-reduce:hidden" />
{t('wallets.wallet_preview.button_locking')}
</>
) : (
t('wallets.wallet_preview.button_lock')
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Loading
Loading