Skip to content
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
bdb3901
feat(card): wip - baanx integration
Brunonascdev Oct 3, 2025
49d4bb0
feat(card): new login endpoint refactor
Brunonascdev Oct 8, 2025
fc7ae1a
feat(card): merge with main
Brunonascdev Oct 8, 2025
0623f88
feat(card): add test files
Brunonascdev Oct 8, 2025
eca9348
feat(card): fix remaining tests
Brunonascdev Oct 9, 2025
55f5c53
refactor(card): remove logger from cardTokenVault
Brunonascdev Oct 9, 2025
a2d172c
chore: remove changed perps envs on .js.example
Brunonascdev Oct 9, 2025
a18dcec
feat(card): handle 404 status and change logger.error to logger.log
Brunonascdev Oct 9, 2025
4da2553
feat(card): increase test coverage
Brunonascdev Oct 9, 2025
a384023
feat(card): fix sonarcloud issues
Brunonascdev Oct 9, 2025
7b7a03f
Merge branch 'main' of github.com:MetaMask/metamask-mobile into chore…
Brunonascdev Oct 9, 2025
524ec37
Merge branch 'main' into chore/card-api-integration-foundation
Brunonascdev Oct 9, 2025
2ef1abd
fix(card): failure on refresh token exchange
Brunonascdev Oct 9, 2025
984d40f
Merge branch 'chore/card-api-integration-foundation' of github.com:Me…
Brunonascdev Oct 9, 2025
5a144b8
Merge branch 'main' into chore/card-api-integration-foundation
Brunonascdev Oct 9, 2025
e99f7a9
Merge branch 'main' into chore/card-api-integration-foundation
Brunonascdev Oct 10, 2025
b825071
Merge branch 'main' of github.com:MetaMask/metamask-mobile into chore…
Brunonascdev Oct 10, 2025
6358101
feat(card): fix re-renders on cardsdk
Brunonascdev Oct 10, 2025
ae70e03
Merge branch 'main' of github.com:MetaMask/metamask-mobile into chore…
Brunonascdev Oct 12, 2025
85fdfa3
feat(card): modify CardImage component to support metal card
Brunonascdev Oct 12, 2025
2f8d0d3
feat(card): authenticated data wip
Brunonascdev Oct 12, 2025
89341cb
feat(card): merge with main
Brunonascdev Oct 13, 2025
d240dd2
feat(card): authenticated data - need to fix BigNumber issue
Brunonascdev Oct 13, 2025
257b976
fix(card): bigint issue
Brunonascdev Oct 14, 2025
5a72d57
feat(card): performance fixes
Brunonascdev Oct 14, 2025
eb93b2f
feat(card): add warning
Brunonascdev Oct 15, 2025
cc98b7d
Merge branch 'main' of github.com:MetaMask/metamask-mobile into feat/…
Brunonascdev Oct 15, 2025
25cd4c6
feat(card): adapt spending limit progress bar component to use total …
Brunonascdev Oct 15, 2025
9e5385f
feat(card): adapt hooks and components for Solana assets
Brunonascdev Oct 15, 2025
fecd953
feat(card): working version with Solana assets
Brunonascdev Oct 16, 2025
1bb1362
Merge branch 'main' of github.com:MetaMask/metamask-mobile into feat/…
Brunonascdev Oct 16, 2025
0f5ca3a
feat(card): last solana changes, authentication route setup
Brunonascdev Oct 17, 2025
18fb6b2
feat(card): fix infinite loading state on get card failure
Brunonascdev Oct 17, 2025
af88f86
feat(card): merge with main changes
Brunonascdev Oct 17, 2025
2e8a90f
feat(card): fix missing sdk issue
Brunonascdev Oct 17, 2025
c1329ba
feat(card): fix inconsistent sdk behavior
Brunonascdev Oct 17, 2025
51a5053
test(card): add tests for authenticated data changes
Brunonascdev Oct 18, 2025
a240215
lint(card): undo changes on button readme
Brunonascdev Oct 18, 2025
3e659ab
feat(card): fix lint issues and add useCardDetails test file
Brunonascdev Oct 18, 2025
58e611d
Merge branch 'main' of github.com:MetaMask/metamask-mobile into feat/…
Brunonascdev Oct 18, 2025
33fb5fe
test(card): increase coverage
Brunonascdev Oct 18, 2025
36ea7fe
feat(card): fix token parsing issue
Brunonascdev Oct 18, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { backgroundState } from '../../../../../util/test/initial-root-state';

