mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
App/id picker flow (#1126)
* add new id picker flow * refactor: update document management screen actions - Renamed `handleScanDocument` to `handleAddDocument` for clarity. - Updated navigation from 'DocumentOnboarding' to 'CountryPicker'. - Removed unused `handleAddAadhaar` function and its associated button. * address pr feedback * address lint issues * fix test * fix typings and screen * fix e2e button test --------- Co-authored-by: Justin Hernandez <justin.hernandez@self.xyz>
This commit is contained in:
committed by
GitHub
parent
3b14f09c30
commit
422d0cc259
@@ -98,7 +98,6 @@ const Container: React.FC<NavBarProps> = ({
|
||||
<SystemBars style={barStyle} />
|
||||
<XStack
|
||||
backgroundColor={backgroundColor}
|
||||
flexGrow={1}
|
||||
justifyContent="flex-start"
|
||||
alignItems="center"
|
||||
{...props}
|
||||
|
||||
46
app/src/components/NavBar/DocumentFlowNavBar.tsx
Normal file
46
app/src/components/NavBar/DocumentFlowNavBar.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { HelpCircle } from '@tamagui/lucide-icons';
|
||||
|
||||
import { NavBar } from '@/components/NavBar/BaseNavBar';
|
||||
import { slate100 } from '@/utils/colors';
|
||||
import { dinot } from '@/utils/fonts';
|
||||
|
||||
export const DocumentFlowNavBar = ({
|
||||
title,
|
||||
titleFontFamily = dinot,
|
||||
fontSize = 17,
|
||||
}: {
|
||||
title: string;
|
||||
titleFontFamily?: string;
|
||||
fontSize?: number;
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
const { top } = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<NavBar.Container
|
||||
paddingTop={top}
|
||||
backgroundColor={slate100}
|
||||
paddingHorizontal="$4"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<NavBar.LeftAction component="back" onPress={() => navigation.goBack()} />
|
||||
<NavBar.Title fontFamily={titleFontFamily} fontSize={fontSize}>
|
||||
{title}
|
||||
</NavBar.Title>
|
||||
<NavBar.RightAction
|
||||
component={<HelpCircle color={'transparent'} />}
|
||||
onPress={() => {
|
||||
/* Handle help action, button is transparent for now as we dont have the help screen ready */
|
||||
}}
|
||||
/>
|
||||
</NavBar.Container>
|
||||
);
|
||||
};
|
||||
77
app/src/components/flag/RoundFlag.tsx
Normal file
77
app/src/components/flag/RoundFlag.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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 getCountryISO2 from 'country-iso-3-to-2';
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import * as CountryFlags from 'react-native-svg-circle-country-flags';
|
||||
|
||||
import { slate300 } from '@/utils/colors';
|
||||
|
||||
type CountryFlagComponent = React.ComponentType<{
|
||||
width: number;
|
||||
height: number;
|
||||
}>;
|
||||
|
||||
type CountryFlagsRecord = Record<string, CountryFlagComponent>;
|
||||
|
||||
interface RoundFlagProps {
|
||||
countryCode: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
const findFlagComponent = (formattedCode: string) => {
|
||||
const patterns = [
|
||||
formattedCode,
|
||||
formattedCode.toLowerCase(),
|
||||
formattedCode.charAt(0).toUpperCase() +
|
||||
formattedCode.charAt(1).toLowerCase(),
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const component = (CountryFlags as unknown as CountryFlagsRecord)[pattern];
|
||||
if (component) {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getCountryFlag = (countryCode: string): CountryFlagComponent | null => {
|
||||
try {
|
||||
const normalizedCountryCode = countryCode === 'D<<' ? 'DEU' : countryCode;
|
||||
const iso2 = getCountryISO2(normalizedCountryCode);
|
||||
if (!iso2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const formattedCode = iso2.toUpperCase();
|
||||
return findFlagComponent(formattedCode);
|
||||
} catch (error) {
|
||||
console.error('Error getting country flag:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const RoundFlag: React.FC<RoundFlagProps> = ({ countryCode, size }) => {
|
||||
const CountryFlagComponent = getCountryFlag(countryCode);
|
||||
|
||||
if (!CountryFlagComponent) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
backgroundColor: slate300,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<CountryFlagComponent width={size} height={size} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
6
app/src/images/icons/epassport_rounded.svg
Normal file
6
app/src/images/icons/epassport_rounded.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="48" height="32" viewBox="0 0 48 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="48" height="32" rx="2" fill="#075985"/>
|
||||
<circle cx="24" cy="16" r="8.5" stroke="white" stroke-width="4"/>
|
||||
<line x1="-1.74846e-07" y1="16" x2="15" y2="16" stroke="white" stroke-width="4"/>
|
||||
<line x1="33" y1="16" x2="48" y2="16" stroke="white" stroke-width="4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 376 B |
61
app/src/images/icons/id_card_placeholder.svg
Normal file
61
app/src/images/icons/id_card_placeholder.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 105 KiB |
3
app/src/images/icons/plus.svg
Normal file
3
app/src/images/icons/plus.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.0644531 7.6543C0.0644531 7.34375 0.175781 7.07715 0.398438 6.85449C0.621094 6.63184 0.887695 6.52051 1.19824 6.52051H6.375V1.35254C6.375 1.04199 6.4834 0.775391 6.7002 0.552734C6.91699 0.330078 7.18359 0.21875 7.5 0.21875C7.81055 0.21875 8.07715 0.330078 8.2998 0.552734C8.52246 0.775391 8.63379 1.04199 8.63379 1.35254V6.52051H13.8105C14.1152 6.52051 14.3789 6.63184 14.6016 6.85449C14.8242 7.07715 14.9355 7.34375 14.9355 7.6543C14.9355 7.9707 14.8242 8.24023 14.6016 8.46289C14.3789 8.67969 14.1152 8.78809 13.8105 8.78809H8.63379V13.9648C8.63379 14.2695 8.52246 14.5332 8.2998 14.7559C8.07715 14.9785 7.81055 15.0898 7.5 15.0898C7.18359 15.0898 6.91699 14.9785 6.7002 14.7559C6.4834 14.5332 6.375 14.2695 6.375 13.9648V8.78809H1.19824C0.887695 8.78809 0.621094 8.67969 0.398438 8.46289C0.175781 8.24023 0.0644531 7.9707 0.0644531 7.6543Z" fill="#94A3B8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 975 B |
@@ -4,13 +4,15 @@
|
||||
|
||||
import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';
|
||||
|
||||
import ComingSoonScreen from '@/screens/document/ComingSoonScreen';
|
||||
import CountryPickerScreen from '@/screens/document/CountryPickerScreen';
|
||||
import DocumentCameraScreen from '@/screens/document/DocumentCameraScreen';
|
||||
import DocumentCameraTroubleScreen from '@/screens/document/DocumentCameraTroubleScreen';
|
||||
import DocumentNFCMethodSelectionScreen from '@/screens/document/DocumentNFCMethodSelectionScreen';
|
||||
import DocumentNFCScanScreen from '@/screens/document/DocumentNFCScanScreen';
|
||||
import DocumentNFCTroubleScreen from '@/screens/document/DocumentNFCTroubleScreen';
|
||||
import DocumentOnboardingScreen from '@/screens/document/DocumentOnboardingScreen';
|
||||
import UnsupportedDocumentScreen from '@/screens/document/UnsupportedDocumentScreen';
|
||||
import IDPickerScreen from '@/screens/document/IDPickerScreen';
|
||||
|
||||
const documentScreens = {
|
||||
DocumentCamera: {
|
||||
@@ -56,8 +58,8 @@ const documentScreens = {
|
||||
headerShown: false,
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
UnsupportedDocument: {
|
||||
screen: UnsupportedDocumentScreen,
|
||||
ComingSoon: {
|
||||
screen: ComingSoonScreen,
|
||||
options: {
|
||||
headerShown: false,
|
||||
} as NativeStackNavigationOptions,
|
||||
@@ -73,6 +75,22 @@ const documentScreens = {
|
||||
animation: 'slide_from_bottom',
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
CountryPicker: {
|
||||
screen: CountryPickerScreen,
|
||||
options: {
|
||||
headerShown: false,
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
IDPicker: {
|
||||
screen: IDPickerScreen,
|
||||
options: {
|
||||
headerShown: false,
|
||||
} as NativeStackNavigationOptions,
|
||||
initialParams: {
|
||||
countryCode: '',
|
||||
documentTypes: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default documentScreens;
|
||||
|
||||
@@ -79,7 +79,7 @@ const NavigationWithTracking = () => {
|
||||
return () => {
|
||||
cleanup();
|
||||
};
|
||||
}, []);
|
||||
}, [selfClient]);
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView>
|
||||
|
||||
@@ -136,7 +136,7 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
SdkEvents.PROVING_PASSPORT_NOT_SUPPORTED,
|
||||
({ countryCode, documentCategory }) => {
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('UnsupportedDocument', {
|
||||
navigationRef.navigate('ComingSoon', {
|
||||
countryCode,
|
||||
documentCategory,
|
||||
} as any);
|
||||
|
||||
@@ -124,6 +124,7 @@ function ParameterSection({
|
||||
|
||||
const items = [
|
||||
'DevSettings',
|
||||
'CountryPicker',
|
||||
'AadhaarUpload',
|
||||
'DevFeatureFlags',
|
||||
'DevHapticFeedback',
|
||||
@@ -149,7 +150,7 @@ const items = [
|
||||
'RecoverWithPhrase',
|
||||
'ShowRecoveryPhrase',
|
||||
'CloudBackupSettings',
|
||||
'UnsupportedDocument',
|
||||
'ComingSoon',
|
||||
'DocumentCameraTrouble',
|
||||
'DocumentNFCTrouble',
|
||||
] satisfies (keyof RootStackParamList)[];
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import getCountryISO2 from 'country-iso-3-to-2';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import * as CountryFlags from 'react-native-svg-circle-country-flags';
|
||||
import { XStack, YStack } from 'tamagui';
|
||||
import type { RouteProp } from '@react-navigation/native';
|
||||
|
||||
@@ -19,6 +16,7 @@ import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
|
||||
import { PrimaryButton } from '@/components/buttons/PrimaryButton';
|
||||
import { SecondaryButton } from '@/components/buttons/SecondaryButton';
|
||||
import { RoundFlag } from '@/components/flag/RoundFlag';
|
||||
import { BodyText } from '@/components/typography/BodyText';
|
||||
import { Title } from '@/components/typography/Title';
|
||||
import useHapticNavigation from '@/hooks/useHapticNavigation';
|
||||
@@ -30,88 +28,66 @@ import { notificationError } from '@/utils/haptic';
|
||||
|
||||
const { flush: flushAnalytics } = analytics();
|
||||
|
||||
type CountryFlagComponent = React.ComponentType<{
|
||||
width: number;
|
||||
height: number;
|
||||
}>;
|
||||
type CountryFlagsRecord = Record<string, CountryFlagComponent>;
|
||||
|
||||
type UnsupportedDocumentScreenRouteProp = RouteProp<
|
||||
type ComingSoonScreenRouteProp = RouteProp<
|
||||
{
|
||||
UnsupportedDocument: {
|
||||
countryCode: string | null;
|
||||
documentCategory: DocumentCategory | null;
|
||||
ComingSoon: {
|
||||
countryCode: string;
|
||||
documentCategory?: DocumentCategory;
|
||||
};
|
||||
},
|
||||
'UnsupportedDocument'
|
||||
'ComingSoon'
|
||||
>;
|
||||
|
||||
interface UnsupportedDocumentScreenProps {
|
||||
route: UnsupportedDocumentScreenRouteProp;
|
||||
interface ComingSoonScreenProps {
|
||||
route: ComingSoonScreenRouteProp;
|
||||
}
|
||||
|
||||
const UnsupportedDocumentScreen: React.FC<UnsupportedDocumentScreenProps> = ({
|
||||
route,
|
||||
}) => {
|
||||
const ComingSoonScreen: React.FC<ComingSoonScreenProps> = ({ route }) => {
|
||||
const selfClient = useSelfClient();
|
||||
const navigateToLaunch = useHapticNavigation('Launch');
|
||||
const navigateToHome = useHapticNavigation('Home');
|
||||
|
||||
const { countryName, country2AlphaCode, documentTypeText } = useMemo(() => {
|
||||
const { countryName, countryCode, documentTypeText } = useMemo(() => {
|
||||
try {
|
||||
const countryCode = route.params?.countryCode;
|
||||
if (countryCode) {
|
||||
const routeCountryCode = route.params?.countryCode;
|
||||
if (routeCountryCode) {
|
||||
// Handle Germany corner case where country code is "D<<" instead of "DEU"
|
||||
let normalizedCountryCode = countryCode;
|
||||
if (countryCode === 'D<<') {
|
||||
normalizedCountryCode = 'DEU';
|
||||
}
|
||||
|
||||
const iso2 = getCountryISO2(normalizedCountryCode);
|
||||
const extractedCode = iso2
|
||||
? iso2.charAt(0).toUpperCase() + iso2.charAt(1).toLowerCase()
|
||||
: 'Unknown';
|
||||
const normalizedCountryCode =
|
||||
routeCountryCode === 'D<<' ? 'DEU' : routeCountryCode;
|
||||
const name =
|
||||
countryCodes[normalizedCountryCode as keyof typeof countryCodes];
|
||||
const docType =
|
||||
route.params?.documentCategory === 'id_card'
|
||||
? 'ID Cards'
|
||||
: 'Passports';
|
||||
|
||||
let docType = '';
|
||||
if (route.params?.documentCategory === 'id_card') {
|
||||
docType = 'ID Cards';
|
||||
} else if (route.params?.documentCategory === 'passport') {
|
||||
docType = 'Passports';
|
||||
}
|
||||
|
||||
return {
|
||||
countryName: name,
|
||||
country2AlphaCode: extractedCode,
|
||||
countryCode: normalizedCountryCode,
|
||||
documentTypeText: docType,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error extracting country from passport data:', error);
|
||||
}
|
||||
const docType =
|
||||
route.params?.documentCategory === 'id_card' ? 'ID Cards' : 'Passports';
|
||||
|
||||
let docType = '';
|
||||
if (route.params?.documentCategory === 'id_card') {
|
||||
docType = 'ID Cards';
|
||||
} else if (route.params?.documentCategory === 'passport') {
|
||||
docType = 'Passports';
|
||||
}
|
||||
|
||||
return {
|
||||
countryName: 'Unknown',
|
||||
country2AlphaCode: 'Unknown',
|
||||
countryCode: 'Unknown',
|
||||
documentTypeText: docType,
|
||||
};
|
||||
}, [route.params?.documentCategory, route.params?.countryCode]);
|
||||
|
||||
// Get country flag component dynamically
|
||||
const getCountryFlag = (code: string) => {
|
||||
try {
|
||||
const FlagComponent = (CountryFlags as unknown as CountryFlagsRecord)[
|
||||
code.toUpperCase()
|
||||
];
|
||||
if (FlagComponent) {
|
||||
return FlagComponent;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting country flag:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const CountryFlagComponent = getCountryFlag(country2AlphaCode);
|
||||
|
||||
const onDismiss = async () => {
|
||||
const hasValidDocument = await hasAnyValidRegisteredDocument(selfClient);
|
||||
if (hasValidDocument) {
|
||||
@@ -125,9 +101,8 @@ const UnsupportedDocumentScreen: React.FC<UnsupportedDocumentScreenProps> = ({
|
||||
try {
|
||||
await sendCountrySupportNotification({
|
||||
countryName,
|
||||
countryCode:
|
||||
country2AlphaCode !== 'Unknown' ? country2AlphaCode : undefined,
|
||||
documentCategory: route.params?.documentCategory ?? '',
|
||||
countryCode: countryCode !== 'Unknown' ? countryCode : '',
|
||||
documentCategory: route.params?.documentCategory,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to open email client:', error);
|
||||
@@ -155,10 +130,8 @@ const UnsupportedDocumentScreen: React.FC<UnsupportedDocumentScreenProps> = ({
|
||||
marginBottom={20}
|
||||
gap={12}
|
||||
>
|
||||
{CountryFlagComponent && (
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<CountryFlagComponent width={60} height={60} />
|
||||
</View>
|
||||
{countryCode !== 'Unknown' && (
|
||||
<RoundFlag countryCode={countryCode} size={60} />
|
||||
)}
|
||||
</XStack>
|
||||
<Title
|
||||
@@ -176,8 +149,9 @@ const UnsupportedDocumentScreen: React.FC<UnsupportedDocumentScreenProps> = ({
|
||||
marginBottom={10}
|
||||
paddingHorizontal={10}
|
||||
>
|
||||
We're working to roll out support for {documentTypeText} in{' '}
|
||||
{countryName}.
|
||||
{documentTypeText
|
||||
? `We're working to roll out support for ${documentTypeText} in ${countryName}.`
|
||||
: `We're working to roll out support in ${countryName}.`}
|
||||
</BodyText>
|
||||
<BodyText
|
||||
fontSize={17}
|
||||
@@ -198,12 +172,12 @@ const UnsupportedDocumentScreen: React.FC<UnsupportedDocumentScreenProps> = ({
|
||||
>
|
||||
<PrimaryButton
|
||||
onPress={onNotifyMe}
|
||||
trackEvent={PassportEvents.NOTIFY_UNSUPPORTED_PASSPORT}
|
||||
trackEvent={PassportEvents.NOTIFY_COMING_SOON}
|
||||
>
|
||||
Sign up for updates
|
||||
</PrimaryButton>
|
||||
<SecondaryButton
|
||||
trackEvent={PassportEvents.DISMISS_UNSUPPORTED_PASSPORT}
|
||||
trackEvent={PassportEvents.DISMISS_COMING_SOON}
|
||||
onPress={onDismiss}
|
||||
>
|
||||
Dismiss
|
||||
@@ -213,4 +187,4 @@ const UnsupportedDocumentScreen: React.FC<UnsupportedDocumentScreenProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default UnsupportedDocumentScreen;
|
||||
export default ComingSoonScreen;
|
||||
197
app/src/screens/document/CountryPickerScreen.tsx
Normal file
197
app/src/screens/document/CountryPickerScreen.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
// 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 { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FlatList, TouchableOpacity, View } from 'react-native';
|
||||
import { Spinner, XStack, YStack } from 'tamagui';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import { commonNames } from '@selfxyz/common/constants/countries';
|
||||
import { SdkEvents, useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
|
||||
import { RoundFlag } from '@/components/flag/RoundFlag';
|
||||
import { DocumentFlowNavBar } from '@/components/NavBar/DocumentFlowNavBar';
|
||||
import { BodyText } from '@/components/typography/BodyText';
|
||||
import type { RootStackParamList } from '@/navigation';
|
||||
import { black, slate100, slate500 } from '@/utils/colors';
|
||||
import { advercase } from '@/utils/fonts';
|
||||
import { buttonTap } from '@/utils/haptic';
|
||||
|
||||
interface CountryData {
|
||||
[countryCode: string]: string[];
|
||||
}
|
||||
|
||||
interface CountryListItem {
|
||||
key: string;
|
||||
countryCode: string;
|
||||
}
|
||||
|
||||
const ITEM_HEIGHT = 65;
|
||||
const FLAG_SIZE = 32;
|
||||
|
||||
const CountryItem = memo<{
|
||||
countryCode: string;
|
||||
onSelect: (code: string) => void;
|
||||
}>(({ countryCode, onSelect }) => {
|
||||
const countryName = commonNames[countryCode as keyof typeof commonNames];
|
||||
|
||||
if (!countryName) return null;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => onSelect(countryCode)}
|
||||
style={{
|
||||
paddingVertical: 13,
|
||||
}}
|
||||
>
|
||||
<XStack alignItems="center" gap={16}>
|
||||
<RoundFlag countryCode={countryCode} size={FLAG_SIZE} />
|
||||
<BodyText fontSize={16} color={black} flex={1}>
|
||||
{countryName}
|
||||
</BodyText>
|
||||
</XStack>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
});
|
||||
|
||||
CountryItem.displayName = 'CountryItem';
|
||||
|
||||
const CountryPickerScreen: React.FC = () => {
|
||||
const [countryData, setCountryData] = useState<CountryData>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
const selfClient = useSelfClient();
|
||||
|
||||
const onPressCountry = useCallback(
|
||||
(countryCode: string) => {
|
||||
buttonTap();
|
||||
if (__DEV__) {
|
||||
console.log('Selected country code:', countryCode);
|
||||
console.log('Current countryData:', countryData);
|
||||
console.log('Available country codes:', Object.keys(countryData));
|
||||
}
|
||||
const documentTypes = countryData[countryCode];
|
||||
if (__DEV__) {
|
||||
console.log('documentTypes for', countryCode, ':', documentTypes);
|
||||
}
|
||||
|
||||
if (documentTypes && documentTypes.length > 0) {
|
||||
const countryName =
|
||||
commonNames[countryCode as keyof typeof commonNames] || countryCode;
|
||||
|
||||
// Emit the country selection event
|
||||
selfClient.emit(SdkEvents.DOCUMENT_COUNTRY_SELECTED, {
|
||||
countryCode: countryCode,
|
||||
countryName: countryName,
|
||||
documentTypes: documentTypes,
|
||||
});
|
||||
|
||||
navigation.navigate('IDPicker', { countryCode, documentTypes });
|
||||
} else {
|
||||
navigation.navigate('ComingSoon', { countryCode });
|
||||
}
|
||||
},
|
||||
[countryData, navigation, selfClient],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCountryData = async () => {
|
||||
try {
|
||||
const response = await fetch('https://api.staging.self.xyz/id-picker');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
setCountryData(result.data);
|
||||
if (__DEV__) {
|
||||
console.log('Set country data:', result.data);
|
||||
}
|
||||
} else {
|
||||
console.error('API returned non-success status:', result.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching country data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchCountryData();
|
||||
}, []);
|
||||
|
||||
const countryList = useMemo(
|
||||
() =>
|
||||
Object.keys(countryData).map(countryCode => ({
|
||||
key: countryCode,
|
||||
countryCode,
|
||||
})),
|
||||
[countryData],
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item }: { item: CountryListItem }) => (
|
||||
<CountryItem countryCode={item.countryCode} onSelect={onPressCountry} />
|
||||
),
|
||||
[onPressCountry],
|
||||
);
|
||||
|
||||
const keyExtractor = useCallback(
|
||||
(item: CountryListItem) => item.countryCode,
|
||||
[],
|
||||
);
|
||||
|
||||
const renderLoadingState = () => (
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Spinner size="small" />
|
||||
</View>
|
||||
);
|
||||
|
||||
const getItemLayout = useCallback(
|
||||
(
|
||||
_data: ReadonlyArray<CountryListItem> | null | undefined,
|
||||
index: number,
|
||||
) => ({
|
||||
length: ITEM_HEIGHT,
|
||||
offset: ITEM_HEIGHT * index,
|
||||
index,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<YStack flex={1} backgroundColor={slate100}>
|
||||
<DocumentFlowNavBar title="GETTING STARTED" />
|
||||
<YStack flex={1} paddingTop="$4" paddingHorizontal="$4">
|
||||
<YStack marginTop="$4" marginBottom="$6">
|
||||
<BodyText fontSize={29} fontFamily={advercase}>
|
||||
Select the country that issued your ID
|
||||
</BodyText>
|
||||
<BodyText fontSize={16} color={slate500} marginTop="$3">
|
||||
Self has support for over 300 ID types. You can select the type of
|
||||
ID in the next step
|
||||
</BodyText>
|
||||
</YStack>
|
||||
{loading ? (
|
||||
renderLoadingState()
|
||||
) : (
|
||||
<FlatList
|
||||
data={countryList}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
showsVerticalScrollIndicator={false}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={10}
|
||||
windowSize={10}
|
||||
initialNumToRender={10}
|
||||
updateCellsBatchingPeriod={50}
|
||||
getItemLayout={getItemLayout}
|
||||
/>
|
||||
)}
|
||||
</YStack>
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CountryPickerScreen;
|
||||
@@ -48,7 +48,7 @@ const DocumentOnboardingScreen: React.FC = () => {
|
||||
onAnimationFinish={() => {
|
||||
setTimeout(() => {
|
||||
animationRef.current?.play();
|
||||
}, 5000); // Pause 5 seconds before playing again
|
||||
}, 220);
|
||||
}}
|
||||
source={passportOnboardingAnimation}
|
||||
style={styles.animation}
|
||||
|
||||
207
app/src/screens/document/IDPickerScreen.tsx
Normal file
207
app/src/screens/document/IDPickerScreen.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
// 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 { View, XStack, YStack } from 'tamagui';
|
||||
import type { RouteProp } from '@react-navigation/native';
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import { SdkEvents, useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
|
||||
import { RoundFlag } from '@/components/flag/RoundFlag';
|
||||
import { DocumentFlowNavBar } from '@/components/NavBar/DocumentFlowNavBar';
|
||||
import { BodyText } from '@/components/typography/BodyText';
|
||||
import AadhaarLogo from '@/images/icons/aadhaar.svg';
|
||||
import EPassportLogoRounded from '@/images/icons/epassport_rounded.svg';
|
||||
import PlusIcon from '@/images/icons/plus.svg';
|
||||
import SelfLogo from '@/images/logo.svg';
|
||||
import { useSafeAreaInsets } from '@/mocks/react-native-safe-area-context';
|
||||
import type { RootStackParamList } from '@/navigation';
|
||||
import { black, slate100, slate300, slate400, white } from '@/utils/colors';
|
||||
import { extraYPadding } from '@/utils/constants';
|
||||
import { advercase, dinot } from '@/utils/fonts';
|
||||
import { buttonTap } from '@/utils/haptic';
|
||||
|
||||
type IDPickerScreenRouteProp = RouteProp<RootStackParamList, 'IDPicker'>;
|
||||
|
||||
const getDocumentName = (docType: string): string => {
|
||||
switch (docType) {
|
||||
case 'p':
|
||||
return 'Passport';
|
||||
case 'i':
|
||||
return 'ID card';
|
||||
case 'a':
|
||||
return 'Aadhaar';
|
||||
default:
|
||||
return 'Unknown Document';
|
||||
}
|
||||
};
|
||||
|
||||
const getDocumentNameForEvent = (docType: string): string => {
|
||||
switch (docType) {
|
||||
case 'p':
|
||||
return 'passport';
|
||||
case 'i':
|
||||
return 'id_card';
|
||||
case 'a':
|
||||
return 'aadhaar';
|
||||
default:
|
||||
return 'unknown_document';
|
||||
}
|
||||
};
|
||||
|
||||
const getDocumentDescription = (docType: string): string => {
|
||||
switch (docType) {
|
||||
case 'p':
|
||||
return 'Verified Biometric Passport';
|
||||
case 'i':
|
||||
return 'Verified Biometric ID card';
|
||||
case 'a':
|
||||
return 'Verified mAadhaar QR code';
|
||||
default:
|
||||
return 'Unknown Document';
|
||||
}
|
||||
};
|
||||
|
||||
const getDocumentLogo = (docType: string): React.ReactNode => {
|
||||
switch (docType) {
|
||||
case 'p':
|
||||
return <EPassportLogoRounded />;
|
||||
case 'i':
|
||||
return <EPassportLogoRounded />;
|
||||
case 'a':
|
||||
return <AadhaarLogo />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const IDPickerScreen: React.FC = () => {
|
||||
const route = useRoute<IDPickerScreenRouteProp>();
|
||||
const { countryCode = '', documentTypes = [] } = route.params || {};
|
||||
const bottom = useSafeAreaInsets().bottom;
|
||||
const selfClient = useSelfClient();
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
|
||||
const onSelectDocumentType = (docType: string) => {
|
||||
buttonTap();
|
||||
|
||||
const countryName = getDocumentName(docType);
|
||||
|
||||
selfClient.emit(SdkEvents.DOCUMENT_TYPE_SELECTED, {
|
||||
documentType: docType,
|
||||
documentName: getDocumentNameForEvent(docType),
|
||||
countryCode: countryCode,
|
||||
countryName: countryName,
|
||||
});
|
||||
switch (docType) {
|
||||
case 'p':
|
||||
navigation.navigate('DocumentOnboarding');
|
||||
break;
|
||||
case 'i':
|
||||
navigation.navigate('DocumentOnboarding');
|
||||
break;
|
||||
case 'a':
|
||||
navigation.navigate('AadhaarUpload', { countryCode } as never);
|
||||
break;
|
||||
default:
|
||||
navigation.navigate('ComingSoon', { countryCode } as never);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Navigate to the next screen based on document type
|
||||
if (__DEV__) {
|
||||
console.log(
|
||||
`Selected document type: ${docType} for country: ${countryCode}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<YStack
|
||||
flex={1}
|
||||
backgroundColor={slate100}
|
||||
paddingBottom={bottom + extraYPadding + 24}
|
||||
>
|
||||
<DocumentFlowNavBar title="GETTING STARTED" />
|
||||
<YStack
|
||||
flex={1}
|
||||
paddingTop="$4"
|
||||
paddingHorizontal="$4"
|
||||
justifyContent="center"
|
||||
>
|
||||
<YStack marginTop="$4" marginBottom="$6">
|
||||
<XStack
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
borderRadius={'$2'}
|
||||
gap={'$2.5'}
|
||||
>
|
||||
<View width={48} height={48}>
|
||||
<RoundFlag countryCode={countryCode} size={48} />
|
||||
</View>
|
||||
<PlusIcon width={18} height={18} color={slate400} />
|
||||
<YStack
|
||||
backgroundColor={black}
|
||||
borderRadius={'$2'}
|
||||
height={48}
|
||||
width={48}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<SelfLogo width={24} height={24} />
|
||||
</YStack>
|
||||
</XStack>
|
||||
<BodyText
|
||||
marginTop="$6"
|
||||
fontSize={29}
|
||||
fontFamily={advercase}
|
||||
textAlign="center"
|
||||
>
|
||||
Select an ID type
|
||||
</BodyText>
|
||||
</YStack>
|
||||
<YStack gap="$3">
|
||||
{documentTypes.map((docType: string) => (
|
||||
<XStack
|
||||
key={docType}
|
||||
backgroundColor={white}
|
||||
borderWidth={1}
|
||||
borderColor={slate300}
|
||||
elevation={4}
|
||||
borderRadius={'$5'}
|
||||
padding={'$3'}
|
||||
pressStyle={{ scale: 0.97, backgroundColor: slate100 }}
|
||||
onPress={() => onSelectDocumentType(docType)}
|
||||
>
|
||||
<XStack alignItems="center" gap={'$3'} flex={1}>
|
||||
{getDocumentLogo(docType)}
|
||||
<YStack gap={'$1'}>
|
||||
<BodyText fontSize={24} fontFamily={dinot} color={black}>
|
||||
{getDocumentName(docType)}
|
||||
</BodyText>
|
||||
<BodyText fontSize={14} fontFamily={dinot} color="#9193A2">
|
||||
{getDocumentDescription(docType)}
|
||||
</BodyText>
|
||||
</YStack>
|
||||
</XStack>
|
||||
</XStack>
|
||||
))}
|
||||
<BodyText
|
||||
fontSize={18}
|
||||
fontFamily={dinot}
|
||||
color={slate400}
|
||||
textAlign="center"
|
||||
>
|
||||
Be sure your document is ready to scan
|
||||
</BodyText>
|
||||
</YStack>
|
||||
</YStack>
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default IDPickerScreen;
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import LottieView from 'lottie-react-native';
|
||||
import LottieView, { type LottieViewProps } from 'lottie-react-native';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Linking, StyleSheet, View } from 'react-native';
|
||||
import { SystemBars } from 'react-native-edge-to-edge';
|
||||
@@ -48,7 +48,8 @@ const SuccessScreen: React.FC = () => {
|
||||
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
const [animationSource, setAnimationSource] = useState<any>(loadingAnimation);
|
||||
const [animationSource, setAnimationSource] =
|
||||
useState<LottieViewProps['source']>(loadingAnimation);
|
||||
const [countdown, setCountdown] = useState<number | null>(null);
|
||||
const [countdownStarted, setCountdownStarted] = useState(false);
|
||||
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
@@ -111,7 +111,7 @@ const AccountRecoveryChoiceScreen: React.FC = () => {
|
||||
onRestoreFromCloudNext,
|
||||
navigation,
|
||||
toggleCloudBackupEnabled,
|
||||
selfClient,
|
||||
useProtocolStore,
|
||||
]);
|
||||
|
||||
const handleManualRecoveryPress = useCallback(() => {
|
||||
|
||||
@@ -102,7 +102,7 @@ const RecoverWithPhraseScreen: React.FC = () => {
|
||||
navigation,
|
||||
restoreAccountFromMnemonic,
|
||||
trackEvent,
|
||||
selfClient,
|
||||
useProtocolStore,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -280,10 +280,10 @@ const ManageDocumentsScreen: React.FC = () => {
|
||||
trackEvent(DocumentEvents.MANAGE_SCREEN_OPENED);
|
||||
}, [trackEvent]);
|
||||
|
||||
const handleScanDocument = () => {
|
||||
const handleAddDocument = () => {
|
||||
impactLight();
|
||||
trackEvent(DocumentEvents.ADD_NEW_SCAN_SELECTED);
|
||||
navigation.navigate('DocumentOnboarding');
|
||||
navigation.navigate('CountryPicker');
|
||||
};
|
||||
|
||||
const handleGenerateMock = () => {
|
||||
@@ -292,12 +292,6 @@ const ManageDocumentsScreen: React.FC = () => {
|
||||
navigation.navigate('CreateMock');
|
||||
};
|
||||
|
||||
const handleAddAadhaar = () => {
|
||||
impactLight();
|
||||
trackEvent(DocumentEvents.ADD_NEW_AADHAAR_SELECTED);
|
||||
navigation.navigate('AadhaarUpload');
|
||||
};
|
||||
|
||||
return (
|
||||
<YStack
|
||||
flex={1}
|
||||
@@ -322,11 +316,8 @@ const ManageDocumentsScreen: React.FC = () => {
|
||||
</Text>
|
||||
|
||||
<ButtonsContainer>
|
||||
<PrimaryButton onPress={handleScanDocument}>
|
||||
Scan New ID Document
|
||||
</PrimaryButton>
|
||||
<PrimaryButton onPress={handleAddAadhaar}>
|
||||
Add Aadhaar
|
||||
<PrimaryButton onPress={handleAddDocument}>
|
||||
Add New Document
|
||||
</PrimaryButton>
|
||||
<SecondaryButton onPress={handleGenerateMock}>
|
||||
Generate Mock Document
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import React from 'react';
|
||||
import { Linking, StyleSheet, View } from 'react-native';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Anchor, Text, YStack } from 'tamagui';
|
||||
@@ -13,18 +13,23 @@ import { AppEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
import AbstractButton from '@/components/buttons/AbstractButton';
|
||||
import { BodyText } from '@/components/typography/BodyText';
|
||||
import { Caption } from '@/components/typography/Caption';
|
||||
import { privacyUrl, supportedBiometricIdsUrl, termsUrl } from '@/consts/links';
|
||||
import { privacyUrl, termsUrl } from '@/consts/links';
|
||||
import useConnectionModal from '@/hooks/useConnectionModal';
|
||||
import useHapticNavigation from '@/hooks/useHapticNavigation';
|
||||
import Logo from '@/images/logo.svg';
|
||||
import { black, slate400, white, zinc800, zinc900 } from '@/utils/colors';
|
||||
import { extraYPadding } from '@/utils/constants';
|
||||
import IDCardPlaceholder from '@/images/icons/id_card_placeholder.svg';
|
||||
import {
|
||||
black,
|
||||
red500,
|
||||
slate300,
|
||||
slate400,
|
||||
white,
|
||||
zinc800,
|
||||
} from '@/utils/colors';
|
||||
import { advercase, dinot } from '@/utils/fonts';
|
||||
|
||||
const LaunchScreen: React.FC = () => {
|
||||
useConnectionModal();
|
||||
const onStartPress = useHapticNavigation('DocumentOnboarding');
|
||||
const onAadhaarPress = useHapticNavigation('AadhaarUpload');
|
||||
const onPress = useHapticNavigation('CountryPicker');
|
||||
const createMock = useHapticNavigation('CreateMock');
|
||||
const { bottom } = useSafeAreaInsets();
|
||||
|
||||
@@ -35,75 +40,59 @@ const LaunchScreen: React.FC = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<YStack
|
||||
backgroundColor={black}
|
||||
flex={1}
|
||||
alignItems="center"
|
||||
paddingHorizontal={20}
|
||||
paddingBottom={bottom + extraYPadding}
|
||||
>
|
||||
<YStack backgroundColor={black} flex={1} alignItems="center">
|
||||
<View style={styles.container}>
|
||||
<View style={styles.card}>
|
||||
<YStack flex={1} justifyContent="center" alignItems="center">
|
||||
<GestureDetector gesture={devModeTap}>
|
||||
<View style={styles.logoSection}>
|
||||
<Logo style={styles.logo} />
|
||||
</View>
|
||||
<YStack
|
||||
backgroundColor={red500}
|
||||
borderRadius={14}
|
||||
overflow="hidden"
|
||||
>
|
||||
<IDCardPlaceholder width={300} height={180} />
|
||||
</YStack>
|
||||
</GestureDetector>
|
||||
|
||||
<Text style={styles.title}>Get started</Text>
|
||||
|
||||
<BodyText style={styles.description}>
|
||||
Register with Self using your passport, biometric ID or Aadhaar card
|
||||
to prove your identity across the web without revealing your
|
||||
personal information.
|
||||
</BodyText>
|
||||
</View>
|
||||
</YStack>
|
||||
<Text
|
||||
color={white}
|
||||
fontSize={38}
|
||||
fontFamily={advercase}
|
||||
fontWeight="500"
|
||||
textAlign="center"
|
||||
marginBottom={16}
|
||||
>
|
||||
Take control of your digital identity
|
||||
</Text>
|
||||
<BodyText
|
||||
color={slate300}
|
||||
fontSize={16}
|
||||
textAlign="center"
|
||||
marginHorizontal={40}
|
||||
marginBottom={40}
|
||||
>
|
||||
Self is the easiest way to verify your identity safely wherever you
|
||||
are.
|
||||
</BodyText>
|
||||
</View>
|
||||
|
||||
<YStack
|
||||
gap="$3"
|
||||
width="100%"
|
||||
alignItems="center"
|
||||
marginBottom={20}
|
||||
marginTop={24}
|
||||
paddingHorizontal={20}
|
||||
paddingBottom={bottom}
|
||||
paddingTop={30}
|
||||
backgroundColor={zinc800}
|
||||
>
|
||||
<YStack gap="$3" width="100%">
|
||||
<AbstractButton
|
||||
bgColor={black}
|
||||
borderColor={zinc800}
|
||||
borderWidth={1}
|
||||
color={white}
|
||||
trackEvent={AppEvents.SUPPORTED_BIOMETRIC_IDS}
|
||||
onPress={async () => {
|
||||
try {
|
||||
await Linking.openURL(supportedBiometricIdsUrl);
|
||||
} catch (error) {
|
||||
console.warn('Failed to open supported IDs URL:', error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
List of Supported Biometric IDs
|
||||
</AbstractButton>
|
||||
|
||||
<AbstractButton
|
||||
trackEvent={AppEvents.GET_STARTED_BIOMETRIC}
|
||||
onPress={onStartPress}
|
||||
bgColor={white}
|
||||
color={black}
|
||||
testID="launch-get-started-button"
|
||||
>
|
||||
I have a Passport or Biometric ID
|
||||
</AbstractButton>
|
||||
<AbstractButton
|
||||
trackEvent={AppEvents.GET_STARTED_AADHAAR}
|
||||
onPress={onAadhaarPress}
|
||||
bgColor={white}
|
||||
color={black}
|
||||
testID="launch-get-started-button"
|
||||
>
|
||||
I have an Aadhaar Card
|
||||
</AbstractButton>
|
||||
</YStack>
|
||||
<AbstractButton
|
||||
trackEvent={AppEvents.GET_STARTED}
|
||||
onPress={onPress}
|
||||
bgColor={white}
|
||||
color={black}
|
||||
testID="launch-get-started-button"
|
||||
>
|
||||
Get Started
|
||||
</AbstractButton>
|
||||
|
||||
<Caption style={styles.notice}>
|
||||
By continuing, you agree to the
|
||||
@@ -125,8 +114,8 @@ export default LaunchScreen;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 0,
|
||||
justifyContent: 'flex-start',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '102%',
|
||||
paddingTop: '30%',
|
||||
@@ -138,7 +127,6 @@ const styles = StyleSheet.create({
|
||||
paddingVertical: 40,
|
||||
paddingHorizontal: 20,
|
||||
alignItems: 'center',
|
||||
backgroundColor: zinc900,
|
||||
shadowColor: black,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.2,
|
||||
@@ -157,25 +145,11 @@ const styles = StyleSheet.create({
|
||||
width: 40,
|
||||
height: 40,
|
||||
},
|
||||
title: {
|
||||
fontFamily: advercase,
|
||||
fontSize: 38,
|
||||
fontWeight: '500',
|
||||
color: white,
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
description: {
|
||||
color: white,
|
||||
fontSize: 16,
|
||||
lineHeight: 22,
|
||||
textAlign: 'center',
|
||||
marginBottom: 8,
|
||||
},
|
||||
|
||||
notice: {
|
||||
fontFamily: dinot,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
color: slate400,
|
||||
textAlign: 'center',
|
||||
lineHeight: 18,
|
||||
|
||||
@@ -113,7 +113,7 @@ const SplashScreen: React.FC = ({}) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [isAnimationFinished, nextScreen, queuedDeepLink, navigation]);
|
||||
}, [isAnimationFinished, nextScreen, queuedDeepLink, navigation, selfClient]);
|
||||
|
||||
return (
|
||||
<LottieView
|
||||
|
||||
@@ -41,11 +41,15 @@ export const sendCountrySupportNotification = async ({
|
||||
['documentCategory', documentCategory || 'Unknown'],
|
||||
['tz', getTimeZone()],
|
||||
['ts', new Date().toISOString()],
|
||||
['origin', 'unsupported_passport_screen'],
|
||||
['origin', 'coming_soon_screen'],
|
||||
] as [string, string][];
|
||||
|
||||
const documentTypeText =
|
||||
documentCategory === 'id_card' ? 'ID cards' : 'passports';
|
||||
documentCategory === 'id_card'
|
||||
? 'ID cards'
|
||||
: documentCategory === 'passport'
|
||||
? 'passports'
|
||||
: 'documents';
|
||||
|
||||
const body = `Hi SELF Team,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user