-
Notifications
You must be signed in to change notification settings - Fork 84
Add lock wallet confirmation modal and settings toggle #1009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import type { PropsWithChildren } from 'react' | ||
| import { useState, type PropsWithChildren } from 'react' | ||
| import type { SessionResponse } from '@joinmarket-webui/joinmarket-api-ts/jm' | ||
| import type { TFunction } from 'i18next' | ||
| import { | ||
|
|
@@ -12,7 +12,9 @@ import { | |
| } 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' | ||
|
|
@@ -21,6 +23,7 @@ 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' | ||
|
|
||
| const WithActivityIndicator = ({ active, children }: PropsWithChildren<{ active: boolean }>) => { | ||
|
|
@@ -137,6 +140,9 @@ 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 | ||
|
|
@@ -155,6 +161,24 @@ export function AppNavbar({ | |
|
|
||
| const rescanningRoute = rescanInfo?.rescanning !== true ? undefined : routes.rescan | ||
|
|
||
| const handleLockWallet = async () => { | ||
| try { | ||
| setIsLockingWallet(true) | ||
| await onLockWallet(navigate, t) | ||
| } finally { | ||
| setIsLockingWallet(false) | ||
| setShowLockWalletDialog(false) | ||
| } | ||
| } | ||
|
|
||
| const onClickLockWallet = () => { | ||
| if (showLockWalletConfirmation) { | ||
| setShowLockWalletDialog(true) | ||
| } else { | ||
| handleLockWallet() | ||
| } | ||
| } | ||
|
Comment on lines
+164
to
+180
|
||
|
|
||
| return ( | ||
| <header className="light:bg-gray-100 light:text-black flex items-center justify-between bg-[#23262b] px-4 py-2 text-white transition-colors duration-300"> | ||
| <WalletPreview | ||
|
|
@@ -228,7 +252,7 @@ export function AppNavbar({ | |
| className="hidden sm:flex" | ||
| variant="ghost-navbar" | ||
| size="icon" | ||
| onClick={async () => await onLockWallet(navigate, t)} | ||
| onClick={onClickLockWallet} | ||
| aria-label={t('settings.button_lock_wallet')} | ||
| title={t('settings.button_lock_wallet')} | ||
| > | ||
|
|
@@ -246,6 +270,12 @@ export function AppNavbar({ | |
| </Button> | ||
| {sidebarTrigger} | ||
| </div> | ||
| <LockWalletConfirmDialog | ||
| open={showLockWalletDialog} | ||
| onOpenChange={setShowLockWalletDialog} | ||
| onConfirm={handleLockWallet} | ||
| isLocking={isLockingWallet} | ||
| /> | ||
| </header> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import type { ComponentProps } from 'react' | ||
| import { AlertTriangleIcon } from 'lucide-react' | ||
| import { useTranslation } from 'react-i18next' | ||
| import { useStore } from 'zustand' | ||
| import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' | ||
| import { Button } from '@/components/ui/button' | ||
| import { | ||
| Dialog, | ||
| DialogContent, | ||
| DialogDescription, | ||
| DialogFooter, | ||
| DialogHeader, | ||
| DialogTitle, | ||
| } from '@/components/ui/dialog' | ||
| import { jmSessionStore } from '@/store/jmSessionStore' | ||
| import type { WithRequiredProperty } from '@/types/global' | ||
|
|
||
| type LockWalletConfirmDialogProps = WithRequiredProperty< | ||
| Omit<ComponentProps<typeof Dialog>, 'children'>, | ||
| 'open' | 'onOpenChange' | ||
| > & { | ||
| onConfirm: () => void | ||
| isLocking?: boolean | ||
| } | ||
|
|
||
| export const LockWalletConfirmDialog = ({ | ||
| open, | ||
| onOpenChange, | ||
| onConfirm, | ||
| isLocking = false, | ||
| }: LockWalletConfirmDialogProps) => { | ||
| const { t } = useTranslation() | ||
| const session = useStore(jmSessionStore, (state) => state.state) | ||
|
|
||
| const makerRunning = session?.maker_running === true | ||
| const coinjoinInProgress = session?.coinjoin_in_process === true | ||
|
|
||
| return ( | ||
| <Dialog open={open} onOpenChange={onOpenChange}> | ||
| <DialogContent className="max-w-md"> | ||
| <DialogHeader> | ||
| <DialogTitle>{t('wallets.wallet_preview.modal_lock_wallet_title')}</DialogTitle> | ||
| <DialogDescription> | ||
| {t('wallets.wallet_preview.modal_lock_wallet_alternative_action_text')} | ||
| </DialogDescription> | ||
| </DialogHeader> | ||
| <div className="flex flex-col gap-3"> | ||
| {makerRunning && ( | ||
| <Alert variant="warning"> | ||
| <AlertTriangleIcon className="size-4" /> | ||
| <AlertTitle>{t('wallets.wallet_preview.modal_lock_wallet_title')}</AlertTitle> | ||
| <AlertDescription>{t('wallets.wallet_preview.modal_lock_wallet_maker_running_text')}</AlertDescription> | ||
| </Alert> | ||
| )} | ||
| {coinjoinInProgress && ( | ||
| <Alert variant="warning"> | ||
| <AlertTriangleIcon className="size-4" /> | ||
| <AlertTitle>{t('wallets.wallet_preview.modal_lock_wallet_title')}</AlertTitle> | ||
| <AlertDescription> | ||
| {t('wallets.wallet_preview.modal_lock_wallet_coinjoin_in_progress_text')} | ||
| </AlertDescription> | ||
| </Alert> | ||
| )} | ||
| </div> | ||
| <DialogFooter> | ||
| <Button variant="outline" onClick={() => onOpenChange(false)} disabled={isLocking}> | ||
| {t('global.cancel')} | ||
| </Button> | ||
| <Button variant="destructive" onClick={onConfirm} disabled={isLocking}> | ||
| {isLocking ? t('wallets.wallet_preview.button_locking') : t('wallets.wallet_preview.button_lock')} | ||
| </Button> | ||
| </DialogFooter> | ||
| </DialogContent> | ||
| </Dialog> | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
onClickLockWalletfunction should be async and should await the call tohandleLockWallet()on line 178 to ensure proper error handling. Currently, ifhandleLockWallet()fails when the confirmation is disabled, the error would be silently ignored. The SettingsPage implementation correctly usesawaitfor this call.