Files
self/app/src/screens/prove/ProveScreen.tsx
Justin Hernandez 86656fdca6 Fix lazy loading, web build; revert tree shaking for now (#815)
* Revert "fix yarn web (#814)"

This reverts commit bf158fd977.

* Revert "add hermes ios engine"

This reverts commit f6defcfb12.

* Revert "codex feedback"

This reverts commit df35a47f72.

* Revert "merge with dev"

This reverts commit 47a8d9d2f1.

* Revert "update Gemfile.lock and remove lock when reinstalling"

This reverts commit 5f25752f77.

* Revert "test package update"

This reverts commit 19dcba0e42.

* Revert "revert install cmd changes"

This reverts commit 5427bd12a1.

* Revert "Fix CI build by excluding nokogiri in GitHub Actions/Act environments"

This reverts commit dbff69a13e.

* Revert "prettier"

This reverts commit 87f421456f.

* Revert "fix building"

This reverts commit fbbefd2a3d.

* Revert "optimize path resolving"

This reverts commit 23b1118b8e.

* Revert "fix loading"

This reverts commit f0c884b43b.

* Revert "clean up dupe bundle install"

This reverts commit 5428567bbe.

* Revert "fix metro loading"

This reverts commit 3a766001dc.

* Revert "remove passport provider lazy loading"

This reverts commit 5f793a54b3.

* Revert "allow cursor to edit Gemfile and update lock file"

This reverts commit b6f7158e8e.

* Revert "Update Gemfile.lock to exclude nokogiri in CI environments"

This reverts commit 243640152d.

* Revert "fix install commands"

This reverts commit 2dc7d7c1c9.

* Revert "fix imports and test"

This reverts commit 83d6308029.

* Revert "fix import"

This reverts commit fa42b07ce4.

* Revert "update build checks"

This reverts commit b281f15a16.

* Revert "save updated imports"

This reverts commit 215bca4bee.

* Revert "fix build command"

This reverts commit 37f95bc8fb.

* Revert "build dependencies before linting"

This reverts commit 9e57e017ca.

* Revert "lint suggestions"

This reverts commit ff9b9d2c7c.

* Revert "fix type. more opportunities"

This reverts commit 7ad82d5817.

* Revert "add typing to crypto loader"

This reverts commit d55eec8f44.

* Revert "yarn nice"

This reverts commit df1c2dbd9b.

* Revert "update lock"

This reverts commit 04692ba3b5.

* Revert "cm feedback"

This reverts commit 848071f315.

* fix file name

* fix web loading

* fix border width styling

* fix package commands

* fix import and update lock

* fixes from reverted commits

* more fixes for web building

* fix yarn web:build

* add yarn web:build workflow

* cm feedback

* final fixes

* fix for and vitge

* fix loading
2025-07-30 22:27:49 -07:00

385 lines
13 KiB
TypeScript

// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11
import { useIsFocused, useNavigation } from '@react-navigation/native';
import type { SelfAppDisclosureConfig } from '@selfxyz/common';
import { formatEndpoint } from '@selfxyz/common';
import { Eye, EyeOff } from '@tamagui/lucide-icons';
import LottieView from 'lottie-react-native';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
LayoutChangeEvent,
NativeScrollEvent,
NativeSyntheticEvent,
ScrollView,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import { Image, Text, View, XStack, YStack } from 'tamagui';
import miscAnimation from '../../assets/animations/loading/misc.json';
import { HeldPrimaryButtonProveScreen } from '../../components/buttons/HeldPrimaryButtonProveScreen';
import Disclosures from '../../components/Disclosures';
import { BodyText } from '../../components/typography/BodyText';
import { Caption } from '../../components/typography/Caption';
import { ProofEvents } from '../../consts/analytics';
import { ExpandableBottomLayout } from '../../layouts/ExpandableBottomLayout';
import { setDefaultDocumentTypeIfNeeded } from '../../providers/passportDataProvider';
import { ProofStatus } from '../../stores/proof-types';
import { useProofHistoryStore } from '../../stores/proofHistoryStore';
import { useSelfAppStore } from '../../stores/selfAppStore';
import analytics from '../../utils/analytics';
import { black, slate300, white } from '../../utils/colors';
import { formatUserId } from '../../utils/formatUserId';
import { buttonTap } from '../../utils/haptic';
import { useProvingStore } from '../../utils/proving/provingMachine';
const { trackEvent } = analytics();
const ProveScreen: React.FC = () => {
const { navigate } = useNavigation();
const isFocused = useIsFocused();
const selectedApp = useSelfAppStore(state => state.selfApp);
const selectedAppRef = useRef<typeof selectedApp>(null);
const [hasScrolledToBottom, setHasScrolledToBottom] = useState(false);
const [scrollViewContentHeight, setScrollViewContentHeight] = useState(0);
const [scrollViewHeight, setScrollViewHeight] = useState(0);
const [showFullAddress, setShowFullAddress] = useState(false);
const scrollViewRef = useRef<ScrollView>(null);
const isContentShorterThanScrollView = useMemo(
() => scrollViewContentHeight <= scrollViewHeight,
[scrollViewContentHeight, scrollViewHeight],
);
const provingStore = useProvingStore();
const currentState = useProvingStore(state => state.currentState);
const isReadyToProve = currentState === 'ready_to_prove';
const { addProofHistory } = useProofHistoryStore();
useEffect(() => {
if (provingStore.uuid && selectedApp) {
addProofHistory({
appName: selectedApp.appName,
sessionId: provingStore.uuid!,
userId: selectedApp.userId,
userIdType: selectedApp.userIdType,
endpointType: selectedApp.endpointType,
status: ProofStatus.PENDING,
logoBase64: selectedApp.logoBase64,
disclosures: JSON.stringify(selectedApp.disclosures),
});
}
}, [addProofHistory, provingStore.uuid, selectedApp]);
useEffect(() => {
if (isContentShorterThanScrollView) {
setHasScrolledToBottom(true);
} else {
setHasScrolledToBottom(false);
}
}, [isContentShorterThanScrollView]);
useEffect(() => {
if (!isFocused || !selectedApp) {
return;
}
setDefaultDocumentTypeIfNeeded();
if (selectedAppRef.current?.sessionId !== selectedApp.sessionId) {
console.log('[ProveScreen] Selected app updated:', selectedApp);
provingStore.init('disclose');
}
selectedAppRef.current = selectedApp;
}, [selectedApp, isFocused, provingStore]);
const disclosureOptions = useMemo(() => {
return (selectedApp?.disclosures as SelfAppDisclosureConfig) || [];
}, [selectedApp?.disclosures]);
// Format the logo source based on whether it's a URL or base64 string
const logoSource = useMemo(() => {
if (!selectedApp?.logoBase64) {
return null;
}
// Check if the logo is already a URL
if (
selectedApp.logoBase64.startsWith('http://') ||
selectedApp.logoBase64.startsWith('https://')
) {
return { uri: selectedApp.logoBase64 };
}
// Otherwise handle as base64 as before
const base64String = selectedApp.logoBase64.startsWith('data:image')
? selectedApp.logoBase64
: `data:image/png;base64,${selectedApp.logoBase64}`;
return { uri: base64String };
}, [selectedApp?.logoBase64]);
const url = useMemo(() => {
if (!selectedApp?.endpoint) {
return null;
}
return formatEndpoint(selectedApp.endpoint);
}, [selectedApp?.endpoint]);
const formattedUserId = useMemo(
() => formatUserId(selectedApp?.userId, selectedApp?.userIdType),
[selectedApp?.userId, selectedApp?.userIdType],
);
function onVerify() {
provingStore.setUserConfirmed();
buttonTap();
trackEvent(ProofEvents.PROOF_VERIFICATION_STARTED, {
appName: selectedApp?.appName,
sessionId: provingStore.uuid,
endpointType: selectedApp?.endpointType,
userIdType: selectedApp?.userIdType,
});
setTimeout(() => {
navigate('ProofRequestStatusScreen');
}, 100);
}
const handleScroll = useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
if (hasScrolledToBottom || isContentShorterThanScrollView) {
return;
}
const { layoutMeasurement, contentOffset, contentSize } =
event.nativeEvent;
const paddingToBottom = 10;
const isCloseToBottom =
layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom;
if (isCloseToBottom && !hasScrolledToBottom) {
setHasScrolledToBottom(true);
buttonTap();
trackEvent(ProofEvents.PROOF_DISCLOSURES_SCROLLED, {
appName: selectedApp?.appName,
sessionId: provingStore.uuid,
});
}
},
[
hasScrolledToBottom,
isContentShorterThanScrollView,
selectedApp,
provingStore.uuid,
],
);
const handleContentSizeChange = useCallback(
(contentWidth: number, contentHeight: number) => {
setScrollViewContentHeight(contentHeight);
},
[],
);
const handleScrollViewLayout = useCallback((event: LayoutChangeEvent) => {
setScrollViewHeight(event.nativeEvent.layout.height);
}, []);
const handleAddressToggle = useCallback(() => {
if (selectedApp?.userIdType === 'hex') {
setShowFullAddress(!showFullAddress);
buttonTap();
}
}, [selectedApp?.userIdType, showFullAddress]);
return (
<ExpandableBottomLayout.Layout flex={1} backgroundColor={black}>
<ExpandableBottomLayout.TopSection backgroundColor={black}>
<YStack alignItems="center">
{!selectedApp?.sessionId ? (
<LottieView
source={miscAnimation}
autoPlay
loop
resizeMode="cover"
cacheComposition={true}
renderMode="HARDWARE"
style={styles.animation}
speed={1}
progress={0}
/>
) : (
<YStack alignItems="center" justifyContent="center">
{logoSource && (
<Image
marginBottom={20}
source={logoSource}
width={100}
height={100}
objectFit="contain"
/>
)}
<BodyText fontSize={12} color={slate300} marginBottom={20}>
{url}
</BodyText>
<BodyText fontSize={24} color={slate300} textAlign="center">
<Text color={white}>{selectedApp.appName}</Text> is requesting
that you prove the following information:
</BodyText>
</YStack>
)}
</YStack>
</ExpandableBottomLayout.TopSection>
<ExpandableBottomLayout.BottomSection
paddingBottom={20}
backgroundColor={white}
maxHeight={'55%'}
>
<ScrollView
ref={scrollViewRef}
onScroll={handleScroll}
scrollEventThrottle={16}
onContentSizeChange={handleContentSizeChange}
onLayout={handleScrollViewLayout}
>
<Disclosures disclosures={disclosureOptions} />
{/* Display connected wallet or UUID */}
{formattedUserId && (
<View marginTop={20} paddingHorizontal={20}>
<BodyText
fontSize={16}
color={black}
fontWeight="600"
marginBottom={10}
>
{selectedApp?.userIdType === 'hex'
? 'Connected Wallet'
: 'Connected ID'}
:
</BodyText>
<TouchableOpacity
onPress={handleAddressToggle}
activeOpacity={selectedApp?.userIdType === 'hex' ? 0.7 : 1}
style={{ minHeight: 44 }}
>
<View
backgroundColor={slate300}
padding={15}
borderRadius={8}
marginBottom={10}
>
<XStack alignItems="center" justifyContent="space-between">
<View
flex={1}
marginRight={selectedApp?.userIdType === 'hex' ? 12 : 0}
>
<BodyText
fontSize={14}
color={black}
lineHeight={20}
fontFamily={
showFullAddress && selectedApp?.userIdType === 'hex'
? 'monospace'
: 'normal'
}
flexWrap={showFullAddress ? 'wrap' : 'nowrap'}
>
{selectedApp?.userIdType === 'hex' && showFullAddress
? selectedApp.userId
: formattedUserId}
</BodyText>
</View>
{selectedApp?.userIdType === 'hex' && (
<View alignItems="center" justifyContent="center">
{showFullAddress ? (
<EyeOff size={16} color={black} />
) : (
<Eye size={16} color={black} />
)}
</View>
)}
</XStack>
{selectedApp?.userIdType === 'hex' && (
<BodyText
fontSize={12}
color={black}
opacity={0.6}
marginTop={4}
>
{showFullAddress
? 'Tap to hide address'
: 'Tap to show full address'}
</BodyText>
)}
</View>
</TouchableOpacity>
</View>
)}
{/* Display userDefinedData if it exists */}
{selectedApp?.userDefinedData && (
<View marginTop={20} paddingHorizontal={20}>
<BodyText
fontSize={16}
color={black}
fontWeight="600"
marginBottom={10}
>
Additional Information:
</BodyText>
<View
backgroundColor={slate300}
padding={15}
borderRadius={8}
marginBottom={10}
>
<BodyText fontSize={14} color={black} lineHeight={20}>
{selectedApp.userDefinedData}
</BodyText>
</View>
</View>
)}
<View marginTop={20}>
<Caption
textAlign="center"
size="small"
marginBottom={20}
marginTop={10}
borderRadius={4}
paddingBottom={20}
>
Self will confirm that these details are accurate and none of your
confidential info will be revealed to {selectedApp?.appName}
</Caption>
</View>
</ScrollView>
<HeldPrimaryButtonProveScreen
onVerify={onVerify}
selectedAppSessionId={selectedApp?.sessionId}
hasScrolledToBottom={hasScrolledToBottom}
isReadyToProve={isReadyToProve}
/>
</ExpandableBottomLayout.BottomSection>
</ExpandableBottomLayout.Layout>
);
};
export default ProveScreen;
const styles = StyleSheet.create({
animation: {
top: 0,
width: 200,
height: 200,
transform: [{ scale: 2 }, { translateY: -20 }],
},
});