Repository: joinmarket-webui/jam (ts)

Issue:
016 Imported wallet starts rescan.  When I lock the wallet, it says it's "Inactive" which makes me think 
... makes me think it's "lock wallet" stops a rescan:
![image](https://github.com/joinmarket-webui/jam/assets/106188942/1fc652a4-ba56-40a8-8502-c2bd9749f828)

**Is your feature request related to a problem? Please describe.**

Not really, just a display issue.  "Inactive" wallet makes me think its rescan stopped.

**Describe the solution you'd like**

"Inactive" status should be "locked", perhaps.  Or something to indicate it is still rescanning.


Pull request description:
ui: improve phrasing and display rescanning alert
Resolves #682.

- Change phrasing from "active"/"inactive" to "unlocked"/"locked".
- Display "rescanning" alert in `Wallets` and `CreateWallet` component

## :camera_flash: 
<img src="https://github.com/joinmarket-webui/jam/assets/3358649/5625838e-b780-4bf5-96ee-68d22535ef69" width=300 />
<img src="https://github.com/joinmarket-webui/jam/assets/3358649/b14ac319-0c4d-4991-8038-60d8f9a94f98" width=300 />

<img src="https://github.com/joinmarket-webui/jam/assets/3358649/231a7dd4-d03d-4ef6-8a78-076542c4b866" width=300 />


Patch:
diff --git a/src/components/CreateWallet.tsx b/src/components/CreateWallet.tsx
index 5ae21a7..d5471ef 100644
--- a/src/components/CreateWallet.tsx
+++ b/src/components/CreateWallet.tsx
@@ -106,6 +106,12 @@ export default function CreateWallet({ parentRoute, startWallet }: CreateWalletP
   const [alert, setAlert] = useState<SimpleAlert>()
   const [createdWallet, setCreatedWallet] = useState<CreatedWalletWithAuth>()
 
+  const isCreated = useMemo(() => !!createdWallet?.walletFileName && !!createdWallet?.auth, [createdWallet])
+  const canCreate = useMemo(
+    () => !isCreated && !serviceInfo?.walletFileName && !serviceInfo?.rescanning,
+    [isCreated, serviceInfo],
+  )
+
   const createWallet = useCallback(
     async ({ walletName, password }) => {
       setAlert(undefined)
@@ -152,17 +158,33 @@ export default function CreateWallet({ parentRoute, startWallet }: CreateWalletP
         <PageTitle title={t('create_wallet.title')} />
       )}
       {alert && <rb.Alert variant={alert.variant}>{alert.message}</rb.Alert>}
-      {serviceInfo?.walletFileName && !createdWallet ? (
-        <rb.Alert variant="warning">
-          <Trans i18nKey="create_wallet.alert_other_wallet_unlocked">
-            Currently <strong>{{ walletName: walletDisplayName(serviceInfo.walletFileName) }}</strong> is active. You
-            need to lock it first.
-            <Link to={routes.walletList} className="alert-link">
-              Go back
-            </Link>
-            .
-          </Trans>
-        </rb.Alert>
+      {!canCreate && !isCreated ? (
+        <>
+          {serviceInfo?.walletFileName && (
+            <rb.Alert variant="warning">
+              <Trans i18nKey="create_wallet.alert_other_wallet_unlocked">
+                Currently <strong>{{ walletName: walletDisplayName(serviceInfo.walletFileName) }}</strong> is active.
+                You need to lock it first.
+                <Link to={routes.walletList} className="alert-link">
+                  Go back
+                </Link>
+                .
+              </Trans>
+            </rb.Alert>
+          )}
+          {serviceInfo?.rescanning === true && (
+            <rb.Alert variant="warning" data-testid="alert-rescanning">
+              <Trans i18nKey="create_wallet.alert_rescan_in_progress">
+                Rescanning the timechain is currently in progress. Please wait until the process finishes and then try
+                again.
+                <Link to={routes.walletList} className="alert-link">
+                  Go back
+                </Link>
+                .
+              </Trans>
+            </rb.Alert>
+          )}
+        </>
       ) : (
         <>
           <PreventLeavingPageByMistake />
diff --git a/src/components/ImportWallet.tsx b/src/components/ImportWallet.tsx
index 5ae94bb..c103abf 100644
--- a/src/components/ImportWallet.tsx
+++ b/src/components/ImportWallet.tsx
@@ -394,7 +394,7 @@ export default function ImportWallet({ parentRoute, startWallet }: ImportWalletP
   const [importDetailsFormValues, setImportDetailsFormValues] = useState<ImportWalletDetailsFormValues>()
   const [recoveredWallet, setRecoveredWallet] = useState<RecoveredWalletWithAuth>()
 
-  const isRecovered = useMemo(() => !!recoveredWallet?.walletFileName && recoveredWallet?.auth, [recoveredWallet])
+  const isRecovered = useMemo(() => !!recoveredWallet?.walletFileName && !!recoveredWallet?.auth, [recoveredWallet])
   const canRecover = useMemo(
     () => !isRecovered && !serviceInfo?.walletFileName && !serviceInfo?.rescanning,
     [isRecovered, serviceInfo],
@@ -571,7 +571,7 @@ export default function ImportWallet({ parentRoute, startWallet }: ImportWalletP
             </rb.Alert>
           )}
           {serviceInfo?.rescanning === true && (
-            <rb.Alert variant="warning">
+            <rb.Alert variant="warning" data-testid="alert-rescanning">
               <Trans i18nKey="import_wallet.alert_rescan_in_progress">
                 Rescanning the timechain is currently in progress. Please wait until the process finishes and then try
                 again.
diff --git a/src/components/Wallet.tsx b/src/components/Wallet.tsx
index 6cf9e0b..ebfa1f0 100644
--- a/src/components/Wallet.tsx
+++ b/src/components/Wallet.tsx
@@ -190,9 +190,9 @@ export default function Wallet({
               </rb.Card.Title>
 
               {isActive ? (
-                <span className="text-success">{t('wallets.wallet_preview.wallet_active')}</span>
+                <span className="text-success">{t('wallets.wallet_preview.wallet_unlocked')}</span>
               ) : (
-                <span className="text-muted">{t('wallets.wallet_preview.wallet_inactive')}</span>
+                <span className="text-muted">{t('wallets.wallet_preview.wallet_locked')}</span>
               )}
             </div>
 
diff --git a/src/components/Wallets.tsx b/src/components/Wallets.tsx
index 9b4d686..dbead45 100644
--- a/src/components/Wallets.tsx
+++ b/src/components/Wallets.tsx
@@ -210,6 +210,11 @@ export default function Wallets({ currentWallet, startWallet, stopWallet }: Wall
           subtitle={walletList?.length === 0 ? t('wallets.subtitle_no_wallets') : undefined}
           center={true}
         />
+        {serviceInfo?.rescanning === true && (
+          <rb.Alert variant="info" data-testid="alert-rescanning">
+            {t('app.alert_rescan_in_progress')}
+          </rb.Alert>
+        )}
         {alert && <Alert {...alert} />}
         {isLoading ? (
           <div className="d-flex justify-content-center align-items-center">
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json
index b01bc72..423c6dc 100644
--- a/src/i18n/locales/en/translation.json
+++ b/src/i18n/locales/en/translation.json
@@ -105,6 +105,8 @@
       "alert_wallet_already_locked": "{{ walletName }} already locked.",
       "wallet_active": "Active",
       "wallet_inactive": "Inactive",
+      "wallet_locked": "Locked",
+      "wallet_unlocked": "Unlocked",
       "placeholder_password": "Password",
       "button_open": "Open",
       "button_lock": "Lock",
@@ -123,6 +125,7 @@
     "title_wallet_created": "Wallet created successfully!",
     "subtitle_wallet_created": "Please write down your seed phrase and password! Without this information you will not be able to access and recover your wallet!",
     "alert_other_wallet_unlocked": "Currently <1>{{ walletName }}</1> is active. You need to lock it first. <3>Go back</3>.",
+    "alert_rescan_in_progress": "Rescanning the timechain is currently in progress. Please wait until the process finishes and then try again. <1>Go back</1>.",
     "feedback_valid": "Looks good!",
     "label_wallet_name": "Wallet name",
     "placeholder_wallet_name": "Your Wallet...",


Test patch:
diff --git a/src/components/CreateWallet.test.tsx b/src/components/CreateWallet.test.tsx
index 49c0eaf..89d1a45 100644
--- a/src/components/CreateWallet.test.tsx
+++ b/src/components/CreateWallet.test.tsx
@@ -41,6 +41,22 @@ describe('<CreateWallet />', () => {
     ;(apiMock.getSession as jest.Mock).mockResolvedValue(neverResolvingPromise)
   })
 
+  it('should display alert when rescanning is active', async () => {
+    ;(apiMock.getSession as jest.Mock).mockResolvedValueOnce({
+      ok: true,
+      json: () =>
+        Promise.resolve({
+          rescanning: true,
+        }),
+    })
+
+    await act(async () => setup({}))
+
+    expect(screen.getByText('create_wallet.title')).toBeVisible()
+    expect(screen.getByTestId('alert-rescanning')).toBeVisible()
+    expect(screen.queryByText('create_wallet.button_create')).not.toBeInTheDocument()
+  })
+
   it('should render without errors', () => {
     act(() => setup({}))
 
diff --git a/src/components/Wallet.test.tsx b/src/components/Wallet.test.tsx
index 026d787..22d5384 100644
--- a/src/components/Wallet.test.tsx
+++ b/src/components/Wallet.test.tsx
@@ -52,7 +52,7 @@ describe('<Wallet />', () => {
     act(() => setup({ walletFileName: dummyWalletFileName }))
 
     expect(screen.getByText(walletDisplayName(dummyWalletFileName))).toBeInTheDocument()
-    expect(screen.getByText('wallets.wallet_preview.wallet_inactive')).toBeInTheDocument()
+    expect(screen.getByText('wallets.wallet_preview.wallet_locked')).toBeInTheDocument()
     expect(screen.queryByPlaceholderText('wallets.wallet_preview.placeholder_password')).not.toBeInTheDocument()
     expect(screen.queryByText('wallets.wallet_preview.button_unlock')).not.toBeInTheDocument()
     expect(screen.queryByText('wallets.wallet_preview.button_open')).not.toBeInTheDocument()
@@ -62,7 +62,7 @@ describe('<Wallet />', () => {
   it('should unlock inactive wallet successfully', async () => {
     await act(async () => setup({ walletFileName: dummyWalletFileName, unlockWallet: mockUnlockWallet }))
 
-    expect(screen.getByText('wallets.wallet_preview.wallet_inactive')).toBeInTheDocument()
+    expect(screen.getByText('wallets.wallet_preview.wallet_locked')).toBeInTheDocument()
     expect(screen.getByText('wallets.wallet_preview.button_unlock')).toBeInTheDocument()
     expect(screen.getByPlaceholderText('wallets.wallet_preview.placeholder_password')).toBeInTheDocument()
     expect(screen.queryByText('wallets.wallet_preview.button_open')).not.toBeInTheDocument()
@@ -93,7 +93,7 @@ describe('<Wallet />', () => {
     )
 
     expect(screen.getByText(walletDisplayName(dummyWalletFileName))).toBeInTheDocument()
-    expect(screen.getByText('wallets.wallet_preview.wallet_active')).toBeInTheDocument()
+    expect(screen.getByText('wallets.wallet_preview.wallet_unlocked')).toBeInTheDocument()
     expect(screen.queryByPlaceholderText('wallets.wallet_preview.placeholder_password')).toBeInTheDocument()
     expect(screen.queryByText('wallets.wallet_preview.button_unlock')).toBeInTheDocument()
     expect(screen.queryByText('wallets.wallet_preview.button_open')).not.toBeInTheDocument()
@@ -110,7 +110,7 @@ describe('<Wallet />', () => {
     )
 
     expect(screen.getByText(walletDisplayName(dummyWalletFileName))).toBeInTheDocument()
-    expect(screen.getByText('wallets.wallet_preview.wallet_active')).toBeInTheDocument()
+    expect(screen.getByText('wallets.wallet_preview.wallet_unlocked')).toBeInTheDocument()
     expect(screen.getByText('wallets.wallet_preview.button_open')).toBeInTheDocument()
     expect(screen.getByText('wallets.wallet_preview.button_lock')).toBeInTheDocument()
     expect(screen.queryByPlaceholderText('wallets.wallet_preview.placeholder_password')).not.toBeInTheDocument()
@@ -126,7 +126,7 @@ describe('<Wallet />', () => {
       }),
     )
 
-    expect(screen.getByText('wallets.wallet_preview.wallet_active')).toBeInTheDocument()
+    expect(screen.getByText('wallets.wallet_preview.wallet_unlocked')).toBeInTheDocument()
     expect(screen.getByText('wallets.wallet_preview.button_lock')).toBeInTheDocument()
 
     await act(async () => {
diff --git a/src/components/Wallets.test.tsx b/src/components/Wallets.test.tsx
index a12c6ae..18dfbe9 100644
--- a/src/components/Wallets.test.tsx
+++ b/src/components/Wallets.test.tsx
@@ -6,7 +6,6 @@ import user from '@testing-library/user-event'
 import * as apiMock from '../libs/JmWalletApi'
 
 import Wallets from './Wallets'
-import { walletDisplayName } from '../utils'
 import { CurrentWallet } from '../context/WalletContext'
 
 jest.mock('../libs/JmWalletApi', () => ({
@@ -44,7 +43,7 @@ describe('<Wallets />', () => {
     ;(apiMock.getGetinfo as jest.Mock).mockResolvedValue(neverResolvingPromise)
   })
 
-  it('should render without errors', () => {
+  it('should display loading indicator while fetching data', () => {
     const neverResolvingPromise = new Promise(() => {})
     ;(apiMock.getSession as jest.Mock).mockResolvedValueOnce(neverResolvingPromise)
     ;(apiMock.getWalletAll as jest.Mock).mockResolvedValueOnce(neverResolvingPromise)
@@ -75,6 +74,24 @@ describe('<Wallets />', () => {
     expect(screen.getByText('wallets.button_new_wallet')).toBeInTheDocument()
   })
 
+  it('should display alert when rescanning is active', async () => {
+    ;(apiMock.getWalletAll as jest.Mock).mockResolvedValueOnce({
+      ok: false,
+    })
+    ;(apiMock.getSession as jest.Mock).mockResolvedValueOnce({
+      ok: true,
+      json: () =>
+        Promise.resolve({
+          rescanning: true,
+        }),
+    })
+
+    await act(async () => setup({}))
+
+    expect(screen.getByText('wallets.title')).toBeVisible()
+    expect(screen.getByTestId('alert-rescanning')).toBeVisible()
+  })
+
   it('should display big call-to-action buttons if no wallet has been created yet', async () => {
     ;(apiMock.getSession as jest.Mock).mockResolvedValueOnce({
       ok: true,
@@ -214,7 +231,7 @@ describe('<Wallets />', () => {
 
       await act(async () => setup({}))
 
-      expect(screen.getByText('wallets.wallet_preview.wallet_inactive')).toBeInTheDocument()
+      expect(screen.getByText('wallets.wallet_preview.wallet_locked')).toBeInTheDocument()
       expect(screen.getByText('wallets.wallet_preview.button_unlock')).toBeInTheDocument()
       expect(screen.getByPlaceholderText('wallets.wallet_preview.placeholder_password')).toBeInTheDocument()
 
@@ -261,7 +278,7 @@ describe('<Wallets />', () => {
 
       await act(async () => setup({}))
 
-      expect(screen.getByText('wallets.wallet_preview.wallet_inactive')).toBeInTheDocument()
+      expect(screen.getByText('wallets.wallet_preview.wallet_locked')).toBeInTheDocument()
       expect(screen.getByText('wallets.wallet_preview.button_unlock')).toBeInTheDocument()
       expect(screen.getByPlaceholderText('wallets.wallet_preview.placeholder_password')).toBeInTheDocument()
 
@@ -313,7 +330,7 @@ describe('<Wallets />', () => {
         }),
       )
 
-      expect(screen.getByText('wallets.wallet_preview.wallet_active')).toBeInTheDocument()
+      expect(screen.getByText('wallets.wallet_preview.wallet_unlocked')).toBeInTheDocument()
       expect(screen.getByText('wallets.wallet_preview.button_lock')).toBeInTheDocument()
 
       await act(async () => {
@@ -361,7 +378,7 @@ describe('<Wallets />', () => {
         }),
       )
 
-      expect(screen.getByText('wallets.wallet_preview.wallet_active')).toBeInTheDocument()
+      expect(screen.getByText('wallets.wallet_preview.wallet_unlocked')).toBeInTheDocument()
       expect(screen.getByText('wallets.wallet_preview.button_lock')).toBeInTheDocument()
 
       await act(async () => {