Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { YStack } from 'tamagui';
import AppNavigation from './src/Navigation';
import { initSentry, wrapWithSentry } from './src/Sentry';
import { AuthProvider } from './src/stores/authProvider';
import { DatabaseProvider } from './src/stores/databaseProvider';
import { PassportProvider } from './src/stores/passportDataProvider';

initSentry();
Expand All @@ -18,7 +19,9 @@ function App(): React.JSX.Element {
<YStack f={1} h="100%" w="100%">
<AuthProvider>
<PassportProvider>
<AppNavigation />
<DatabaseProvider>
<AppNavigation />
</DatabaseProvider>
</PassportProvider>
</AuthProvider>
</YStack>
Expand Down
2 changes: 2 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"react-native-quick-crypto": "^0.7.12",
"react-native-safe-area-context": "^5.2.0",
"react-native-screens": "^4.6.0",
"react-native-sqlite-storage": "^6.0.1",
"react-native-svg": "^15.11.1",
"socket.io-client": "^4.7.5",
"tamagui": "1.110.0",
Expand All @@ -118,6 +119,7 @@
"@types/react": "^18.2.6",
"@types/react-native": "^0.73.0",
"@types/react-native-dotenv": "^0.2.0",
"@types/react-native-sqlite-storage": "^6.0.5",
"eslint": "^8.19.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
Expand Down
15 changes: 15 additions & 0 deletions app/src/Navigation/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { NativeStackNavigationOptions } from '@react-navigation/native-stack';

import ProofHistoryDetailScreen from '../screens/ProofHistoryDetailScreen';
import ProofHistoryScreen from '../screens/ProofHistoryScreen';
import CloudBackupScreen from '../screens/Settings/CloudBackupScreen';
import DevSettingsScreen from '../screens/Settings/DevSettingsScreen';
import PassportDataInfoScreen from '../screens/Settings/PassportDataInfoScreen';
Expand Down Expand Up @@ -64,6 +66,19 @@ const settingsScreens = {
},
} as NativeStackNavigationOptions,
},
ProofHistory: {
screen: ProofHistoryScreen,
options: {
title: 'Approved Requests',
navigationBarColor: black,
},
},
ProofHistoryDetail: {
screen: ProofHistoryDetailScreen,
options: {
title: 'Approval',
},
},
};

export default settingsScreens;
306 changes: 306 additions & 0 deletions app/src/screens/ProofHistoryDetailScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
import { CheckSquare2, Info, Wallet } from '@tamagui/lucide-icons';
import React from 'react';
import { useMemo } from 'react';
import { ScrollView, StatusBar, StyleSheet } from 'react-native';
import { Card, Image, Text, XStack, YStack } from 'tamagui';

import { ProofHistory, ProofStatus } from '../stores/proofHistoryStore';
import {
black,
blue100,
blue600,
blue700,
emerald500,
red500,
slate100,
slate200,
slate400,
slate700,
white,
zinc400,
} from '../utils/colors';

type ProofHistoryDetailScreenProps = {
route: {
params: {
data: ProofHistory;
};
};
};

enum DisclosureType {
NAME = 'name',
OFAC = 'ofac',
AGE = 'age',
ISSUING_STATE = 'issuing_state',
PASSPORT_NUMBER = 'passport_number',
NATIONALITY = 'nationality',
DATE_OF_BIRTH = 'date_of_birth',
GENDER = 'gender',
EXPIRY_DATE = 'expiry_date',
EXCLUDED_COUNTRIES = 'excludedCountries',
MINIMUM_AGE = 'minimumAge',
}

