mirror of
https://github.com/selfxyz/self.git
synced 2026-02-19 02:24:25 -05:00
[SELF-1952] UI: Create KYC verified screen; prompt to enter proving flow (#1681)
* first pass at kyc verified screen * finalize kyc verified design * add queue buffer
This commit is contained in:
@@ -80,6 +80,7 @@ export type RootStackParamList = Omit<
|
||||
| 'IDPicker'
|
||||
| 'IdDetails'
|
||||
| 'KycSuccess'
|
||||
| 'KYCVerified'
|
||||
| 'RegistrationFallback'
|
||||
| 'Loading'
|
||||
| 'Modal'
|
||||
@@ -207,6 +208,12 @@ export type RootStackParamList = Omit<
|
||||
userId?: string;
|
||||
}
|
||||
| undefined;
|
||||
KYCVerified:
|
||||
| {
|
||||
status?: string;
|
||||
userId?: string;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
// Dev screens
|
||||
CreateMock: undefined;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';
|
||||
|
||||
import KycSuccessScreen from '@/screens/kyc/KycSuccessScreen';
|
||||
import KYCVerifiedScreen from '@/screens/kyc/KYCVerifiedScreen';
|
||||
import AccountVerifiedSuccessScreen from '@/screens/onboarding/AccountVerifiedSuccessScreen';
|
||||
import DisclaimerScreen from '@/screens/onboarding/DisclaimerScreen';
|
||||
import SaveRecoveryPhraseScreen from '@/screens/onboarding/SaveRecoveryPhraseScreen';
|
||||
@@ -38,6 +39,13 @@ const onboardingScreens = {
|
||||
animation: 'slide_from_bottom',
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
KYCVerified: {
|
||||
screen: KYCVerifiedScreen,
|
||||
options: {
|
||||
headerShown: false,
|
||||
animation: 'slide_from_bottom',
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
};
|
||||
|
||||
export default onboardingScreens;
|
||||
|
||||
@@ -4,16 +4,89 @@
|
||||
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import type { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
|
||||
import messaging from '@react-native-firebase/messaging';
|
||||
|
||||
import { NotificationEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
|
||||
import { navigationRef } from '@/navigation';
|
||||
import { trackEvent } from '@/services/analytics';
|
||||
|
||||
// Queue for pending navigation actions that need to wait for navigation to be ready
|
||||
let pendingNavigation: FirebaseMessagingTypes.RemoteMessage | null = null;
|
||||
|
||||
/**
|
||||
* Execute navigation for a notification
|
||||
* @returns true if navigation was executed, false if it needs to be queued
|
||||
*/
|
||||
const executeNotificationNavigation = (
|
||||
remoteMessage: FirebaseMessagingTypes.RemoteMessage,
|
||||
): boolean => {
|
||||
if (!navigationRef.isReady()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const notificationType = remoteMessage.data?.type;
|
||||
const status = remoteMessage.data?.status;
|
||||
|
||||
// Handle KYC result notifications
|
||||
if (notificationType === 'kyc_result' && status === 'approved') {
|
||||
navigationRef.navigate('KYCVerified', {
|
||||
status: String(status),
|
||||
userId: remoteMessage.data?.user_id
|
||||
? String(remoteMessage.data.user_id)
|
||||
: undefined,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// Add handling for other notification types here as needed
|
||||
// For retry/rejected statuses, could navigate to appropriate screens in future
|
||||
|
||||
return true; // Navigation handled (or not applicable)
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle navigation based on notification type and data
|
||||
* Queues navigation if navigationRef is not ready yet
|
||||
*/
|
||||
const handleNotificationNavigation = (
|
||||
remoteMessage: FirebaseMessagingTypes.RemoteMessage,
|
||||
) => {
|
||||
const executed = executeNotificationNavigation(remoteMessage);
|
||||
|
||||
if (!executed) {
|
||||
// Navigation not ready yet - queue for later
|
||||
pendingNavigation = remoteMessage;
|
||||
if (__DEV__) {
|
||||
console.log(
|
||||
'Navigation not ready, queuing notification navigation:',
|
||||
remoteMessage.data?.type,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Process any pending navigation once navigation is ready
|
||||
*/
|
||||
const processPendingNavigation = () => {
|
||||
if (pendingNavigation && navigationRef.isReady()) {
|
||||
if (__DEV__) {
|
||||
console.log(
|
||||
'Processing pending notification navigation:',
|
||||
pendingNavigation.data?.type,
|
||||
);
|
||||
}
|
||||
executeNotificationNavigation(pendingNavigation);
|
||||
pendingNavigation = null;
|
||||
}
|
||||
};
|
||||
|
||||
export const NotificationTrackingProvider: React.FC<PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
// Handle notification tap when app is in background
|
||||
const unsubscribe = messaging().onNotificationOpenedApp(remoteMessage => {
|
||||
trackEvent(NotificationEvents.BACKGROUND_NOTIFICATION_OPENED, {
|
||||
messageId: remoteMessage.messageId,
|
||||
@@ -22,8 +95,12 @@ export const NotificationTrackingProvider: React.FC<PropsWithChildren> = ({
|
||||
// Track if user interacted with any actions
|
||||
actionId: remoteMessage.data?.actionId,
|
||||
});
|
||||
|
||||
// Handle navigation based on notification type
|
||||
handleNotificationNavigation(remoteMessage);
|
||||
});
|
||||
|
||||
// Handle notification tap when app is completely closed (cold start)
|
||||
messaging()
|
||||
.getInitialNotification()
|
||||
.then(remoteMessage => {
|
||||
@@ -35,11 +112,34 @@ export const NotificationTrackingProvider: React.FC<PropsWithChildren> = ({
|
||||
// Track if user interacted with any actions
|
||||
actionId: remoteMessage.data?.actionId,
|
||||
});
|
||||
|
||||
// Handle navigation based on notification type
|
||||
handleNotificationNavigation(remoteMessage);
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
|
||||
// Monitor navigation readiness and process pending navigation
|
||||
useEffect(() => {
|
||||
// Check immediately if navigation is already ready
|
||||
if (navigationRef.isReady()) {
|
||||
processPendingNavigation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Poll for navigation readiness if not ready yet
|
||||
const checkInterval = setInterval(() => {
|
||||
if (navigationRef.isReady()) {
|
||||
processPendingNavigation();
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
}, 100); // Check every 100ms
|
||||
|
||||
// Cleanup interval on unmount
|
||||
return () => clearInterval(checkInterval);
|
||||
}, []);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
83
app/src/screens/kyc/KYCVerifiedScreen.tsx
Normal file
83
app/src/screens/kyc/KYCVerifiedScreen.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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 {
|
||||
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';
|
||||
|
||||
const KYCVerifiedScreen: React.FC = () => {
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const handleGenerateProof = () => {
|
||||
buttonTap();
|
||||
navigation.navigate('ProvingScreenRouter');
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingBottom: insets.bottom }]}>
|
||||
<View style={styles.spacer} />
|
||||
<YStack
|
||||
paddingHorizontal={24}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gap={12}
|
||||
marginBottom={64}
|
||||
>
|
||||
<Title style={styles.title}>Your ID has been verified</Title>
|
||||
<Description style={styles.description}>
|
||||
Next Self will generate a zk proof specifically for this device that
|
||||
you can use to proof your identity.
|
||||
</Description>
|
||||
</YStack>
|
||||
<YStack gap={12} paddingHorizontal={20} paddingBottom={24}>
|
||||
<AbstractButton
|
||||
bgColor={white}
|
||||
color={black}
|
||||
onPress={handleGenerateProof}
|
||||
>
|
||||
Generate proof
|
||||
</AbstractButton>
|
||||
</YStack>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: black,
|
||||
},
|
||||
spacer: {
|
||||
flex: 1,
|
||||
},
|
||||
title: {
|
||||
color: white,
|
||||
textAlign: 'center',
|
||||
fontSize: 28,
|
||||
letterSpacing: 1,
|
||||
},
|
||||
description: {
|
||||
color: white,
|
||||
textAlign: 'center',
|
||||
fontSize: 18,
|
||||
lineHeight: 27,
|
||||
},
|
||||
});
|
||||
|
||||
export default KYCVerifiedScreen;
|
||||
Reference in New Issue
Block a user