From 7bab28c1be14c028fd33eadca25abe0f2e953516 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Fri, 13 Feb 2026 17:59:17 +0100 Subject: [PATCH] refactor: externalize LoginCard --- src/components/login/LoginCard.tsx | 127 ++++++++++++++++++ src/components/login/LoginForm.tsx | 2 - src/components/login/LoginPage.tsx | 104 +++----------- .../jam/pages/login/LoginCard.stories.tsx | 69 ++++++++++ 4 files changed, 211 insertions(+), 91 deletions(-) create mode 100644 src/components/login/LoginCard.tsx create mode 100644 src/stories/jam/pages/login/LoginCard.stories.tsx diff --git a/src/components/login/LoginCard.tsx b/src/components/login/LoginCard.tsx new file mode 100644 index 00000000..d6f5cec2 --- /dev/null +++ b/src/components/login/LoginCard.tsx @@ -0,0 +1,127 @@ +import type { ComponentProps } from 'react' +import type { ErrorMessage } from '@joinmarket-webui/joinmarket-api-ts/jm' +import { AlertCircleIcon, RefreshCwIcon, WalletIcon } from 'lucide-react' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Skeleton } from '@/components/ui/skeleton' +import { Spinner } from '@/components/ui/spinner' +import { routes } from '@/constants/routes' +import { cn } from '@/lib/utils' +import { LoginForm } from './LoginForm' + +type LoginFormProps = ComponentProps +type LoginCardProps = Omit & + Required> & { + isSubmitting: boolean + listWalletsFetching: boolean + listWalletsLoading: boolean + listWalletsError?: ErrorMessage + onReloadClick: () => Promise + } + +export const LoginCard = ({ + wallets, + activeWallet, + makerRunning, + coinjoinInProgress, + onSubmit, + isSubmitting, + listWalletsLoading, + listWalletsFetching, + listWalletsError, + onReloadClick, +}: LoginCardProps) => { + const { t } = useTranslation() + const navigate = useNavigate() + + return ( + + +
+ {listWalletsFetching ? ( + + ) : ( + void onReloadClick()} /> + )} +
+ {/*TODO: i18n */}Welcome to Jam + {listWalletsLoading ? ( + <> + + + ) : wallets && wallets.length > 0 ? ( + {/*TODO: i18n */}Select a wallet and enter your password to continue. + ) : undefined} +
+ + + {listWalletsError ? ( + <> + + + {t('wallets.error_loading_failed')} + {listWalletsError.message || t('global.errors.reason_unknown')} + + + + ) : ( + <> + {wallets === undefined || listWalletsLoading ? ( + <> + +
+
 
+
 
+
+ + ) : ( + <> + {wallets && wallets.length === 0 ? ( +
+

{t('wallets.subtitle_no_wallets')}

+
+ ) : ( + + )} + +
+ + +
+ + )} + + )} +
+
+ ) +} diff --git a/src/components/login/LoginForm.tsx b/src/components/login/LoginForm.tsx index 06b1eeb2..1d3d4b4e 100644 --- a/src/components/login/LoginForm.tsx +++ b/src/components/login/LoginForm.tsx @@ -184,8 +184,6 @@ type LoginFormLoadingProps = { } type LoginFormProps = - | LoginFormLoadingProps - | LoginFormComponentProps | (LoginFormLoadingProps & Partial) | ({ loading?: false } & LoginFormComponentProps) diff --git a/src/components/login/LoginPage.tsx b/src/components/login/LoginPage.tsx index 425797c7..7d1fad8c 100644 --- a/src/components/login/LoginPage.tsx +++ b/src/components/login/LoginPage.tsx @@ -1,25 +1,18 @@ import { listwalletsOptions, unlockwalletMutation } from '@joinmarket-webui/joinmarket-api-ts/@tanstack/react-query' import { useMutation, useQuery } from '@tanstack/react-query' -import { AlertCircleIcon, RefreshCwIcon, WalletIcon } from 'lucide-react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { toast } from 'sonner' import { useStore } from 'zustand' -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' -import { Badge } from '@/components/ui/badge' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Skeleton } from '@/components/ui/skeleton' -import { Spinner } from '@/components/ui/spinner' import { routes } from '@/constants/routes' import { useApiClient } from '@/hooks/useApiClient' import { hashPassword } from '@/lib/hash' import { withQueryDelay } from '@/lib/queryClient' -import { cn, sortWallets } from '@/lib/utils' +import { sortWallets } from '@/lib/utils' import type { WalletFileName } from '@/lib/utils' import { authStore, type AuthState } from '@/store/authStore' import { jmSessionStore } from '@/store/jmSessionStore' -import { LoginForm } from './LoginForm' +import { LoginCard } from './LoginCard' interface LoginFormData { walletFileName: WalletFileName @@ -107,86 +100,19 @@ const LoginPage = () => { return (
- - -
- {listWalletsFetching ? ( - - ) : ( - void listWalletsRefetch()} /> - )} -
- {/*TODO: i18n */}Welcome to Jam - {listWalletsLoading ? ( - <> - - - ) : wallets.length > 0 ? ( - {/*TODO: i18n */}Select a wallet and enter your password to continue. - ) : undefined} -
- - - {listWalletsError ? ( - <> - - - {t('wallets.error_loading_failed')} - {listWalletsError.message || t('global.errors.reason_unknown')} - - - - ) : ( - <> - {listWalletsLoading ? ( - <> - -
-
 
-
 
-
- - ) : ( - <> - {wallets.length === 0 ? ( -
-

{t('wallets.subtitle_no_wallets')}

-
- ) : ( - - )} - -
- - -
- - )} - - )} -
-
+ void (await listWalletsRefetch())} + />
) } diff --git a/src/stories/jam/pages/login/LoginCard.stories.tsx b/src/stories/jam/pages/login/LoginCard.stories.tsx new file mode 100644 index 00000000..be1b2876 --- /dev/null +++ b/src/stories/jam/pages/login/LoginCard.stories.tsx @@ -0,0 +1,69 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { LoginCard } from '@/components/login/LoginCard' +import type { WalletFileName } from '@/lib/utils' + +const meta: Meta = { + title: 'Page/Login/LoginCard', + component: LoginCard, + tags: ['autodocs'], +} +export default meta + +type Story = StoryObj + +const WALLETS = [ + 'Satoshi.jmdat', + 'Wallet With Whitespaces.jmdat', + 'a.jmdat', + 'A very long wallet name that should be shortened but show start and end of name.jmdat', +] as WalletFileName[] + +export const Default: Story = { + args: { + wallets: WALLETS, + activeWallet: undefined, + makerRunning: false, + coinjoinInProgress: false, + disabled: false, + onSubmit: async () => alert('Submit clicked!'), + }, + argTypes: { + activeWallet: { + control: 'select', + options: [...WALLETS, 'None'], + }, + }, +} + +export const Empty: Story = { + args: { + wallets: [], + activeWallet: undefined, + makerRunning: false, + coinjoinInProgress: false, + disabled: false, + onSubmit: async () => alert('Submit clicked!'), + }, +} + +export const Loading: Story = { + args: { + listWalletsLoading: true, + }, +} +export const Fetching: Story = { + args: { + listWalletsFetching: true, + wallets: WALLETS, + }, +} + +export const Error: Story = { + args: { + listWalletsError: { + message: 'Wallet loading failed.', + error_description: '500 Server Error', + }, + onReloadClick: async () => alert('Reload clicked!'), + }, +}