mirror of
https://github.com/selfxyz/self.git
synced 2026-02-19 02:24:25 -05:00
SELF-1932: sumsub success screen (#1667)
* fix typos * typo * match screen design. fix tests
This commit is contained in:
@@ -201,6 +201,7 @@ export type RootStackParamList = Omit<
|
||||
|
||||
// Onboarding screens
|
||||
Disclaimer: undefined;
|
||||
KycSuccess: undefined;
|
||||
|
||||
// Dev screens
|
||||
CreateMock: undefined;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';
|
||||
|
||||
import KycSuccessScreen from '@/screens/kyc/KycSuccessScreen';
|
||||
import AccountVerifiedSuccessScreen from '@/screens/onboarding/AccountVerifiedSuccessScreen';
|
||||
import DisclaimerScreen from '@/screens/onboarding/DisclaimerScreen';
|
||||
import SaveRecoveryPhraseScreen from '@/screens/onboarding/SaveRecoveryPhraseScreen';
|
||||
@@ -30,6 +31,13 @@ const onboardingScreens = {
|
||||
animation: 'slide_from_bottom',
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
KycSuccess: {
|
||||
screen: KycSuccessScreen,
|
||||
options: {
|
||||
headerShown: false,
|
||||
animation: 'slide_from_bottom',
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
};
|
||||
|
||||
export default onboardingScreens;
|
||||
|
||||
@@ -375,8 +375,13 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
countryCode,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Success case: navigate to KYC success screen
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('KycSuccess');
|
||||
}
|
||||
// success case: provider handles its own success UI
|
||||
} catch (error) {
|
||||
const safeInitError = sanitizeErrorMessage(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
|
||||
124
app/src/screens/kyc/KycSuccessScreen.tsx
Normal file
124
app/src/screens/kyc/KycSuccessScreen.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { YStack } from 'tamagui';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import { DelayedLottieView } from '@selfxyz/mobile-sdk-alpha';
|
||||
import loadingAnimation from '@selfxyz/mobile-sdk-alpha/animations/loading/misc.json';
|
||||
import {
|
||||
AbstractButton,
|
||||
Description,
|
||||
Title,
|
||||
} from '@selfxyz/mobile-sdk-alpha/components';
|
||||
import { black, white } from '@selfxyz/mobile-sdk-alpha/constants/colors';
|
||||
|
||||
import { buttonTap } from '@/integrations/haptics';
|
||||
import type { RootStackParamList } from '@/navigation';
|
||||
import { requestNotificationPermission } from '@/services/notifications/notificationService';
|
||||
|
||||
const KycSuccessScreen: React.FC = () => {
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const handleReceiveUpdates = async () => {
|
||||
buttonTap();
|
||||
await requestNotificationPermission();
|
||||
// Navigate to Home regardless of permission result
|
||||
navigation.navigate('Home', {});
|
||||
};
|
||||
|
||||
const handleCheckLater = () => {
|
||||
buttonTap();
|
||||
navigation.navigate('Home', {});
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingBottom: insets.bottom }]}>
|
||||
<View style={styles.centerSection}>
|
||||
<View style={styles.animationContainer}>
|
||||
<DelayedLottieView
|
||||
autoPlay
|
||||
loop={true}
|
||||
source={loadingAnimation}
|
||||
style={styles.animation}
|
||||
cacheComposition={true}
|
||||
renderMode="HARDWARE"
|
||||
/>
|
||||
</View>
|
||||
<YStack
|
||||
paddingHorizontal={24}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gap={12}
|
||||
>
|
||||
<Title style={styles.title}>Your ID is being verified</Title>
|
||||
<Description style={styles.description}>
|
||||
Turn on push notifications to receive an update on your
|
||||
verification. It's also safe the close the app and come back later.
|
||||
</Description>
|
||||
</YStack>
|
||||
</View>
|
||||
<YStack gap={12} paddingHorizontal={20} paddingBottom={24}>
|
||||
<AbstractButton
|
||||
bgColor={white}
|
||||
color={black}
|
||||
onPress={handleReceiveUpdates}
|
||||
>
|
||||
Receive live updates
|
||||
</AbstractButton>
|
||||
<AbstractButton
|
||||
bgColor="transparent"
|
||||
color={white}
|
||||
borderColor="rgba(255, 255, 255, 0.3)"
|
||||
borderWidth={1}
|
||||
onPress={handleCheckLater}
|
||||
>
|
||||
I will check back later
|
||||
</AbstractButton>
|
||||
</YStack>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: black,
|
||||
},
|
||||
centerSection: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
animationContainer: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 32,
|
||||
},
|
||||
animation: {
|
||||
width: 160,
|
||||
height: 160,
|
||||
},
|
||||
title: {
|
||||
color: white,
|
||||
textAlign: 'center',
|
||||
fontSize: 28,
|
||||
letterSpacing: 1,
|
||||
},
|
||||
description: {
|
||||
color: white,
|
||||
textAlign: 'center',
|
||||
fontSize: 18,
|
||||
},
|
||||
});
|
||||
|
||||
export default KycSuccessScreen;
|
||||
@@ -85,6 +85,7 @@ describe('navigation', () => {
|
||||
'Home',
|
||||
'IDPicker',
|
||||
'IdDetails',
|
||||
'KycSuccess',
|
||||
'Loading',
|
||||
'ManageDocuments',
|
||||
'MockDataDeepLink',
|
||||
|
||||
154
app/tests/src/screens/kyc/KycSuccessScreen.test.tsx
Normal file
154
app/tests/src/screens/kyc/KycSuccessScreen.test.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import React from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { render } from '@testing-library/react-native';
|
||||
|
||||
import ErrorBoundary from '@/components/ErrorBoundary';
|
||||
import KycSuccessScreen from '@/screens/kyc/KycSuccessScreen';
|
||||
import * as notificationService from '@/services/notifications/notificationService';
|
||||
|
||||
// Note: While jest.setup.js provides comprehensive React Native mocking,
|
||||
// react-test-renderer requires component-based mocks (functions) rather than
|
||||
// string-based mocks for proper rendering. This minimal mock provides the
|
||||
// specific components needed for this test without using requireActual to
|
||||
// avoid memory issues (see .cursor/rules/test-memory-optimization.mdc).
|
||||
jest.mock('react-native', () => ({
|
||||
__esModule: true,
|
||||
Platform: { OS: 'ios', select: jest.fn() },
|
||||
StyleSheet: {
|
||||
create: (styles: any) => styles,
|
||||
flatten: (style: any) => style,
|
||||
},
|
||||
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
||||
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
|
||||
}));
|
||||
|
||||
jest.mock('react-native-edge-to-edge', () => ({
|
||||
SystemBars: () => null,
|
||||
}));
|
||||
|
||||
jest.mock('react-native-safe-area-context', () => ({
|
||||
useSafeAreaInsets: jest.fn(() => ({ top: 0, bottom: 0 })),
|
||||
}));
|
||||
|
||||
jest.mock('@react-navigation/native', () => ({
|
||||
useNavigation: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock Tamagui components
|
||||
jest.mock('tamagui', () => ({
|
||||
__esModule: true,
|
||||
YStack: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
||||
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
||||
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
|
||||
}));
|
||||
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha', () => ({
|
||||
DelayedLottieView: () => null,
|
||||
}));
|
||||
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha/constants/colors', () => ({
|
||||
black: '#000000',
|
||||
white: '#FFFFFF',
|
||||
}));
|
||||
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha/components', () => ({
|
||||
AbstractButton: ({ children, onPress }: any) => (
|
||||
<button onClick={onPress} data-testid="abstract-button" type="button">
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
PrimaryButton: ({ children, onPress }: any) => (
|
||||
<button onClick={onPress} data-testid="primary-button" type="button">
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
SecondaryButton: ({ children, onPress }: any) => (
|
||||
<button onClick={onPress} data-testid="secondary-button" type="button">
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
Title: ({ children }: any) => <div data-testid="title">{children}</div>,
|
||||
Description: ({ children }: any) => (
|
||||
<div data-testid="description">{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/integrations/haptics', () => ({
|
||||
buttonTap: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/services/notifications/notificationService', () => ({
|
||||
requestNotificationPermission: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/config/sentry', () => ({
|
||||
captureException: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/services/analytics', () => ({
|
||||
flushAllAnalytics: jest.fn(),
|
||||
trackNfcEvent: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockUseNavigation = useNavigation as jest.MockedFunction<
|
||||
typeof useNavigation
|
||||
>;
|
||||
|
||||
describe('KycSuccessScreen', () => {
|
||||
const mockNavigate = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockUseNavigation.mockReturnValue({
|
||||
navigate: mockNavigate,
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('should render the screen without errors', () => {
|
||||
const { root } = render(<KycSuccessScreen />);
|
||||
expect(root).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have navigation available', () => {
|
||||
render(<KycSuccessScreen />);
|
||||
expect(mockUseNavigation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have notification service available', () => {
|
||||
render(<KycSuccessScreen />);
|
||||
expect(notificationService.requestNotificationPermission).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders fallback on render error', () => {
|
||||
// Mock console.error to suppress error boundary error logs during test
|
||||
const consoleErrorSpy = jest
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
// Create a component that throws an error during render
|
||||
const ThrowError = () => {
|
||||
throw new Error('Test render error');
|
||||
};
|
||||
|
||||
// Render the error-throwing component wrapped in ErrorBoundary
|
||||
const { root } = render(
|
||||
<ErrorBoundary>
|
||||
<ThrowError />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
|
||||
// Verify the error boundary fallback UI is displayed
|
||||
// Use a more flexible matcher since the text is nested in mocked components
|
||||
expect(root.findByType('span').props.children).toBe(
|
||||
'Something went wrong. Please restart the app.',
|
||||
);
|
||||
|
||||
// Restore console.error
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user