add NextScreen and RegisterScreen

This commit is contained in:
turnoffthiscomputer
2024-06-10 18:10:19 +02:00
committed by 0xturboblitz
parent 852ecfd617
commit b08e090e08
10 changed files with 353 additions and 378 deletions

View File

@@ -37,7 +37,7 @@ export const sbtApp: AppType = {
selectable: true,
icon: Flame,
tags: [sepolia()],
// ProveScreen UI
name: 'Soulbound token',
disclosureOptions: {
@@ -45,7 +45,7 @@ export const sbtApp: AppType = {
expiry_date: "optional",
older_than: "optional"
},
// SendProofScreen UI before sending proof
beforeSendText1: "You can now use this proof to mint a Soulbound token.",
beforeSendText2: "Disclosed information will be displayed on SBT.",
@@ -62,14 +62,14 @@ export const sbtApp: AppType = {
return (
<Pressable onPress={() => {
Clipboard.setString(txHash);
toast?.show('🖨️', {
message: "Tx copied to clipboard",
customData: {
type: "success",
},
})
}}
Clipboard.setString(txHash);
toast?.show('🖨️', {
message: "Tx copied to clipboard",
customData: {
type: "success",
},
})
}}
>
<XStack jc='space-between' h="$2" ai="center">
<Text color={textColor1} fontWeight="bold" fontSize="$5">
@@ -122,9 +122,9 @@ export const sbtApp: AppType = {
} = useUserStore.getState();
setStep(Steps.GENERATING_PROOF);
const reveal_bitmap = revealBitmapFromMapping(disclosure);
const response = await axios.get(COMMITMENT_TREE_TRACKER_URL)
// const serializedCommitmentTree = "[[\"9366833337168993085050982292715343583458999801189875133285760454940954329736\",\"17067815450997614268337156469331439256078702232208444991806942459610897177755\",\"6218618977460894587557092460164616095207478656436068295742870309857616419830\",\"1009498555512750055176786258919772755314598234878788682229429740456064488924\",\"2317777252282411584898482846587421326341858131145081778162865818517424463113\",\"14350861400343175672772758664935358862843556622155842278173685659399974430673\"],[\"5757843324860707578753413472099376283217223062835733089254074659436006978958\",\"9384382887555344903988763589988369409408141218078864334664000402547342440893\",\"20714514634358291855499138323356766695315870633431415798546884765927810445680\"],[\"6444500081923737565029349850782686417529434309028817508928891238372057960879\",\"20714514634358291855499138323356766695315870633431415798546884765927810445680\"],[\"13949165376611379310020797746578693825960496340786495286952352659551479278661\"]]"
console.log('response.data:', response.data);
@@ -149,7 +149,7 @@ export const sbtApp: AppType = {
);
console.log('inputs:', inputs);
const start = Date.now();
const proof = await generateProof(
@@ -173,7 +173,7 @@ export const sbtApp: AppType = {
type: "error",
},
})
setStep(Steps.NFC_SCAN_COMPLETED);
setStep(Steps.NEXT_SCREEN);
amplitude.track(error.message);
}
},
@@ -188,15 +188,15 @@ export const sbtApp: AppType = {
toast,
setStep
} = useNavigationStore.getState();
if (!proof) {
console.error('Proof is not generated');
return;
}
setStep(Steps.PROOF_SENDING);
toast?.show('🚀',{
toast?.show('🚀', {
message: "Transaction sent...",
customData: {
type: "info",
@@ -214,7 +214,7 @@ export const sbtApp: AppType = {
txHash: txHash,
proofSentText: `SBT minting... Network: Sepolia. Transaction hash: ${txHash}`
});
const receipt = await provider.waitForTransaction(txHash);
console.log('receipt status:', receipt?.status);
@@ -248,7 +248,7 @@ export const sbtApp: AppType = {
if (error.isAxiosError && error.response) {
const errorMessage = error.response.data.error;
console.log('Server error message:', errorMessage);
// parse blockchain error and show it
const match = errorMessage.match(/execution reverted: "([^"]*)"/);
if (match && match[1]) {

View File

@@ -41,7 +41,7 @@ const CameraScreen: React.FC<CameraScreenProps> = ({ sheetIsOpen, setSheetIsOpen
</YStack>
<YStack gap="$2">
<YStack gap="$2" mb="$6">
<Button borderWidth={1.3} borderColor={borderColor} borderRadius="$10" bg="#3185FC" onPress={startCameraScan}><Camera color={textColor1} /></Button>
<Button bg={textColor2} borderColor={borderColor} borderRadius="$10" onPress={() => setSheetIsOpen(true)}><SquarePen /></Button>
</YStack>

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { YStack, XStack, Text, Button, Tabs, Sheet, Label, Fieldset, Input, Switch, H2, Image, useWindowDimensions, H4, H3 } from 'tamagui'
import { HelpCircle, IterationCw, VenetianMask, Cog, CheckCircle2, ChevronLeft, Share } from '@tamagui/lucide-icons';
import { HelpCircle, IterationCw, VenetianMask, Cog, CheckCircle2, ChevronLeft, Share, Eraser } from '@tamagui/lucide-icons';
import X from '../images/x.png'
import Telegram from '../images/telegram.png'
import Github from '../images/github.png'
@@ -20,17 +20,19 @@ import { scan } from '../utils/nfcScanner';
import useNavigationStore from '../stores/navigationStore';
import NfcScreen from './NfcScreen';
import CameraScreen from './CameraScreen';
import NextScreen from './NextScreen';
import { mockPassportData_sha256WithRSAEncryption_65537 } from '../../../common/src/utils/mockPassportData';
import Dialog from "react-native-dialog";
import { contribute } from '../utils/contribute';
import RegisterScreen from './RegisterScreen';
const MainScreen: React.FC = () => {
const [NFCScanIsOpen, setNFCScanIsOpen] = useState(false);
const [displayOtherOptions, setDisplayOtherOptions] = useState(false);
const [SettingsIsOpen, setSettingsIsOpen] = useState(false);
const [DialogContributeIsOpen, setDialogContributeIsOpen] = useState(false);
const [HelpIsOpen, setHelpIsOpen] = useState(false);
const [brokenCamera, setBrokenCamera] = useState(false);
const [sheetIsOpen, setSheetIsOpen] = useState(false);
const {
@@ -42,28 +44,11 @@ const MainScreen: React.FC = () => {
clearPassportDataFromStorage,
clearSecretFromStorage,
registerCommitment,
registerPassportData,
passportData,
secret
registered
} = useUserStore()
const decrementStep = () => {
if (selectedTab === "nfc") {
updateNavigationStore({
selectedTab: "scan",
})
}
else if (selectedTab === "app") {
updateNavigationStore({
selectedTab: "nfc",
})
}
else if (selectedTab === "prove") {
updateNavigationStore({
selectedTab: "app",
})
}
};
const {
showWarningModal,
update: updateNavigationStore,
@@ -82,25 +67,42 @@ const MainScreen: React.FC = () => {
})
deleteMrzFields();
}
const handleSkip = () => {
registerCommitment(
secret,
mockPassportData_sha256WithRSAEncryption_65537
)
update({
passportData: mockPassportData_sha256WithRSAEncryption_65537
})
setStep(Steps.NFC_SCAN_COMPLETED);
deleteMrzFields();
toast?.show("Using mock passport data!", { type: "info" })
}
const handleHideData = () => {
updateNavigationStore({
hideData: !hideData,
})
}
const handleSkip = () => {
registerPassportData(
mockPassportData_sha256WithRSAEncryption_65537
)
update({
passportData: mockPassportData_sha256WithRSAEncryption_65537
})
setStep(Steps.NEXT_SCREEN);
deleteMrzFields();
toast?.show("Using mock passport data!", { type: "info" })
}
const decrementStep = () => {
if (selectedTab === "nfc") {
setStep(Steps.MRZ_SCAN);
}
else if (selectedTab === "next") {
setStep(Steps.MRZ_SCAN_COMPLETED);
}
else if (selectedTab === "register") {
setStep(Steps.NEXT_SCREEN);
}
else if (selectedTab === "prove") {
setStep(Steps.REGISTERED);
}
else if (selectedTab === "mint") {
setStep(Steps.REGISTERED);
}
};
const handleNFCScan = () => {
if ((Platform.OS === 'ios')) {
console.log('ios');
@@ -117,13 +119,19 @@ const MainScreen: React.FC = () => {
setDialogContributeIsOpen(false);
}
useEffect(() => {
if (passportNumber?.length === 9 && (dateOfBirth?.length === 6 && dateOfExpiry?.length === 6)) {
setStep(Steps.MRZ_SCAN_COMPLETED);
}
}, [passportNumber, dateOfBirth, dateOfExpiry]);
useEffect(() => {
if (registered) {
setStep(Steps.REGISTERED);
}
}, [registered]);
useEffect(() => {
let timeoutId: ReturnType<typeof setTimeout>;
if (step == Steps.MRZ_SCAN) {
@@ -142,7 +150,7 @@ const MainScreen: React.FC = () => {
setNFCScanIsOpen(false);
}, 0);
}
else if (step == Steps.NFC_SCAN_COMPLETED) {
else if (step == Steps.NEXT_SCREEN) {
// Set the timeout and store its ID
timeoutId = setTimeout(() => {
setNFCScanIsOpen(false);
@@ -153,7 +161,17 @@ const MainScreen: React.FC = () => {
selectedTab: "mint",
})
}
if (step == Steps.NFC_SCAN_COMPLETED) {
if (step == Steps.NEXT_SCREEN) {
updateNavigationStore({
selectedTab: "next",
})
}
if (step == Steps.REGISTER) {
updateNavigationStore({
selectedTab: "register",
})
}
if (step == Steps.REGISTERED) {
updateNavigationStore({
selectedTab: "app",
})
@@ -172,7 +190,7 @@ const MainScreen: React.FC = () => {
<YStack f={1} bc="#161616" mt={Platform.OS === 'ios' ? "$8" : "$0"} >
<YStack >
<XStack jc="space-between" ai="center" px="$3">
<Button p="$2" py="$3" unstyled onPress={decrementStep}><ChevronLeft color={selectedTab === "scan" ? "transparent" : "#a0a0a0"} /></Button>
<Button p="$2" py="$3" unstyled onPress={decrementStep}><ChevronLeft color={(selectedTab === "scan" || selectedTab === "app") ? "transparent" : "#a0a0a0"} /></Button>
<Text fontSize="$6" color="#a0a0a0">
{selectedTab === "scan" ? "Scan" : (selectedTab === "app" ? "Apps" : "Prove")}
@@ -187,7 +205,7 @@ const MainScreen: React.FC = () => {
<Sheet.Frame>
<YStack gap="$5" f={1} pt="$3">
<H2 textAlign='center'>Ready to scan</H2>
{step >= Steps.NFC_SCAN_COMPLETED ?
{step >= Steps.NEXT_SCREEN ?
<CheckCircle2
size="$8"
alignSelf='center'
@@ -217,74 +235,15 @@ const MainScreen: React.FC = () => {
<H2 color={textColor1}>Settings</H2>
<Cog color={textColor1} mt="$1" alignSelf='center' size="$2" />
</XStack>
<Fieldset horizontal>
<Label color={textColor1} width={225} justifyContent="flex-end" htmlFor="name" >
Broken camera
</Label>
<Switch size="$3.5" checked={brokenCamera} onCheckedChange={setBrokenCamera}>
<Switch.Thumb animation="bouncy" bc={bgColor} />
</Switch>
</Fieldset>
{
brokenCamera &&
<YStack pl="$3" gap="$1">
<Fieldset gap="$4" horizontal>
<Label color={textColor1} width={160} justifyContent="flex-end" fontSize={13}>
Passport Number
</Label>
<Input
bg={componentBgColor}
color={textColor1}
h="$3.5"
borderColor={passportNumber?.length === 9 ? "green" : "unset"}
flex={1}
id="passportnumber"
onChangeText={(text) => {
update({ passportNumber: text.toUpperCase() })
}}
value={passportNumber}
keyboardType="default"
/>
</Fieldset>
<Fieldset gap="$4" horizontal>
<Label color={textColor1} width={160} justifyContent="flex-end" fontSize={13}>
Date of birth (yymmdd)
</Label>
<Input
bg={componentBgColor}
color={textColor1}
h="$3.5"
borderColor={dateOfBirth?.length === 6 ? "green" : "unset"}
flex={1}
id="dateofbirth"
onChangeText={(text) => {
update({ dateOfBirth: text })
}}
value={dateOfBirth}
keyboardType={Platform.OS === "ios" ? "default" : "number-pad"}
/>
</Fieldset>
<Fieldset gap="$4" horizontal>
<Label color={textColor1} width={160} justifyContent="flex-end" fontSize={13}>
Date of expiry (yymmdd)
</Label>
<Input
bg={componentBgColor}
color={textColor1}
h="$3.5"
borderColor={dateOfExpiry?.length === 6 ? "green" : "unset"}
flex={1}
id="dateofexpiry"
onChangeText={(text) => {
update({ dateOfExpiry: text })
}}
value={dateOfExpiry}
keyboardType={Platform.OS === "ios" ? "default" : "number-pad"}
/>
</Fieldset>
</YStack>
}
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="restart">
Contribute
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={() => setDialogContributeIsOpen(true)}>
<Share color={textColor1} />
</Button>
</Fieldset>
<Fieldset horizontal>
<Label color={textColor1} width={225} justifyContent="flex-end" htmlFor="restart" >
Private mode
@@ -294,13 +253,15 @@ const MainScreen: React.FC = () => {
</Switch>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="restart">
Contribute
<Fieldset horizontal>
<Label color={textColor1} width={225} justifyContent="flex-end" htmlFor="restart" >
Display other options
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={() => setDialogContributeIsOpen(true)}>
<Share color={textColor1} />
</Button>
<Switch size="$3.5" checked={displayOtherOptions} onCheckedChange={() => setDisplayOtherOptions(!displayOtherOptions)}>
<Switch.Thumb animation="bouncy" bc={bgColor} />
</Switch>
</Fieldset>
<Dialog.Container visible={DialogContributeIsOpen}>
@@ -314,42 +275,46 @@ const MainScreen: React.FC = () => {
</Dialog.Container>
{displayOtherOptions && (
<>
<XStack my="$3" alignSelf='center' h={2} w="80%" bg={componentBgColor} borderRadius={100} />
<Fieldset gap="$4" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="restart">
Restart to step 1
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={handleRestart}>
<IterationCw color={textColor1} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="restart">
Restart to step 1
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={handleRestart}>
<IterationCw color={textColor1} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="skip" >
Use mock passport data
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={handleSkip}>
<VenetianMask color={textColor1} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="skip" >
Use mock passport data
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={handleSkip}>
<VenetianMask color={textColor1} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="skip" >
Delete passport data
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={clearPassportDataFromStorage}>
<Eraser color={textColor1} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="skip" >
Delete passport data
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={clearPassportDataFromStorage}>
<VenetianMask color={textColor1} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="skip" >
Delete secret (caution)
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={clearSecretFromStorage}>
<VenetianMask color={textColor1} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="skip" >
Delete secret (caution)
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={clearSecretFromStorage}>
<Eraser color={textColor2} />
</Button>
</Fieldset>
</>
)}
<YStack flex={1} />
@@ -359,6 +324,7 @@ const MainScreen: React.FC = () => {
</Button> */}
</YStack>
</YStack>
</Sheet.Frame>
</Sheet>
@@ -491,7 +457,7 @@ const MainScreen: React.FC = () => {
</Sheet>
<XStack bc="#343434" h={1.2} />
</YStack>
<Tabs f={1} orientation="horizontal" flexDirection="column" defaultValue="scan"
<Tabs f={1} orientation="horizontal" flexDirection="column" defaultValue={"scan"}
value={selectedTab}
onValueChange={(value) => updateNavigationStore({ selectedTab: value })}
>
@@ -508,11 +474,15 @@ const MainScreen: React.FC = () => {
handleNFCScan={handleNFCScan}
/>
</Tabs.Content>
<Tabs.Content value="next" f={1}>
<NextScreen />
</Tabs.Content>
<Tabs.Content value="register" f={1}>
<RegisterScreen />
</Tabs.Content>
<Tabs.Content value="app" f={1}>
<AppScreen />
</Tabs.Content>
<Tabs.Content value="prove" f={1}>
<ProveScreen />
</Tabs.Content>

View File

@@ -0,0 +1,111 @@
import React from 'react';
import { YStack, XStack, Text, Button, Image, useWindowDimensions, Fieldset } from 'tamagui';
import { Info } from '@tamagui/lucide-icons';
import { getFirstName, maskString } from '../../utils/utils';
import { attributeToPosition } from '../../../common/src/constants/constants';
import USER from '../images/user.png'
import { borderColor, componentBgColor, textColor1, textColor2 } from '../utils/colors';
import { Platform } from 'react-native';
import { formatAttribute, Steps } from '../utils/utils';
import useUserStore from '../stores/userStore';
import useNavigationStore from '../stores/navigationStore';
const NextScreen: React.FC = () => {
const {
hideData,
setStep,
} = useNavigationStore()
const {
passportData,
} = useUserStore();
const disclosureOptions: any = {
gender: "optional",
nationality: "optional",
expiry_date: "optional",
date_of_birth: "optional",
};
const { height } = useWindowDimensions();
return (
<YStack px="$4" f={1} mb={Platform.OS === 'ios' ? "$5" : "$0"}>
<YStack flex={1} mx="$2" gap="$2">
<YStack alignSelf='center' my="$3">
{hideData
? <Image
w={height > 750 ? 150 : 100}
h={height > 750 ? 190 : 80}
borderRadius={height > 800 ? "$7" : "$6"}
source={{
uri: USER,
}}
/>
: <Image
w={height > 750 ? 150 : 110}
h={height > 750 ? 190 : 130}
borderRadius={height > 750 ? "$7" : "$6"}
source={{
uri: passportData.photoBase64 ?? USER,
}}
/>
}
</YStack>
<Text color={textColor1} fontSize="$5" fontWeight="bold">
Hi {" "}
{
hideData
? maskString(getFirstName(passportData.mrz))
: getFirstName(passportData.mrz)
}
👋
</Text>
<YStack gap="$2.5" mt="$2" ml="$2">
{Object.keys(disclosureOptions).map((key) => {
const key_ = key;
const indexes = attributeToPosition[key_ as keyof typeof attributeToPosition];
const keyFormatted = key_.replace(/_/g, ' ').split(' ').map((word: string) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
const mrzAttribute = passportData.mrz.slice(indexes[0], indexes[1] + 1);
const mrzAttributeFormatted = formatAttribute(key_, mrzAttribute);
return (
<Fieldset horizontal key={key} gap="$3" alignItems='center'>
<Text color={textColor2} w="$10" justifyContent="flex-end" >
{keyFormatted}:
</Text>
<Text
color={textColor1}
>
{hideData ? maskString(mrzAttributeFormatted) : mrzAttributeFormatted}
</Text>
</Fieldset>
);
})}
</YStack>
<YStack f={1} />
<XStack mt="$6" bg={componentBgColor} borderRadius={100} borderWidth={1} borderColor={borderColor} py="$2.5" px="$3">
<Info alignSelf='center' size={24} color={textColor1} />
<Text ml="$3" pr="$6" fontSize="$3" color={textColor1}>Your information will remain confidential and will not be used or shared without your explicit consent.</Text>
</XStack>
<Button
mt="$8"
alignSelf='center'
onPress={() => setStep(Steps.REGISTER)}
borderWidth={1.3} borderColor={borderColor} borderRadius="$10" bg="#3185FC"
mb="$6"
w="100%"
>
<Text color="white" fontSize="$5">Next</Text>
</Button>
</YStack >
</YStack >
);
};
export default NextScreen;

View File

@@ -53,7 +53,7 @@ const NfcScreen: React.FC<NfcScreenProps> = ({ handleNFCScan }) => {
</YStack>
<YStack gap="$2">
<YStack gap="$2" mb="$6">
<Button borderWidth={1.3} borderColor={borderColor} borderRadius="$10" bg="#3185FC" onPress={handleNFCScan}><Nfc color={textColor1} /></Button>
</YStack>

View File

@@ -0,0 +1,61 @@
import React, { useState } from 'react';
import { YStack, XStack, Text, Button, Spinner } from 'tamagui';
import { LockKeyhole } from '@tamagui/lucide-icons';
import { borderColor, componentBgColor, componentBgColor2, textColor1, textColor2 } from '../utils/colors';
import { Platform } from 'react-native';
import useUserStore from '../stores/userStore';
import useNavigationStore from '../stores/navigationStore';
const RegisterScreen: React.FC = () => {
const [registering, setRegistering] = useState(false);
const { isZkeyDownloading } = useNavigationStore.getState();
const handleRegister = async () => {
setRegistering(true);
useUserStore.getState().registerCommitment()
}
return (
<YStack px="$4" f={1} mb={Platform.OS === 'ios' ? "$5" : "$0"}>
<YStack flex={1} mx="$2" gap="$2">
<Text mt="$12" color={textColor1} fontSize="$10" fontWeight="bold">
Register
</Text>
<Text mt="$6" fontSize="$6" color={textColor1}>Join Proof of Passport to affirm your identity and start sharing securely.</Text>
<Text mt="$1" fontSize="$4" color={textColor2}>Easily verify your nationality, gender, or age and take control of your personal data.</Text>
<Text fontSize="$4" color={textColor2}>Share only what you want with the application you wish.</Text>
<YStack f={1} />
<XStack mt="$5" bg={componentBgColor} borderRadius={100} borderWidth={1} borderColor={borderColor} py="$2" px="$3">
<XStack bg={componentBgColor2} borderRadius={100} p="$2" >
<LockKeyhole alignSelf='center' size={24} color={textColor1} />
</XStack>
<Text alignSelf='center' ml="$3" pr="$5" fontSize="$3" color={textColor1}>Registration does not leak any personal information</Text>
</XStack>
<Button
disabled={isZkeyDownloading.register_sha256WithRSAEncryption_65537}
mt="$8"
alignSelf='center'
onPress={handleRegister}
borderWidth={1.3} borderColor={borderColor} borderRadius="$10" bg={isZkeyDownloading.register_sha256WithRSAEncryption_65537 ? "gray" : "#3185FC"}
mb="$6"
w="100%"
>
<XStack gap="$2.5">
{(registering || isZkeyDownloading.register_sha256WithRSAEncryption_65537) && <Spinner color="white" size="small" />}
<Text color="white" fontSize="$5" >
{isZkeyDownloading.register_sha256WithRSAEncryption_65537 ? "Downloading zkey..." : (registering ? "Registering..." : "Register")}
</Text>
</XStack>
</Button>
</YStack >
</YStack >
);
};
export default RegisterScreen;

View File

@@ -1,183 +0,0 @@
import React from 'react';
import { YStack, Text, XStack, Button, Image, ScrollView } from 'tamagui';
import { Steps } from '../utils/utils';
import { Camera, ExternalLink, Nfc, X } from '@tamagui/lucide-icons';
import { blueColorDark, blueColorLight, borderColor, componentBgColor2, greenColorDark, greenColorLight, redColorDark, redColorLight, textColor1, textColor2 } from '../utils/colors';
import { useToastController } from '@tamagui/toast'
import NFCHelp from '../images/nfc_help.png'
import SCANHelp from '../images/scan_help.png'
import { Linking } from 'react-native';
import { startCameraScan } from '../utils/cameraScanner';
interface ScanScreenProps {
handleNFCScan: () => void;
step: number;
}
const ScanScreen: React.FC<ScanScreenProps> = ({ handleNFCScan, step }) => {
return (
<ScrollView f={1}>
<YStack mt="$4" mb="$6" f={1} p="$5" gap="$5" px="$5" justifyContent='center'>
<YStack bc="#1c1c1c" borderWidth={1.2} borderColor="#343434" borderRadius="$6">
<YStack p="$3">
<XStack gap="$4" ai="center">
<XStack p="$2" bc="#232323" borderWidth={1.2} borderColor="#343434" borderRadius="$3">
<Camera color="#a0a0a0" />
</XStack>
<YStack gap="$1">
<Text fontSize={16} fow="bold" color="#ededed">Step 1</Text>
<Text color="#a0a0a0">Scan your passport </Text>
</YStack>
</XStack>
</YStack>
<YStack gap="$2" p="$3" bc="#232323" borderWidth={1.2} borderLeftWidth={0} borderRightWidth={0} borderColor="#343434">
<Image borderRadius="$5"
w="full"
h="$13"
source={{ uri: SCANHelp }}
/>
<Text mt="$1" color={textColor1}>Use your camera to scan the main page of your passport.</Text>
<Text fontSize="$2" style={{ fontStyle: 'italic' }} color={textColor2}>No personal data will be stored or shared with external apps.</Text>
</YStack>
<YStack p="$2">
<XStack gap="$4" ai="center">
{step >= Steps.MRZ_SCAN_COMPLETED ? (
<XStack ml="$2" p="$2" px="$3" bc="#0d1e18" borderRadius="$10">
<Text color="#3bb178" fow="bold">Done</Text>
</XStack>
) : (
<XStack ml="$2" p="$2" px="$3" bc={blueColorDark} borderRadius="$10">
<Text color={blueColorLight} fow="bold">To-do</Text>
</XStack>
)}
<XStack f={1} />
<Button h="$3" onPress={startCameraScan} p="$2" borderRadius="$4" borderWidth={1} backgroundColor="#282828" borderColor="#343434">
<XStack gap="$2">
<Text color="#ededed" fontSize="$5" >Open camera</Text>
<ExternalLink size="$1" color="#ededed" />
</XStack>
</Button>
</XStack>
</YStack>
</YStack >
<YStack bc="#1c1c1c" borderWidth={1.2} borderColor="#343434" borderRadius="$6">
<YStack p="$3">
<XStack gap="$4" ai="center">
<XStack p="$2" bc="#232323" borderWidth={1.2} borderColor="#343434" borderRadius="$3">
<Nfc color="#a0a0a0" />
</XStack>
<YStack gap="$1">
<Text fontSize={16} fow="bold" color="#ededed">Step 2</Text>
<Text color="#a0a0a0">Read the NFC chip </Text>
</YStack>
</XStack>
</YStack>
<XStack gap="$2.5" p="$3" bc="#232323" borderWidth={1.2} borderLeftWidth={0} borderRightWidth={0} borderColor="#343434">
<Image borderRadius="$5"
w="$12"
h="$14"
source={{ uri: NFCHelp }}
/>
<YStack w="$13" jc="space-between">
<Text color={textColor1}>Hold your passport against your device to read the biometric chip.</Text>
<Text color={textColor1}>Follow <Text onPress={() => Linking.openURL('https://zk-passport.github.io/posts/how-to-scan-your-passport-using-nfc/')} color={blueColorLight} style={{ textDecorationLine: 'underline', fontStyle: 'italic' }}>this guide</Text> if you have trouble reading your passport.</Text>
<Text fontSize="$2" color={textColor2} style={{ fontStyle: 'italic' }}>No personnal data will be stored or shared with external apps.</Text>
</YStack>
</XStack>
<YStack p="$2">
<XStack gap="$4" ai="center">
{step < Steps.MRZ_SCAN_COMPLETED ? (
<YStack ml="$2" p="$2" px="$3" bc="#282828" borderRadius="$10">
<Text color="#a0a0a0" fontWeight="bold">To-do</Text>
</YStack>
) : step < Steps.NFC_SCAN_COMPLETED ? (
<XStack ml="$2" p="$2" px="$3" bc={blueColorDark} borderRadius="$10">
<Text color={blueColorLight} fow="bold">To-do</Text>
</XStack>
) : (
<XStack ml="$2" p="$2" px="$3" bc="#0d1e18" borderRadius="$10">
<Text color="#3bb178" fow="bold">Done</Text>
</XStack>
)}
<XStack f={1} />
<Button h="$3" onPress={handleNFCScan} p="$2" borderRadius="$4" borderWidth={1} backgroundColor="#282828" borderColor="#343434">
<XStack gap="$2">
<Text color={step < Steps.MRZ_SCAN_COMPLETED ? "#a0a0a0" : "#ededed"} fontSize="$5" >Scan with NFC</Text>
<ExternalLink size="$1" color={step < Steps.MRZ_SCAN_COMPLETED ? "#a0a0a0" : "#ededed"} />
</XStack>
</Button>
</XStack>
</YStack>
</YStack >
{/*
<XStack
jc="center"
borderColor="#a0a0a0"
borderWidth={1}
borderRadius="$10"
f={0}
w="$1.5"
h="$1.5"
alignSelf='center'>
<Text color="#a0a0a0" fontSize="$4" y={0} x={0} alignSelf='center'>1</Text>
</XStack>
<Text color="#a0a0a0" fontSize="$6" textAlign='center' mt="$2" >Scan the machine readable zone on the main page of your passport</Text> */}
{/* <XStack
mt="$10"
jc="center"
borderColor="#a0a0a0"
borderWidth={1}
borderRadius="$10"
f={0}
w="$1.5"
h="$1.5"
alignSelf='center'>
<Text color="#a0a0a0" fontSize="$4" y={0} x={0} alignSelf='center'>2</Text>
</XStack>
<Text color="#a0a0a0" fontSize="$6" textAlign='center' mt="$2" >Hold your passport against your device to read the biometric chip</Text> */}
{/*
<YStack ai="center">
{
step < Steps.NFC_SCAN_COMPLETED
? (
step < Steps.MRZ_SCAN_COMPLETED
?
<YStack mt="$6">
<Button borderWidth={1} backgroundColor="#282828" borderColor="#343434" variant="outlined">
<Camera color="#ededed" />
<Text color="#ededed" fontSize="$6" onPress={onStartCameraScan} >Open camera</Text>
</Button>
<Spinner mt="$4" color={step === Steps.NFC_SCANNING ? "#3185FC" : "transparent"} />
</YStack>
: (
<YStack mt="$6">
<Text p="$6" fontSize="$6" color="#3185FC" onPress={handleNFCScan}>
{step === Steps.NFC_SCANNING ? "Scanning" : "Scan passport with NFC"}
</Text>
<Spinner mt="$4" color={step === Steps.NFC_SCANNING ? "#3185FC" : "transparent"} />
</YStack>
)
)
:
<XStack />
}
</YStack> */}
</YStack >
</ScrollView>
);
};
export default ScanScreen;

View File

@@ -28,7 +28,7 @@ interface UserState {
secret: string
initUserStore: () => void
registerPassportData: (passportData: PassportData) => void
registerCommitment: (secret: string, passportData: PassportData) => void
registerCommitment: (passportData?: PassportData) => void
clearPassportDataFromStorage: () => void
clearSecretFromStorage: () => void
update: (patch: any) => void
@@ -68,7 +68,7 @@ const useUserStore = create<UserState>((set, get) => ({
passportData: JSON.parse(passportData),
registered: true,
});
useNavigationStore.getState().setStep(Steps.NFC_SCAN_COMPLETED); // this means go to app selection screen
useNavigationStore.getState().setStep(Steps.REGISTERED); // this means go to app selection screen
// TODO: check if the commitment is already registered, if not retry registering it
@@ -81,7 +81,6 @@ const useUserStore = create<UserState>((set, get) => ({
// - Check presence of secret. If there is none, create one and store it
// - Store the passportData and try registering the commitment in the background
registerPassportData: async (passportData) => {
const secret = await loadSecret() as string;
const alreadyStoredPassportData = await loadPassportData();
@@ -92,17 +91,21 @@ const useUserStore = create<UserState>((set, get) => ({
await storePassportData(passportData)
set({ passportData });
get().registerCommitment(
secret,
passportData
)
},
registerCommitment: async (secret, passportData) => {
registerCommitment: async (mockPassportData?: PassportData) => {
const {
toast
} = useNavigationStore.getState();
const secret = await loadSecret() as string;
let passportData = get().passportData
if (mockPassportData) {
passportData = mockPassportData
}
console.log("register commitment")
console.log("sig alg: in userstore", passportData.signatureAlgorithm)
console.log(secret)
console.log(passportData)
try {
const inputs = generateCircuitInputsRegister(
@@ -113,7 +116,7 @@ const useUserStore = create<UserState>((set, get) => ({
);
amplitude.track(`Sig alg supported: ${passportData.signatureAlgorithm}`);
Object.keys(inputs).forEach((key) => {
if (Array.isArray(inputs[key as keyof typeof inputs])) {
console.log(key, inputs[key as keyof typeof inputs].slice(0, 10), '...');
@@ -121,7 +124,7 @@ const useUserStore = create<UserState>((set, get) => ({
console.log(key, inputs[key as keyof typeof inputs]);
}
});
const start = Date.now();
const sigAlgFormatted = formatSigAlgNameForCircuit(passportData.signatureAlgorithm, passportData.pubKey.exponent);

View File

@@ -17,8 +17,8 @@ export const scan = async () => {
dateOfBirth,
dateOfExpiry
} = useUserStore.getState()
const {toast, setStep} = useNavigationStore.getState();
const { toast, setStep } = useNavigationStore.getState();
const check = checkInputs(
passportNumber,
@@ -52,7 +52,7 @@ const scanAndroid = async () => {
dateOfBirth,
dateOfExpiry
} = useUserStore.getState()
const {toast, setStep} = useNavigationStore.getState();
const { toast, setStep } = useNavigationStore.getState();
try {
const response = await PassportReader.scan({
@@ -83,7 +83,7 @@ const scanIOS = async () => {
dateOfBirth,
dateOfExpiry
} = useUserStore.getState()
const {toast, setStep} = useNavigationStore.getState();
const { toast, setStep } = useNavigationStore.getState();
try {
const response = await NativeModules.PassportReader.scanPassport(
@@ -112,7 +112,7 @@ const scanIOS = async () => {
const handleResponseIOS = async (
response: any,
) => {
const {toast} = useNavigationStore.getState();
const { toast } = useNavigationStore.getState();
const parsed = JSON.parse(response);
@@ -177,7 +177,7 @@ const handleResponseIOS = async (
console.log("photoBase64", passportData.photoBase64.substring(0, 100) + '...')
useUserStore.getState().registerPassportData(passportData)
useNavigationStore.getState().setStep(Steps.NFC_SCAN_COMPLETED);
useNavigationStore.getState().setStep(Steps.NEXT_SCREEN);
} catch (e: any) {
console.log('error during parsing:', e);
useNavigationStore.getState().setStep(Steps.MRZ_SCAN_COMPLETED);
@@ -231,7 +231,7 @@ const handleResponseAndroid = async (
...passportData,
photoBase64: passportData.photoBase64.substring(0, 100) + '...'
}, null, 2));
console.log('mrz', passportData.mrz);
console.log('signatureAlgorithm', passportData.signatureAlgorithm);
console.log('pubKey', passportData.pubKey);
@@ -247,5 +247,5 @@ const handleResponseAndroid = async (
console.log("encapContent", encapContent)
useUserStore.getState().registerPassportData(passportData)
useNavigationStore.getState().setStep(Steps.NFC_SCAN_COMPLETED);
useNavigationStore.getState().setStep(Steps.NEXT_SCREEN);
};

View File

@@ -37,12 +37,14 @@ export const Steps = {
MRZ_SCAN: 1,
MRZ_SCAN_COMPLETED: 2,
NFC_SCANNING: 3,
NFC_SCAN_COMPLETED: 4,
APP_SELECTED: 5,
GENERATING_PROOF: 6,
PROOF_GENERATED: 7,
PROOF_SENDING: 8,
PROOF_SENT: 9
NEXT_SCREEN: 4,
REGISTER: 5,
REGISTERED: 6,
APP_SELECTED: 7,
GENERATING_PROOF: 8,
PROOF_GENERATED: 9,
PROOF_SENDING: 10,
PROOF_SENT: 11
};
export function formatAttribute(key: string, attribute: string) {
@@ -54,6 +56,17 @@ export function formatAttribute(key: string, attribute: string) {
} else if (key === 'nationality' && attribute in countryCodes) {
return countryCodes[attribute as keyof typeof countryCodes]
}
else if (key === 'date_of_birth') {
let year = '19' + attribute.substring(0, 2);
const currentYear = 2024;
const birthYear = parseInt(year);
if (currentYear - birthYear > 100) {
year = '20' + attribute.substring(0, 2);
}
const month = attribute.substring(2, 4);
const day = attribute.substring(4, 6);
return `${year}-${month}-${day}`; // ISO 8601 format (YYYY-MM-DD)
}
return attribute;
}