const mockNavigate = jest.fn();
const mockGoBack = jest.fn();
const mockReset = jest.fn();
const mockDispatch = jest.fn();
const mockAddListener = jest.fn(() => jest.fn());

Expand All @@ -16,6 +17,7 @@ jest.mock('@react-navigation/native', () => ({
useNavigation: () => ({
navigate: mockNavigate,
goBack: mockGoBack,
reset: mockReset,
dispatch: mockDispatch,
addListener: mockAddListener,
}),
Expand Down Expand Up @@ -343,9 +345,9 @@ describe('CardAuthentication Component', () => {
fireEvent.press(loginButton);

await waitFor(() => {
expect(mockDispatch).toHaveBeenCalledWith({
type: 'NAVIGATE',
routeName: Routes.CARD.HOME,
expect(mockReset).toHaveBeenCalledWith({
index: 0,
routes: [{ name: Routes.CARD.HOME }],
});
});
});
Expand All @@ -370,7 +372,7 @@ describe('CardAuthentication Component', () => {

// Verify that navigation doesn't happen when there's an error state
// The component should not navigate when error exists
expect(mockDispatch).not.toHaveBeenCalled();
expect(mockReset).not.toHaveBeenCalled();

// Verify error is displayed
expect(screen.getByText('Invalid login details')).toBeOnTheScreen();
Expand Down Expand Up @@ -401,36 +403,41 @@ describe('CardAuthentication Component', () => {
});

describe('Loading State', () => {
it('shows loading state on login button when loading', () => {
mockUseCardProviderAuthentication.mockReturnValue({
login: mockLogin,
loading: true,
error: null,
clearError: mockClearError,
it('shows loading state on login button during login', async () => {
// Mock login to delay resolution so we can check loading state
let resolveLogin: (() => void) | undefined;
const loginPromise = new Promise<void>((resolve) => {
resolveLogin = resolve;
});
mockLogin.mockReturnValue(loginPromise);

render();

const emailInput = screen.getByPlaceholderText(
'Enter your email address',
);
const passwordInput = screen.getByPlaceholderText('Enter your password');
const loginButton = screen.getByTestId(
CardAuthenticationSelectors.VERIFY_ACCOUNT_BUTTON,
);
expect(loginButton).toHaveProp('loading', true);
});

it('registers beforeRemove navigation listener', () => {
mockUseCardProviderAuthentication.mockReturnValue({
login: mockLogin,
loading: true,
error: null,
clearError: mockClearError,
fireEvent.changeText(emailInput, '[email protected]');
fireEvent.changeText(passwordInput, 'password123');
fireEvent.press(loginButton);

// Check loading state is true during login
await waitFor(() => {
expect(loginButton).toHaveProp('loading', true);
});

render();
// Resolve the login
if (resolveLogin) {
resolveLogin();
}

expect(mockAddListener).toHaveBeenCalledWith(
'beforeRemove',
expect.any(Function),
);
// Wait for loading to finish
await waitFor(() => {
expect(loginButton).toHaveProp('loading', false);
});
});
});

Expand Down Expand Up @@ -487,21 +494,6 @@ describe('CardAuthentication Component', () => {
});
});

describe('Navigation Listener Cleanup', () => {
it('sets up navigation listener with cleanup function', () => {
const mockUnsubscribe = jest.fn();
mockAddListener.mockReturnValue(mockUnsubscribe);

render();

expect(mockAddListener).toHaveBeenCalledWith(
'beforeRemove',
expect.any(Function),
);
expect(mockAddListener).toHaveReturnedWith(mockUnsubscribe);
});
});

describe('Accessibility', () => {
it('has proper accessibility labels for form fields', () => {
render();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useNavigation } from '@react-navigation/native';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import {
Image,
KeyboardAvoidingView,
Expand Down Expand Up @@ -30,19 +30,19 @@ import createStyles from './CardAuthentication.styles';
import { SafeAreaView } from 'react-native-safe-area-context';
import useCardProviderAuthentication from '../../hooks/useCardProviderAuthentication';
import { CardAuthenticationSelectors } from '../../../../../../e2e/selectors/Card/CardAuthentication.selectors';
import { NavigationActions } from '@react-navigation/compat';
import Routes from '../../../../../constants/navigation/Routes';
import { CardLocation } from '../../types';
import { strings } from '../../../../../../locales/i18n';
import Logger from '../../../../../util/Logger';

const CardAuthentication = () => {
const { dispatch, addListener } = useNavigation();
const navigation = useNavigation();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [location, setLocation] = useState<CardLocation>('international');
const [loginSuccess, setLoginSuccess] = useState(false);
const theme = useTheme();
const { login, loading, error, clearError } = useCardProviderAuthentication();
const { login, error, clearError } = useCardProviderAuthentication();

const styles = createStyles(theme);

Expand All @@ -60,46 +60,29 @@ const CardAuthentication = () => {
}
};

useEffect(() => {
const unsubscribe = addListener('beforeRemove', (e) => {
if (loading) {
e.preventDefault();
return;
}

dispatch(e.data.action);
});

return unsubscribe;
}, [addListener, loading, dispatch]);

// Navigate to home after successful login when loading is complete
useEffect(() => {
if (loginSuccess && !loading && !error) {
dispatch(
NavigationActions.navigate({
routeName: Routes.CARD.HOME,
}),
);
setLoginSuccess(false); // Reset the flag
}
}, [loginSuccess, loading, error, dispatch]);

const performLogin = useCallback(async () => {
await login({
location,
email,
password,
});

if (!error) {
setLoginSuccess(true);
try {
setLoading(true);
await login({
location,
email,
password,
});

navigation.reset({
index: 0,
routes: [{ name: Routes.CARD.HOME }],
});
} catch (err) {
Logger.log('CardAuthentication::Login failed', err);
} finally {
setLoading(false);
}
}, [error, email, location, login, password]);
}, [email, location, login, password, navigation]);

const isDisabled = useMemo(
() => loading || !!error || email.length === 0 || password.length === 0,
[loading, error, email, password],
() => !!error || email.length === 0 || password.length === 0,
[error, email, password],
);

return (
Expand Down
10 changes: 9 additions & 1 deletion app/components/UI/Card/Views/CardHome/CardHome.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const createStyles = (theme: Theme) =>
},
contentContainer: {
flexGrow: 1,
paddingBottom: 32,
},
defaultHorizontalPadding: {
paddingHorizontal: 16,
Expand All @@ -57,7 +58,6 @@ const createStyles = (theme: Theme) =>
},
cardImageContainer: {
width: '100%',
marginTop: 8,
},
cardAssetItemContainer: {
height: 80,
Expand All @@ -76,6 +76,11 @@ const createStyles = (theme: Theme) =>
backgroundColor: theme.colors.background.default,
width: '100%',
},
spendingLimitDivider: {
height: 1,
backgroundColor: theme.colors.border.muted,
width: '100%',
},
defaultMarginTop: {
marginTop: 16,
},
Expand All @@ -92,6 +97,9 @@ const createStyles = (theme: Theme) =>
halfWidthButton: {
width: '50%',
},
shouldBeHidden: {
display: 'none',
},
});

export default createStyles;
Loading
Loading