From b9552b933122cccb36bff3f1e2b502b00b927f9d Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Mon, 18 Mar 2024 16:57:51 -0500 Subject: [PATCH] fix: header alignment and request pw --- src/app/common/hooks/use-interval.ts | 26 +++++++++++++ src/app/common/hooks/use-waiting-message.ts | 39 +++++++++++++++++++ src/app/components/request-password.tsx | 26 ++++++++++--- .../increase-fee-button.tsx | 10 ++++- .../features/container/utils/route-helpers.ts | 8 ++++ .../increase-fee-dialog.tsx | 2 +- .../containers/container.layout.tsx | 2 +- .../components/containers/headers/header.tsx | 13 +++++-- 8 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 src/app/common/hooks/use-interval.ts create mode 100644 src/app/common/hooks/use-waiting-message.ts diff --git a/src/app/common/hooks/use-interval.ts b/src/app/common/hooks/use-interval.ts new file mode 100644 index 00000000000..84b9d19f9ab --- /dev/null +++ b/src/app/common/hooks/use-interval.ts @@ -0,0 +1,26 @@ +import { useEffect, useRef } from 'react'; + +import { noop } from '@shared/utils'; + +export function useInterval(callback: () => void, delay: number | null) { + const savedCallback = useRef(noop); + + // Remember the latest callback. + useEffect(() => { + if (savedCallback) { + savedCallback.current = callback; + } + }, [callback]); + + // Set up the interval. + useEffect(() => { + function tick() { + savedCallback.current(); + } + if (delay !== null) { + const id = setInterval(tick, delay); + return () => clearInterval(id); + } + return; + }, [delay]); +} diff --git a/src/app/common/hooks/use-waiting-message.ts b/src/app/common/hooks/use-waiting-message.ts new file mode 100644 index 00000000000..6ac9701c667 --- /dev/null +++ b/src/app/common/hooks/use-waiting-message.ts @@ -0,0 +1,39 @@ +import { useMemo, useRef, useState } from 'react'; + +import { useInterval } from './use-interval'; + +// Keys are the seconds to wait before showing the message +export type WaitingMessages = Record; + +function messageForSecondsPassed(waitingMessages: WaitingMessages, seconds: number) { + return waitingMessages[seconds as keyof typeof waitingMessages]; +} + +export const useWaitingMessage = ( + waitingMessages: WaitingMessages, + { waitingMessageInterval } = { + waitingMessageInterval: 1000, + } +): [boolean, string, () => void, () => void] => { + const [isRunning, setIsRunning] = useState(false); + const [waitingMessage, setWaitingMessage] = useState(messageForSecondsPassed(waitingMessages, 0)); + const handlers = useMemo( + () => ({ + startWaitingMessage: () => setIsRunning(true), + stopWaitingMessage: () => setIsRunning(false), + }), + [] + ); + const secondsPassed = useRef(0); + + useInterval( + () => { + secondsPassed.current += waitingMessageInterval / 1000; + const newMessage = messageForSecondsPassed(waitingMessages, secondsPassed.current); + if (newMessage) setWaitingMessage(newMessage); + }, + isRunning ? waitingMessageInterval : null + ); + + return [isRunning, waitingMessage, handlers.startWaitingMessage, handlers.stopWaitingMessage]; +}; diff --git a/src/app/components/request-password.tsx b/src/app/components/request-password.tsx index e01c52a10a0..f1a3f30a70b 100644 --- a/src/app/components/request-password.tsx +++ b/src/app/components/request-password.tsx @@ -6,6 +6,7 @@ import { Box, Stack, styled } from 'leather-styles/jsx'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useKeyActions } from '@app/common/hooks/use-key-actions'; import { buildEnterKeyEvent } from '@app/common/hooks/use-modifier-key'; +import { WaitingMessages, useWaitingMessage } from '@app/common/hooks/use-waiting-message'; import { Button } from '@app/ui/components/button/button'; import { Footer } from '@app/ui/components/containers/footers/footer'; import { Logo } from '@app/ui/components/logo'; @@ -14,6 +15,15 @@ import { Page } from '@app/ui/layout/page/page.layout'; import { ErrorLabel } from './error-label'; +const waitingMessages: WaitingMessages = { + '2': 'Verifying password…', + '10': 'Still working…', + '20': 'Almost there', +}; + +const caption = + 'Your password is used to secure your Secret Key and is only used locally on your device.'; + interface RequestPasswordProps { onSuccess(): void; } @@ -22,9 +32,13 @@ export function RequestPassword({ onSuccess }: RequestPasswordProps) { const [error, setError] = useState(''); const { unlockWallet } = useKeyActions(); const analytics = useAnalytics(); + const [isRunning, waitingMessage, startWaitingMessage, stopWaitingMessage] = + useWaitingMessage(waitingMessages); + const submit = useCallback(async () => { const startUnlockTimeMs = performance.now(); void analytics.track('start_unlock'); + startWaitingMessage(); setError(''); try { await unlockWallet(password); @@ -32,11 +46,12 @@ export function RequestPassword({ onSuccess }: RequestPasswordProps) { } catch (error) { setError('The password you entered is invalid'); } + stopWaitingMessage(); const unlockSuccessTimeMs = performance.now(); void analytics.track('complete_unlock', { durationMs: unlockSuccessTimeMs - startUnlockTimeMs, }); - }, [analytics, unlockWallet, password, onSuccess]); + }, [analytics, startWaitingMessage, stopWaitingMessage, unlockWallet, password, onSuccess]); return ( @@ -52,7 +67,8 @@ export function RequestPassword({ onSuccess }: RequestPasswordProps) {
} > - + Enter your password - - Your password is used to secure your Secret Key and is only used locally on your device. - + {(isRunning && waitingMessage) || caption} { onIncreaseFee(); @@ -32,7 +33,12 @@ export function IncreaseFeeButton(props: IncreaseFeeButtonProps) { > - Increase fee + + {whenPageMode({ + popup: 'Fee', + full: 'Increase fee', + })} + ); diff --git a/src/app/features/container/utils/route-helpers.ts b/src/app/features/container/utils/route-helpers.ts index bb757664c0b..b1863db6ed3 100644 --- a/src/app/features/container/utils/route-helpers.ts +++ b/src/app/features/container/utils/route-helpers.ts @@ -24,7 +24,15 @@ function isOnboardingPage(pathname: RouteUrls) { ); } +function isFundPage(pathname: RouteUrls) { + return ( + pathname === RouteUrls.Fund.replace(':currency', 'STX') || + pathname === RouteUrls.Fund.replace(':currency', 'BTC') + ); +} + export function getPageVariant(pathname: RouteUrls) { + if (isFundPage(pathname)) return 'fund'; if (isHomePage(pathname)) return 'home'; if (isOnboardingPage(pathname)) return 'onboarding'; if (isSummaryPage(pathname)) return 'summary'; diff --git a/src/app/features/dialogs/increase-fee-dialog/increase-fee-dialog.tsx b/src/app/features/dialogs/increase-fee-dialog/increase-fee-dialog.tsx index 09c18db32bb..5eb27ca492e 100644 --- a/src/app/features/dialogs/increase-fee-dialog/increase-fee-dialog.tsx +++ b/src/app/features/dialogs/increase-fee-dialog/increase-fee-dialog.tsx @@ -19,7 +19,7 @@ export function IncreaseFeeDialog({ feeForm, onClose, isShowing }: IncreaseFeeDi } + header={
} > diff --git a/src/app/ui/components/containers/headers/header.tsx b/src/app/ui/components/containers/headers/header.tsx index 67b6b1726e9..c6a922b87c1 100644 --- a/src/app/ui/components/containers/headers/header.tsx +++ b/src/app/ui/components/containers/headers/header.tsx @@ -9,7 +9,7 @@ import { IconButton } from '../../icon-button/icon-button'; import { BigTitleHeader } from './components/big-title-header'; import { HeaderActionButton } from './components/header-action-button'; -type HeaderVariants = 'page' | 'home' | 'onboarding' | 'dialog' | 'bigTitle' | 'summary'; +type HeaderVariants = 'page' | 'home' | 'onboarding' | 'dialog' | 'bigTitle' | 'summary' | 'fund'; export interface HeaderProps { variant: HeaderVariants; @@ -43,16 +43,21 @@ export function Header({ return (