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
3 changes: 3 additions & 0 deletions app/src/components/proof-request/BottomVerifyBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface BottomVerifyBarProps {
onVerify: () => void;
selectedAppSessionId: string | undefined | null;
hasScrolledToBottom: boolean;
isScrollable: boolean;
isReadyToProve: boolean;
isDocumentExpired: boolean;
testID?: string;
Expand All @@ -23,6 +24,7 @@ export const BottomVerifyBar: React.FC<BottomVerifyBarProps> = ({
onVerify,
selectedAppSessionId,
hasScrolledToBottom,
isScrollable,
isReadyToProve,
isDocumentExpired,
testID = 'bottom-verify-bar',
Expand All @@ -41,6 +43,7 @@ export const BottomVerifyBar: React.FC<BottomVerifyBarProps> = ({
onVerify={onVerify}
selectedAppSessionId={selectedAppSessionId}
hasScrolledToBottom={hasScrolledToBottom}
isScrollable={isScrollable}
isReadyToProve={isReadyToProve}
isDocumentExpired={isDocumentExpired}
/>
Expand Down
53 changes: 38 additions & 15 deletions app/src/components/proof-request/ProofRequestCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ProofRequestCardProps {
documentType?: string;
timestamp?: Date;
children?: React.ReactNode;
connectedWalletBadge?: React.ReactNode;
testID?: string;
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
scrollViewRef?: React.RefObject<ScrollViewType>;
Expand All @@ -52,6 +53,7 @@ export const ProofRequestCard: React.FC<ProofRequestCardProps> = ({
documentType = '',
timestamp,
children,
connectedWalletBadge,
testID = 'proof-request-card',
onScroll,
scrollViewRef,
Expand Down Expand Up @@ -111,26 +113,47 @@ export const ProofRequestCard: React.FC<ProofRequestCardProps> = ({
<View
flex={1}
backgroundColor={proofRequestColors.slate100}
padding={proofRequestSpacing.cardPadding}
borderBottomLeftRadius={proofRequestSpacing.borderRadius}
borderBottomRightRadius={proofRequestSpacing.borderRadius}
>
<ScrollView
ref={scrollViewRef}
showsVerticalScrollIndicator={true}
contentContainerStyle={{ flexGrow: 1 }}
onScroll={onScroll}
scrollEventThrottle={16}
onContentSizeChange={onContentSizeChange}
onLayout={onLayout}
contentOffset={
typeof initialScrollOffset === 'number'
? { x: 0, y: initialScrollOffset }
: undefined
{/* Connected Wallet Badge - Fixed position under metadata bar */}
{connectedWalletBadge && (
<View
paddingHorizontal={proofRequestSpacing.cardPadding}
paddingTop={proofRequestSpacing.cardPadding}
paddingBottom={0}
>
{connectedWalletBadge}
</View>
)}

{/* Scrollable Content */}
<View
flex={1}
padding={proofRequestSpacing.cardPadding}
paddingTop={
connectedWalletBadge
? proofRequestSpacing.itemPadding
: proofRequestSpacing.cardPadding
}
>
{children}
</ScrollView>
<ScrollView
ref={scrollViewRef}
showsVerticalScrollIndicator={true}
contentContainerStyle={{ flexGrow: 1 }}
onScroll={onScroll}
scrollEventThrottle={16}
onContentSizeChange={onContentSizeChange}
onLayout={onLayout}
contentOffset={
typeof initialScrollOffset === 'number'
? { x: 0, y: initialScrollOffset }
: undefined
}
>
{children}
</ScrollView>
</View>
</View>
</View>
</View>
Expand Down
34 changes: 17 additions & 17 deletions app/src/screens/verification/DocumentSelectorForProvingScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
isDocumentValidForProving,
useSelfClient,
} from '@selfxyz/mobile-sdk-alpha';
import { blue600, white } from '@selfxyz/mobile-sdk-alpha/constants/colors';
import { black, white } from '@selfxyz/mobile-sdk-alpha/constants/colors';
import { dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts';

import type { IDSelectorState } from '@/components/documents';
Expand Down Expand Up @@ -341,7 +341,7 @@ const DocumentSelectorForProvingScreen: React.FC = () => {
justifyContent="center"
testID="document-selector-loading-container"
>
<ActivityIndicator color={blue600} size="large" />
<ActivityIndicator color={black} size="large" />
</View>
);
}
Expand Down Expand Up @@ -418,25 +418,25 @@ const DocumentSelectorForProvingScreen: React.FC = () => {
appName={selfApp?.appName || 'Self'}
appUrl={url}
documentType={selectedDocumentType}
connectedWalletBadge={
formattedUserId ? (
<ConnectedWalletBadge
address={
selfApp?.userIdType === 'hex'
? truncateAddress(selfApp?.userId || '')
: formattedUserId
}
userIdType={selfApp?.userIdType}
onToggle={() => setWalletModalOpen(true)}
testID="document-selector-wallet-badge"
/>
) : undefined
}
onScroll={handleScroll}
testID="document-selector-card"
>
{/* Connected Wallet Badge */}
{formattedUserId && (
<ConnectedWalletBadge
address={
selfApp?.userIdType === 'hex'
? truncateAddress(selfApp?.userId || '')
: formattedUserId
}
userIdType={selfApp?.userIdType}
onToggle={() => setWalletModalOpen(true)}
testID="document-selector-wallet-badge"
/>
)}

{/* Disclosure Items */}
<YStack marginTop={formattedUserId ? 16 : 0}>
<YStack marginTop={0}>
{disclosureItems.map((item, index) => (
<DisclosureItem
key={item.key}
Expand Down
44 changes: 26 additions & 18 deletions app/src/screens/verification/ProveScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,14 @@ const ProveScreen: React.FC = () => {
const scrollViewRef = useRef<ScrollViewType>(null);

const isContentShorterThanScrollView = useMemo(
() => scrollViewContentHeight <= scrollViewHeight,
() => scrollViewContentHeight <= scrollViewHeight + 50,
[scrollViewContentHeight, scrollViewHeight],
);

const isScrollable = useMemo(
() => !isContentShorterThanScrollView && hasLayoutMeasurements,
[isContentShorterThanScrollView, hasLayoutMeasurements],
);
const provingStore = useProvingStore();
const currentState = useProvingStore(state => state.currentState);
const isReadyToProve = currentState === 'ready_to_prove';
Expand Down Expand Up @@ -255,7 +260,7 @@ const ProveScreen: React.FC = () => {
}
const { layoutMeasurement, contentOffset, contentSize } =
event.nativeEvent;
const paddingToBottom = 10;
const paddingToBottom = 50;
const isCloseToBottom =
layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom;
Expand Down Expand Up @@ -287,7 +292,7 @@ const ProveScreen: React.FC = () => {
// If we now have both measurements and content fits on screen, enable button immediately
if (contentHeight > 0 && scrollViewHeight > 0) {
setHasLayoutMeasurements(true);
if (contentHeight <= scrollViewHeight) {
if (contentHeight <= scrollViewHeight + 50) {
setHasScrolledToBottom(true);
}
}
Expand All @@ -302,7 +307,7 @@ const ProveScreen: React.FC = () => {
// If we now have both measurements and content fits on screen, enable button immediately
if (layoutHeight > 0 && scrollViewContentHeight > 0) {
setHasLayoutMeasurements(true);
if (scrollViewContentHeight <= layoutHeight) {
if (scrollViewContentHeight <= layoutHeight + 50) {
setHasScrolledToBottom(true);
}
}
Expand All @@ -317,27 +322,29 @@ const ProveScreen: React.FC = () => {
appName={selectedApp?.appName || 'Self'}
appUrl={url}
documentType={documentType}
connectedWalletBadge={
formattedUserId ? (
<ConnectedWalletBadge
address={
selectedApp?.userIdType === 'hex'
? truncateAddress(selectedApp?.userId || '')
: formattedUserId
}
userIdType={selectedApp?.userIdType}
onToggle={() => setWalletModalOpen(true)}
testID="prove-screen-wallet-badge"
/>
) : undefined
}
onScroll={handleScroll}
scrollViewRef={scrollViewRef}
onContentSizeChange={handleContentSizeChange}
onLayout={handleScrollViewLayout}
initialScrollOffset={route.params?.scrollOffset}
testID="prove-screen-card"
>
{formattedUserId && (
<ConnectedWalletBadge
address={
selectedApp?.userIdType === 'hex'
? truncateAddress(selectedApp?.userId || '')
: formattedUserId
}
userIdType={selectedApp?.userIdType}
onToggle={() => setWalletModalOpen(true)}
testID="prove-screen-wallet-badge"
/>
)}

<YStack marginTop={formattedUserId ? 16 : 0}>
{/* Disclosure Items */}
<YStack marginTop={0}>
{disclosureItems.map((item, index) => (
<DisclosureItem
key={item.key}
Expand All @@ -354,6 +361,7 @@ const ProveScreen: React.FC = () => {
onVerify={onVerify}
selectedAppSessionId={selectedApp?.sessionId}
hasScrolledToBottom={hasScrolledToBottom}
isScrollable={isScrollable}
isReadyToProve={isReadyToProve}
isDocumentExpired={isDocumentExpired}
testID="prove-screen-verify-bar"
Expand Down
4 changes: 2 additions & 2 deletions app/src/screens/verification/ProvingScreenRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
isDocumentValidForProving,
pickBestDocumentToSelect,
} from '@selfxyz/mobile-sdk-alpha';
import { blue600 } from '@selfxyz/mobile-sdk-alpha/constants/colors';
import { black } from '@selfxyz/mobile-sdk-alpha/constants/colors';
import { dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts';

import { proofRequestColors } from '@/components/proof-request';
Expand Down Expand Up @@ -195,7 +195,7 @@ const ProvingScreenRouter: React.FC = () => {
</View>
) : (
<>
<ActivityIndicator color={blue600} size="large" />
<ActivityIndicator color={black} size="large" />
</>
)}
</View>
Expand Down
9 changes: 6 additions & 3 deletions app/src/stores/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ export const database: ProofDB = {
proof.documentId,
],
);
// Handle case where INSERT OR IGNORE skips insertion due to duplicate sessionId
return {
id: insertResult.insertId.toString(),
id: insertResult.insertId ? insertResult.insertId.toString() : '0',
timestamp,
rowsAffected: insertResult.rowsAffected,
};
Expand All @@ -154,8 +155,9 @@ export const database: ProofDB = {
proof.documentId,
],
);
// Handle case where INSERT OR IGNORE skips insertion due to duplicate sessionId
return {
id: insertResult.insertId.toString(),
id: insertResult.insertId ? insertResult.insertId.toString() : '0',
timestamp,
rowsAffected: insertResult.rowsAffected,
};
Expand All @@ -182,8 +184,9 @@ export const database: ProofDB = {
proof.documentId,
],
);
// Handle case where INSERT OR IGNORE skips insertion due to duplicate sessionId
return {
id: insertResult.insertId.toString(),
id: insertResult.insertId ? insertResult.insertId.toString() : '0',
timestamp,
rowsAffected: insertResult.rowsAffected,
};
Expand Down
37 changes: 37 additions & 0 deletions app/tests/src/stores/database.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,43 @@ describe('database (SQLite)', () => {
rowsAffected: 1,
});
});

it('handles duplicate sessionId gracefully (INSERT OR IGNORE skips)', async () => {
const mockProof = {
appName: 'TestApp',
sessionId: 'session-123',
userId: 'user-456',
userIdType: 'uuid' as const,
endpointType: 'https' as const,
status: ProofStatus.PENDING,
disclosures: '{"test": "data"}',
logoBase64: 'base64-logo',
documentId: 'document-123',
endpoint: 'https://example.com/endpoint',
};

// Simulate INSERT OR IGNORE behavior when a duplicate sessionId exists
const mockInsertResult = {
insertId: 0, // SQLite returns 0 for ignored inserts
rowsAffected: 0,
};

mockDb.executeSql.mockResolvedValueOnce([mockInsertResult]);

const result = await database.insertProof(mockProof);

expect(mockDb.executeSql).toHaveBeenCalledWith(
expect.stringContaining('INSERT OR IGNORE INTO proof_history'),
expect.any(Array),
);

// Should handle undefined/0 insertId gracefully
expect(result).toEqual({
id: '0',
timestamp: expect.any(Number),
rowsAffected: 0,
});
});
});

describe('updateProofStatus', () => {
Expand Down
29 changes: 29 additions & 0 deletions app/tests/src/stores/proofHistoryStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,35 @@ describe('proofHistoryStore', () => {
expect(mockDatabase.insertProof).toHaveBeenCalledWith(mockProof);
expect(useProofHistoryStore.getState().proofHistory).toHaveLength(0);
});

it('handles duplicate insertion gracefully (rowsAffected = 0)', async () => {
const mockProof = {
appName: 'TestApp',
sessionId: 'session-123',
userId: 'user-456',
userIdType: 'uuid',
endpointType: 'celo',
status: ProofStatus.PENDING,
disclosures: '{"test": "data"}',
} as const;

// Simulate INSERT OR IGNORE skipping the insertion due to duplicate sessionId
const mockInsertResult = {
id: '0',
timestamp: Date.now(),
rowsAffected: 0,
};

mockDatabase.insertProof.mockResolvedValue(mockInsertResult);

await act(async () => {
await useProofHistoryStore.getState().addProofHistory(mockProof);
});

expect(mockDatabase.insertProof).toHaveBeenCalledWith(mockProof);
// Should not add to store when rowsAffected is 0
expect(useProofHistoryStore.getState().proofHistory).toHaveLength(0);
});
});

describe('updateProofStatus', () => {
Expand Down
Loading
Loading