const ProofHistoryDetailScreen: React.FC<ProofHistoryDetailScreenProps> = ({
route,
}) => {
const { data } = route.params;
const disclosures = useMemo(() => {
const parsedDisclosures = JSON.parse(data.disclosures);
const result: string[] = [];

Object.entries(parsedDisclosures).forEach(([key, value]) => {
if (key == DisclosureType.MINIMUM_AGE && value) {
result.push(`Age is over ${value}`);
}
if (key == DisclosureType.NAME && value) {
result.push(`Disclosed Name to ${data.appName}`);
}
if (key == DisclosureType.OFAC && value) {
result.push(`Not on the OFAC list`);
}
if (key == DisclosureType.AGE && value) {
result.push(`Disclosed Age to ${data.appName}`);
}
if (key == DisclosureType.ISSUING_STATE && value) {
result.push(`Disclosed Issuing State to ${data.appName}`);
}
if (key == DisclosureType.PASSPORT_NUMBER && value) {
result.push(`Disclosed Passport Number to ${data.appName}`);
}
if (key == DisclosureType.NATIONALITY && value) {
result.push(`Disclosed Nationality to ${data.appName}`);
}
if (key == DisclosureType.DATE_OF_BIRTH && value) {
result.push(`Disclosed Date of Birth to ${data.appName}`);
}
if (key == DisclosureType.GENDER && value) {
result.push(`Disclosed Gender to ${data.appName}`);
}
if (key == DisclosureType.EXPIRY_DATE && value) {
result.push(`Disclosed Expiry Date to ${data.appName}`);
}
if (key == DisclosureType.EXCLUDED_COUNTRIES) {
if (value && Array.isArray(value) && value.length > 0) {
result.push(`Disclosed - Not from excluded countries`);
}
}
});
return result;
}, [data.disclosures]);

const formattedDate = useMemo(() => {
return new Date(data.timestamp).toLocaleString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
}, [data.timestamp]);

const proofStatus = useMemo(() => {
if (data.status == 'success') {
return 'PROOF GRANTED';
} else if (data.status == ProofStatus.PENDING) {
return 'PROOF PENDING';
} else {
return 'PROOF FAILED';
}
}, [data.status]);

const logoSource = useMemo(() => {
if (!data.logoBase64) {
return null;
}

if (
data.logoBase64.startsWith('http://') ||
data.logoBase64.startsWith('https://')
) {
return { uri: data.logoBase64 };
}

const base64String = data.logoBase64.startsWith('data:image')
? data.logoBase64
: `data:image/png;base64,${data.logoBase64}`;
return { uri: base64String };
}, [data.logoBase64]);

const isEthereumAddress = useMemo(() => {
return (
/^0x[a-fA-F0-9]+$/.test(data.userId) &&
(data.endpointType == 'staging_celo' || data.endpointType == 'celo') &&
data.userIdType == 'hex'
);
}, [data.userId, data.endpointType, data.userIdType]);

return (
<YStack flex={1} backgroundColor={white}>
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
<YStack flex={1} padding={20}>
<YStack
backgroundColor={black}
borderBottomLeftRadius={0}
borderBottomRightRadius={0}
borderTopLeftRadius={10}
borderTopRightRadius={10}
paddingBottom={20}
>
<YStack alignItems="center" gap={12} marginTop={40}>
{logoSource && (
<Image
source={logoSource}
width={60}
height={60}
borderRadius={16}
objectFit="contain"
/>
)}
<Text color={white} fontSize={32} fontWeight="600">
{data.appName}
</Text>
<Text color={zinc400} fontSize={16}>
{data.appName}
</Text>
</YStack>

<YStack alignItems="center" paddingHorizontal={20} marginTop={20}>
<Text
color={zinc400}
fontSize={16}
textAlign="center"
fontWeight="500"
>
{data.appName} was granted access to the following information
from your verified Passport.
</Text>
</YStack>
</YStack>

<YStack
backgroundColor={blue100}
paddingVertical={12}
paddingHorizontal={20}
>
<XStack alignItems="center" gap={8}>
<CheckSquare2 color={blue600} size={12} />
<Text color={blue600} fontSize={12} fontWeight="500">
{proofStatus}
</Text>
<Text color={blue600} fontSize={12} marginLeft="auto">
{formattedDate}
</Text>
</XStack>
</YStack>

<Card
backgroundColor={slate100}
elevation={1}
padding={20}
gap={20}
borderTopLeftRadius={0}
borderTopRightRadius={0}
borderBottomLeftRadius={10}
borderBottomRightRadius={10}
>
<YStack gap={10}>
<YStack
backgroundColor={isEthereumAddress ? blue600 : white}
paddingTop={12}
paddingBottom={12}
paddingLeft={10}
paddingRight={6}
borderRadius={4}
style={
isEthereumAddress
? styles.connectedWalletContainer
: styles.walletContainer
}
>
<XStack
backgroundColor={isEthereumAddress ? blue700 : slate100}
paddingVertical={4}
paddingHorizontal={6}
borderRadius={4}
alignItems="center"
gap={8}
>
<Wallet
color={isEthereumAddress ? white : zinc400}
size={12}
/>
<Text
color={isEthereumAddress ? white : zinc400}
fontSize={12}
fontWeight="500"
>
{isEthereumAddress
? 'CONNECTED WALLET ADDRESS'
: 'NO CONNECTED WALLET'}
</Text>
{isEthereumAddress && (
<Text
color={white}
fontSize={12}
marginLeft="auto"
fontWeight="500"
ellipsizeMode="tail"
>
{data.userId.slice(0, 2)}...{data.userId.slice(-4)}
</Text>
)}
</XStack>
</YStack>

<YStack gap={16}>
{disclosures.map((disclosure, index) => (
<XStack
key={index}
backgroundColor={slate100}
paddingVertical={16}
paddingHorizontal={20}
borderRadius={12}
alignItems="center"
>
<YStack
backgroundColor={
data.status == ProofStatus.SUCCESS ? emerald500 : red500
}
width={8}
height={8}
borderRadius={4}
marginRight={12}
/>
<Text
color={slate700}
fontSize={12}
flex={1}
fontWeight="500"
letterSpacing={0.4}
>
{disclosure}
</Text>
<Info color={blue600} size={16} />
</XStack>
))}
</YStack>
</YStack>
</Card>
</YStack>
</ScrollView>
</YStack>
);
};

const styles = StyleSheet.create({
walletContainer: {
shadowColor: blue600,
},
connectedWalletContainer: {
shadowColor: blue700,
},
});

export default ProofHistoryDetailScreen;
Loading
Loading