Show badge for inactive documents (#1487)

* Show badge for inactive documents

* fix

* refactor to use the new flag

* add inactive check to ProveScreen

* lint

* fix for proving button not working

* use new qrHashlogic

* increase bundle size threshold to 46MB

* remove commented out line

* add kyc related changes

---------

Co-authored-by: seshanthS <seshanth@protonmail.com>
This commit is contained in:
Leszek Stachowski
2026-02-12 20:05:47 +01:00
committed by GitHub
parent ec8b8fc419
commit abf01c82c0
16 changed files with 665 additions and 43 deletions

View File

@@ -21,6 +21,7 @@ interface HeldPrimaryButtonProveScreenProps {
isScrollable: boolean;
isReadyToProve: boolean;
isDocumentExpired: boolean;
hasCheckedForInactiveDocument: boolean;
}
interface ButtonContext {
@@ -29,6 +30,7 @@ interface ButtonContext {
isReadyToProve: boolean;
onVerify: () => void;
isDocumentExpired: boolean;
hasCheckedForInactiveDocument: boolean;
}
type ButtonEvent =
@@ -38,6 +40,7 @@ type ButtonEvent =
hasScrolledToBottom: boolean;
isReadyToProve: boolean;
isDocumentExpired: boolean;
hasCheckedForInactiveDocument: boolean;
}
| { type: 'VERIFY' };
@@ -56,6 +59,7 @@ const buttonMachine = createMachine(
isReadyToProve: false,
onVerify: input.onVerify,
isDocumentExpired: false,
hasCheckedForInactiveDocument: false,
}),
on: {
PROPS_UPDATED: {
@@ -177,13 +181,15 @@ const buttonMachine = createMachine(
context.selectedAppSessionId !== event.selectedAppSessionId ||
context.hasScrolledToBottom !== event.hasScrolledToBottom ||
context.isReadyToProve !== event.isReadyToProve ||
context.isDocumentExpired !== event.isDocumentExpired
context.isDocumentExpired !== event.isDocumentExpired ||
context.hasCheckedForInactiveDocument !== event.hasCheckedForInactiveDocument
) {
return {
selectedAppSessionId: event.selectedAppSessionId,
hasScrolledToBottom: event.hasScrolledToBottom,
isReadyToProve: event.isReadyToProve,
isDocumentExpired: event.isDocumentExpired,
hasCheckedForInactiveDocument: event.hasCheckedForInactiveDocument,
};
}
}
@@ -203,6 +209,7 @@ export const HeldPrimaryButtonProveScreen: React.FC<HeldPrimaryButtonProveScreen
isScrollable,
isReadyToProve,
isDocumentExpired,
hasCheckedForInactiveDocument,
}) => {
const [state, send] = useMachine(buttonMachine, {
input: { onVerify },
@@ -215,10 +222,18 @@ export const HeldPrimaryButtonProveScreen: React.FC<HeldPrimaryButtonProveScreen
hasScrolledToBottom,
isReadyToProve,
isDocumentExpired,
hasCheckedForInactiveDocument,
});
}, [selectedAppSessionId, hasScrolledToBottom, isReadyToProve, isDocumentExpired, send]);
}, [
selectedAppSessionId,
hasScrolledToBottom,
isReadyToProve,
isDocumentExpired,
hasCheckedForInactiveDocument,
send,
]);
const isDisabled = !state.matches('ready') && !state.matches('verifying');
const isDisabled = (!state.matches('ready') && !state.matches('verifying')) || !hasCheckedForInactiveDocument;
const LoadingContent: React.FC<{ text: string }> = ({ text }) => (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>

View File

@@ -236,13 +236,15 @@ export async function storeDocumentWithDeduplication(
// Add to catalog
const docType = passportData.documentType;
const documentCategory = passportData.documentCategory || inferDocumentCategory(docType);
const metadata: DocumentMetadata = {
id: contentHash,
documentType: docType,
documentCategory: passportData.documentCategory || inferDocumentCategory(docType),
documentCategory,
data: isMRZDocument(passportData) ? passportData.mrz : (passportData as AadhaarData).qrData || '',
mock: passportData.mock || false,
isRegistered: false,
hasExpirationDate: documentCategory === 'id_card' || documentCategory === 'passport',
};
catalog.documents.push(metadata);

View File

@@ -20,6 +20,7 @@ export interface DocumentMetadata {
mock: boolean;
isRegistered?: boolean;
registeredAt?: number; // timestamp (epoch ms) when document was registered
hasExpirationDate?: boolean; // whether the document has an expiration date
}
/**

View File

@@ -4,11 +4,12 @@
import { describe, expect, it } from 'vitest';
import type { DocumentCatalog } from '@selfxyz/common/types';
import type { AadhaarData, DocumentCatalog } from '@selfxyz/common';
import type { PassportData } from '@selfxyz/common/types/passport';
import type { DocumentsAdapter, SelfClient } from '../../src';
import { createSelfClient, defaultConfig, loadSelectedDocument } from '../../src';
import { storeDocumentWithDeduplication } from '../../src/documents/utils';
const createMockSelfClientWithDocumentsAdapter = (documentsAdapter: DocumentsAdapter): SelfClient => {
return createSelfClient({
@@ -160,3 +161,171 @@ describe('loadSelectedDocument', () => {
expect(saveDocumentCatalogSpy).not.toHaveBeenCalled();
});
});
describe('storeDocumentWithDeduplication', () => {
const passportDocument = {
mrz: 'P<UTOERIKSSON<<ANNA<MARIA<<<<<<',
eContent: [1, 2, 3],
documentType: 'passport',
documentCategory: 'passport',
} as PassportData;
const idCardDocument = {
mrz: 'P<UTOERIKSSON<<ANNA<MARIA<<<<<<',
eContent: [1, 2, 3],
documentType: 'id_card',
documentCategory: 'id_card',
} as PassportData;
const aadhaarDocument = {
qrData: 'test-qr-data',
documentType: 'aadhaar',
documentCategory: 'aadhaar',
} as AadhaarData;
it('sets hasExpirationDate to true for passport documents', async () => {
const emptyCatalog: DocumentCatalog = { documents: [] };
const loadDocumentCatalogSpy = vi.fn().mockResolvedValue(emptyCatalog);
const saveDocumentCatalogSpy = vi.fn();
const saveDocumentSpy = vi.fn();
const client = createMockSelfClientWithDocumentsAdapter({
loadDocumentCatalog: loadDocumentCatalogSpy,
loadDocumentById: vi.fn(),
saveDocumentCatalog: saveDocumentCatalogSpy,
saveDocument: saveDocumentSpy,
deleteDocument: vi.fn(),
});
await storeDocumentWithDeduplication(client, passportDocument);
expect(saveDocumentCatalogSpy).toHaveBeenCalledTimes(1);
const savedCatalog = saveDocumentCatalogSpy.mock.calls[0][0] as DocumentCatalog;
expect(savedCatalog.documents).toHaveLength(1);
expect(savedCatalog.documents[0].documentCategory).toBe('passport');
expect(savedCatalog.documents[0].hasExpirationDate).toBe(true);
});
it('sets hasExpirationDate to true for ID card documents', async () => {
const emptyCatalog: DocumentCatalog = { documents: [] };
const loadDocumentCatalogSpy = vi.fn().mockResolvedValue(emptyCatalog);
const saveDocumentCatalogSpy = vi.fn();
const saveDocumentSpy = vi.fn();
const client = createMockSelfClientWithDocumentsAdapter({
loadDocumentCatalog: loadDocumentCatalogSpy,
loadDocumentById: vi.fn(),
saveDocumentCatalog: saveDocumentCatalogSpy,
saveDocument: saveDocumentSpy,
deleteDocument: vi.fn(),
});
await storeDocumentWithDeduplication(client, idCardDocument);
expect(saveDocumentCatalogSpy).toHaveBeenCalledTimes(1);
const savedCatalog = saveDocumentCatalogSpy.mock.calls[0][0] as DocumentCatalog;
expect(savedCatalog.documents).toHaveLength(1);
expect(savedCatalog.documents[0].documentCategory).toBe('id_card');
expect(savedCatalog.documents[0].hasExpirationDate).toBe(true);
});
it('sets hasExpirationDate to false for Aadhaar documents', async () => {
const emptyCatalog: DocumentCatalog = { documents: [] };
const loadDocumentCatalogSpy = vi.fn().mockResolvedValue(emptyCatalog);
const saveDocumentCatalogSpy = vi.fn();
const saveDocumentSpy = vi.fn();
const client = createMockSelfClientWithDocumentsAdapter({
loadDocumentCatalog: loadDocumentCatalogSpy,
loadDocumentById: vi.fn(),
saveDocumentCatalog: saveDocumentCatalogSpy,
saveDocument: saveDocumentSpy,
deleteDocument: vi.fn(),
});
await storeDocumentWithDeduplication(client, aadhaarDocument);
expect(saveDocumentCatalogSpy).toHaveBeenCalledTimes(1);
const savedCatalog = saveDocumentCatalogSpy.mock.calls[0][0] as DocumentCatalog;
expect(savedCatalog.documents).toHaveLength(1);
expect(savedCatalog.documents[0].documentCategory).toBe('aadhaar');
expect(savedCatalog.documents[0].hasExpirationDate).toBe(false);
});
it('infers passport category and sets hasExpirationDate when documentCategory is missing', async () => {
const docWithoutCategory = {
mrz: 'P<UTOERIKSSON<<ANNA<MARIA<<<<<<',
eContent: [1, 2, 3],
documentType: 'passport',
} as PassportData;
const emptyCatalog: DocumentCatalog = { documents: [] };
const saveDocumentCatalogSpy = vi.fn();
const client = createMockSelfClientWithDocumentsAdapter({
loadDocumentCatalog: vi.fn().mockResolvedValue(emptyCatalog),
loadDocumentById: vi.fn(),
saveDocumentCatalog: saveDocumentCatalogSpy,
saveDocument: vi.fn(),
deleteDocument: vi.fn(),
});
await storeDocumentWithDeduplication(client, docWithoutCategory);
const savedCatalog = saveDocumentCatalogSpy.mock.calls[0][0] as DocumentCatalog;
expect(savedCatalog.documents[0].documentCategory).toBe('passport');
expect(savedCatalog.documents[0].hasExpirationDate).toBe(true);
});
it('infers id_card category and sets hasExpirationDate when documentCategory is missing', async () => {
const docWithoutCategory = {
mrz: 'P<UTOERIKSSON<<ANNA<MARIA<<<<<<',
eContent: [1, 2, 3],
documentType: 'id_card',
} as PassportData;
const emptyCatalog: DocumentCatalog = { documents: [] };
const saveDocumentCatalogSpy = vi.fn();
const client = createMockSelfClientWithDocumentsAdapter({
loadDocumentCatalog: vi.fn().mockResolvedValue(emptyCatalog),
loadDocumentById: vi.fn(),
saveDocumentCatalog: saveDocumentCatalogSpy,
saveDocument: vi.fn(),
deleteDocument: vi.fn(),
});
await storeDocumentWithDeduplication(client, docWithoutCategory);
const savedCatalog = saveDocumentCatalogSpy.mock.calls[0][0] as DocumentCatalog;
expect(savedCatalog.documents[0].documentCategory).toBe('id_card');
expect(savedCatalog.documents[0].hasExpirationDate).toBe(true);
});
it('infers aadhaar category and sets hasExpirationDate to false when documentCategory is missing', async () => {
const docWithoutCategory = {
qrData: 'test-qr-data',
documentType: 'aadhaar',
} as AadhaarData;
const emptyCatalog: DocumentCatalog = { documents: [] };
const saveDocumentCatalogSpy = vi.fn();
const client = createMockSelfClientWithDocumentsAdapter({
loadDocumentCatalog: vi.fn().mockResolvedValue(emptyCatalog),
loadDocumentById: vi.fn(),
saveDocumentCatalog: saveDocumentCatalogSpy,
saveDocument: vi.fn(),
deleteDocument: vi.fn(),
});
await storeDocumentWithDeduplication(client, docWithoutCategory);
const savedCatalog = saveDocumentCatalogSpy.mock.calls[0][0] as DocumentCatalog;
expect(savedCatalog.documents[0].documentCategory).toBe('aadhaar');
expect(savedCatalog.documents[0].hasExpirationDate).toBe(false);
});
});