mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 06:38:09 -05:00
SELF-1645: Disable Verify button for expired documents (#1497)
* Disable Verify button for expired documents * coderabbit feedbacks
This commit is contained in:
@@ -8,10 +8,6 @@ import { Dimensions } from 'react-native';
|
||||
import { Separator, Text, XStack, YStack } from 'tamagui';
|
||||
|
||||
import type { AadhaarData } from '@selfxyz/common';
|
||||
import {
|
||||
attributeToPosition,
|
||||
attributeToPosition_ID,
|
||||
} from '@selfxyz/common/constants';
|
||||
import type { PassportData } from '@selfxyz/common/types/passport';
|
||||
import { isAadhaarDocument, isMRZDocument } from '@selfxyz/common/utils/types';
|
||||
import {
|
||||
@@ -28,6 +24,11 @@ import EPassport from '@selfxyz/mobile-sdk-alpha/svgs/icons/epassport.svg';
|
||||
|
||||
import LogoGray from '@/assets/images/logo_gray.svg';
|
||||
import { SvgXml } from '@/components/homescreen/SvgXmlWrapper';
|
||||
import {
|
||||
formatDateFromYYMMDD,
|
||||
getDocumentAttributes,
|
||||
getNameAndSurname,
|
||||
} from '@/utils/documentAttributes';
|
||||
|
||||
// Import the logo SVG as a string
|
||||
const logoSvg = `<svg width="47" height="46" viewBox="0 0 47 46" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -332,6 +333,7 @@ const IdCardLayout: FC<IdCardLayoutAttributes> = ({
|
||||
name="DOB"
|
||||
value={formatDateFromYYMMDD(
|
||||
getDocumentAttributes(idDocument).dobSlice,
|
||||
true,
|
||||
)}
|
||||
maskValue="XX/XX/XXXX"
|
||||
hidden={hidden}
|
||||
@@ -342,7 +344,6 @@ const IdCardLayout: FC<IdCardLayoutAttributes> = ({
|
||||
name="EXPIRY DATE"
|
||||
value={formatDateFromYYMMDD(
|
||||
getDocumentAttributes(idDocument).expiryDateSlice,
|
||||
true,
|
||||
)}
|
||||
maskValue="XX/XX/XXXX"
|
||||
hidden={hidden}
|
||||
@@ -520,164 +521,3 @@ const IdAttribute: FC<IdAttributeProps> = ({
|
||||
};
|
||||
|
||||
export default IdCardLayout;
|
||||
|
||||
// Helper functions to safely extract document data
|
||||
function getDocumentAttributes(document: PassportData | AadhaarData) {
|
||||
if (isAadhaarDocument(document)) {
|
||||
return getAadhaarAttributes(document);
|
||||
} else if (isMRZDocument(document)) {
|
||||
return getPassportAttributes(document.mrz, document.documentCategory);
|
||||
} else {
|
||||
// Fallback for unknown document types
|
||||
return {
|
||||
nameSlice: '',
|
||||
dobSlice: '',
|
||||
yobSlice: '',
|
||||
issuingStateSlice: '',
|
||||
nationalitySlice: '',
|
||||
passNoSlice: '',
|
||||
sexSlice: '',
|
||||
expiryDateSlice: '',
|
||||
isPassportType: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getAadhaarAttributes(document: AadhaarData) {
|
||||
const extractedFields = document.extractedFields;
|
||||
// For Aadhaar, we format the name to work with the existing getNameAndSurname function
|
||||
// We'll put the full name in the "surname" position and leave names empty
|
||||
const fullName = extractedFields?.name || '';
|
||||
const nameSliceFormatted = fullName ? `${fullName}<<` : ''; // Format like MRZ
|
||||
|
||||
// Format DOB to YYMMDD for consistency with passport format
|
||||
let dobFormatted = '';
|
||||
if (extractedFields?.dob && extractedFields?.mob && extractedFields?.yob) {
|
||||
const year =
|
||||
extractedFields.yob.length === 4
|
||||
? extractedFields.yob.slice(-2)
|
||||
: extractedFields.yob;
|
||||
const month = extractedFields.mob.padStart(2, '0');
|
||||
const day = extractedFields.dob.padStart(2, '0');
|
||||
dobFormatted = `${year}${month}${day}`;
|
||||
}
|
||||
|
||||
return {
|
||||
nameSlice: nameSliceFormatted,
|
||||
dobSlice: dobFormatted,
|
||||
yobSlice: extractedFields?.yob || '',
|
||||
issuingStateSlice: extractedFields?.state || '',
|
||||
nationalitySlice: 'IND', // Aadhaar is always Indian
|
||||
passNoSlice: extractedFields?.aadhaarLast4Digits || '',
|
||||
sexSlice:
|
||||
extractedFields?.gender === 'M'
|
||||
? 'M'
|
||||
: extractedFields?.gender === 'F'
|
||||
? 'F'
|
||||
: extractedFields?.gender || '',
|
||||
expiryDateSlice: '', // Aadhaar doesn't expire
|
||||
isPassportType: false,
|
||||
};
|
||||
}
|
||||
|
||||
function getPassportAttributes(mrz: string, documentCategory: string) {
|
||||
const isPassportType = documentCategory === 'passport';
|
||||
const attributePositions = isPassportType
|
||||
? attributeToPosition
|
||||
: attributeToPosition_ID;
|
||||
|
||||
const nameSlice = mrz.slice(
|
||||
attributePositions.name[0],
|
||||
attributePositions.name[1],
|
||||
);
|
||||
const dobSlice = mrz.slice(
|
||||
attributePositions.date_of_birth[0],
|
||||
attributePositions.date_of_birth[1] + 1,
|
||||
);
|
||||
const yobSlice = mrz.slice(
|
||||
attributePositions.date_of_birth[0],
|
||||
attributePositions.date_of_birth[0] + 1,
|
||||
);
|
||||
const issuingStateSlice = mrz.slice(
|
||||
attributePositions.issuing_state[0],
|
||||
attributePositions.issuing_state[1] + 1,
|
||||
);
|
||||
const nationalitySlice = mrz.slice(
|
||||
attributePositions.nationality[0],
|
||||
attributePositions.nationality[1] + 1,
|
||||
);
|
||||
const passNoSlice = mrz.slice(
|
||||
attributePositions.passport_number[0],
|
||||
attributePositions.passport_number[1] + 1,
|
||||
);
|
||||
const sexSlice = mrz.slice(
|
||||
attributePositions.gender[0],
|
||||
attributePositions.gender[1] + 1,
|
||||
);
|
||||
const expiryDateSlice = mrz.slice(
|
||||
attributePositions.expiry_date[0],
|
||||
attributePositions.expiry_date[1] + 1,
|
||||
);
|
||||
return {
|
||||
nameSlice,
|
||||
dobSlice,
|
||||
yobSlice,
|
||||
issuingStateSlice,
|
||||
nationalitySlice,
|
||||
passNoSlice,
|
||||
sexSlice,
|
||||
expiryDateSlice,
|
||||
isPassportType,
|
||||
};
|
||||
}
|
||||
|
||||
function getNameAndSurname(nameSlice: string) {
|
||||
// Split by double << to separate surname from names
|
||||
const parts = nameSlice.split('<<');
|
||||
if (parts.length < 2) {
|
||||
return { surname: [], names: [] };
|
||||
}
|
||||
|
||||
// First part is surname, second part contains names separated by single <
|
||||
const surname = parts[0].replace(/</g, '').trim();
|
||||
const namesString = parts[1];
|
||||
|
||||
// Split names by single < and filter out empty strings
|
||||
const names = namesString.split('<').filter(name => name.length > 0);
|
||||
|
||||
return {
|
||||
surname: surname ? [surname] : [],
|
||||
names: names[0] ? [names[0]] : [],
|
||||
};
|
||||
}
|
||||
|
||||
function formatDateFromYYMMDD(
|
||||
dateString: string,
|
||||
isExpiry: boolean = false,
|
||||
): string {
|
||||
if (dateString.length !== 6) {
|
||||
return dateString;
|
||||
}
|
||||
|
||||
const yy = parseInt(dateString.substring(0, 2), 10);
|
||||
const mm = dateString.substring(2, 4);
|
||||
const dd = dateString.substring(4, 6);
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const century = Math.floor(currentYear / 100) * 100;
|
||||
let year = century + yy;
|
||||
|
||||
if (isExpiry) {
|
||||
// For expiry: if year is in the past, assume next century
|
||||
if (year < currentYear) {
|
||||
year += 100;
|
||||
}
|
||||
} else {
|
||||
// For birth: if year is in the future, assume previous century
|
||||
if (year > currentYear) {
|
||||
year -= 100;
|
||||
}
|
||||
}
|
||||
|
||||
return `${dd}/${mm}/${year}`;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,10 @@ import { useIsFocused, useNavigation } from '@react-navigation/native';
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { Eye, EyeOff } from '@tamagui/lucide-icons';
|
||||
|
||||
import { isMRZDocument } from '@selfxyz/common';
|
||||
import type { SelfAppDisclosureConfig } from '@selfxyz/common/utils/appType';
|
||||
import { formatEndpoint } from '@selfxyz/common/utils/scope';
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { loadSelectedDocument, useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import miscAnimation from '@selfxyz/mobile-sdk-alpha/animations/loading/misc.json';
|
||||
import {
|
||||
BodyText,
|
||||
@@ -48,6 +49,10 @@ import {
|
||||
import { getPointsAddress } from '@/services/points';
|
||||
import { useProofHistoryStore } from '@/stores/proofHistoryStore';
|
||||
import { ProofStatus } from '@/stores/proofTypes';
|
||||
import {
|
||||
checkDocumentExpiration,
|
||||
getDocumentAttributes,
|
||||
} from '@/utils/documentAttributes';
|
||||
import { formatUserId } from '@/utils/formatUserId';
|
||||
|
||||
const ProveScreen: React.FC = () => {
|
||||
@@ -64,6 +69,8 @@ const ProveScreen: React.FC = () => {
|
||||
const [scrollViewContentHeight, setScrollViewContentHeight] = useState(0);
|
||||
const [scrollViewHeight, setScrollViewHeight] = useState(0);
|
||||
const [showFullAddress, setShowFullAddress] = useState(false);
|
||||
const [isDocumentExpired, setIsDocumentExpired] = useState(false);
|
||||
const isDocumentExpiredRef = useRef(false);
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
|
||||
const isContentShorterThanScrollView = useMemo(
|
||||
@@ -115,11 +122,43 @@ const ProveScreen: React.FC = () => {
|
||||
|
||||
setDefaultDocumentTypeIfNeeded();
|
||||
|
||||
if (selectedAppRef.current?.sessionId !== selectedApp.sessionId) {
|
||||
provingStore.init(selfClient, 'disclose');
|
||||
}
|
||||
selectedAppRef.current = selectedApp;
|
||||
}, [selectedApp, isFocused, provingStore, selfClient]);
|
||||
const checkExpirationAndInit = async () => {
|
||||
let isExpired = false;
|
||||
try {
|
||||
const selectedDocument = await loadSelectedDocument(selfClient);
|
||||
if (!selectedDocument || !isMRZDocument(selectedDocument.data)) {
|
||||
setIsDocumentExpired(false);
|
||||
isExpired = false;
|
||||
isDocumentExpiredRef.current = false;
|
||||
} else {
|
||||
const { data: passportData } = selectedDocument;
|
||||
const attributes = getDocumentAttributes(passportData);
|
||||
const expiryDateSlice = attributes.expiryDateSlice;
|
||||
isExpired = checkDocumentExpiration(expiryDateSlice);
|
||||
setIsDocumentExpired(isExpired);
|
||||
isDocumentExpiredRef.current = isExpired;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking document expiration:', error);
|
||||
setIsDocumentExpired(false);
|
||||
isExpired = false;
|
||||
isDocumentExpiredRef.current = false;
|
||||
}
|
||||
|
||||
if (
|
||||
!isExpired &&
|
||||
selectedAppRef.current?.sessionId !== selectedApp.sessionId
|
||||
) {
|
||||
provingStore.init(selfClient, 'disclose');
|
||||
}
|
||||
selectedAppRef.current = selectedApp;
|
||||
};
|
||||
|
||||
checkExpirationAndInit();
|
||||
//removed provingStore from dependencies because it causes infinite re-render on longpressing the button
|
||||
//as it sets provingStore.setUserConfirmed()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedApp?.sessionId, isFocused, selfClient]);
|
||||
|
||||
// Enhance selfApp with user's points address if not already set
|
||||
useEffect(() => {
|
||||
@@ -206,7 +245,11 @@ const ProveScreen: React.FC = () => {
|
||||
const isCloseToBottom =
|
||||
layoutMeasurement.height + contentOffset.y >=
|
||||
contentSize.height - paddingToBottom;
|
||||
if (isCloseToBottom && !hasScrolledToBottom) {
|
||||
if (
|
||||
isCloseToBottom &&
|
||||
!hasScrolledToBottom &&
|
||||
!isDocumentExpiredRef.current
|
||||
) {
|
||||
setHasScrolledToBottom(true);
|
||||
buttonTap();
|
||||
trackEvent(ProofEvents.PROOF_DISCLOSURES_SCROLLED, {
|
||||
@@ -425,6 +468,7 @@ const ProveScreen: React.FC = () => {
|
||||
selectedAppSessionId={selectedApp?.sessionId}
|
||||
hasScrolledToBottom={hasScrolledToBottom}
|
||||
isReadyToProve={isReadyToProve}
|
||||
isDocumentExpired={isDocumentExpired}
|
||||
/>
|
||||
</ExpandableBottomLayout.BottomSection>
|
||||
</ExpandableBottomLayout.Layout>
|
||||
|
||||
236
app/src/utils/documentAttributes.ts
Normal file
236
app/src/utils/documentAttributes.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import type { AadhaarData } from '@selfxyz/common';
|
||||
import {
|
||||
attributeToPosition,
|
||||
attributeToPosition_ID,
|
||||
} from '@selfxyz/common/constants';
|
||||
import type { PassportData } from '@selfxyz/common/types/passport';
|
||||
import { isAadhaarDocument, isMRZDocument } from '@selfxyz/common/utils/types';
|
||||
|
||||
export interface DocumentAttributes {
|
||||
nameSlice: string;
|
||||
dobSlice: string;
|
||||
yobSlice: string;
|
||||
issuingStateSlice: string;
|
||||
nationalitySlice: string;
|
||||
passNoSlice: string;
|
||||
sexSlice: string;
|
||||
expiryDateSlice: string;
|
||||
isPassportType: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts attributes from Aadhaar document data
|
||||
*/
|
||||
function getAadhaarAttributes(document: AadhaarData): DocumentAttributes {
|
||||
const extractedFields = document.extractedFields;
|
||||
// For Aadhaar, we format the name to work with the existing getNameAndSurname function
|
||||
// We'll put the full name in the "surname" position and leave names empty
|
||||
const fullName = extractedFields?.name || '';
|
||||
const nameSliceFormatted = fullName ? `${fullName}<<` : ''; // Format like MRZ
|
||||
|
||||
// Format DOB to YYMMDD for consistency with passport format
|
||||
let dobFormatted = '';
|
||||
if (extractedFields?.dob && extractedFields?.mob && extractedFields?.yob) {
|
||||
const year =
|
||||
extractedFields.yob.length === 4
|
||||
? extractedFields.yob.slice(-2)
|
||||
: extractedFields.yob;
|
||||
const month = extractedFields.mob.padStart(2, '0');
|
||||
const day = extractedFields.dob.padStart(2, '0');
|
||||
dobFormatted = `${year}${month}${day}`;
|
||||
}
|
||||
|
||||
return {
|
||||
nameSlice: nameSliceFormatted,
|
||||
dobSlice: dobFormatted,
|
||||
yobSlice: extractedFields?.yob || '',
|
||||
issuingStateSlice: extractedFields?.state || '',
|
||||
nationalitySlice: 'IND', // Aadhaar is always Indian
|
||||
passNoSlice: extractedFields?.aadhaarLast4Digits || '',
|
||||
sexSlice:
|
||||
extractedFields?.gender === 'M'
|
||||
? 'M'
|
||||
: extractedFields?.gender === 'F'
|
||||
? 'F'
|
||||
: extractedFields?.gender || '',
|
||||
expiryDateSlice: '', // Aadhaar doesn't expire
|
||||
isPassportType: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts attributes from MRZ string (passport or ID card)
|
||||
*/
|
||||
function getPassportAttributes(
|
||||
mrz: string,
|
||||
documentCategory: string,
|
||||
): DocumentAttributes {
|
||||
const isPassportType = documentCategory === 'passport';
|
||||
const attributePositions = isPassportType
|
||||
? attributeToPosition
|
||||
: attributeToPosition_ID;
|
||||
|
||||
const nameSlice = mrz.slice(
|
||||
attributePositions.name[0],
|
||||
attributePositions.name[1],
|
||||
);
|
||||
const dobSlice = mrz.slice(
|
||||
attributePositions.date_of_birth[0],
|
||||
attributePositions.date_of_birth[1] + 1,
|
||||
);
|
||||
const yobSlice = mrz.slice(
|
||||
attributePositions.date_of_birth[0],
|
||||
attributePositions.date_of_birth[0] + 2,
|
||||
);
|
||||
const issuingStateSlice = mrz.slice(
|
||||
attributePositions.issuing_state[0],
|
||||
attributePositions.issuing_state[1] + 1,
|
||||
);
|
||||
const nationalitySlice = mrz.slice(
|
||||
attributePositions.nationality[0],
|
||||
attributePositions.nationality[1] + 1,
|
||||
);
|
||||
const passNoSlice = mrz.slice(
|
||||
attributePositions.passport_number[0],
|
||||
attributePositions.passport_number[1] + 1,
|
||||
);
|
||||
const sexSlice = mrz.slice(
|
||||
attributePositions.gender[0],
|
||||
attributePositions.gender[1] + 1,
|
||||
);
|
||||
const expiryDateSlice = mrz.slice(
|
||||
attributePositions.expiry_date[0],
|
||||
attributePositions.expiry_date[1] + 1,
|
||||
);
|
||||
return {
|
||||
nameSlice,
|
||||
dobSlice,
|
||||
yobSlice,
|
||||
issuingStateSlice,
|
||||
nationalitySlice,
|
||||
passNoSlice,
|
||||
sexSlice,
|
||||
expiryDateSlice,
|
||||
isPassportType,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a document expiration date (in YYMMDD format) has passed.
|
||||
* we assume dateOfExpiry is this century because ICAO standard for biometric passport
|
||||
* became standard around 2002
|
||||
* @param dateOfExpiry - Expiration date in YYMMDD format from MRZ
|
||||
* @returns true if the document is expired, false otherwise
|
||||
*/
|
||||
export function checkDocumentExpiration(dateOfExpiry: string): boolean {
|
||||
if (!dateOfExpiry || dateOfExpiry.length !== 6) {
|
||||
return false; // Invalid format, don't treat as expired
|
||||
}
|
||||
|
||||
const year = parseInt(dateOfExpiry.slice(0, 2), 10);
|
||||
const fullyear = 2000 + year;
|
||||
const month = parseInt(dateOfExpiry.slice(2, 4), 10) - 1; // JS months are 0-indexed
|
||||
const day = parseInt(dateOfExpiry.slice(4, 6), 10);
|
||||
|
||||
const expiryDateUTC = new Date(Date.UTC(fullyear, month, day, 0, 0, 0, 0));
|
||||
const nowUTC = new Date();
|
||||
const todayUTC = new Date(
|
||||
Date.UTC(
|
||||
nowUTC.getFullYear(),
|
||||
nowUTC.getMonth(),
|
||||
nowUTC.getDate(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
|
||||
return todayUTC >= expiryDateUTC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats date from YYMMDD format to DD/MM/YYYY format
|
||||
* For expiry (isDOB is false), we assume its this century because ICAO standard for biometric passport
|
||||
* became standard around 2002
|
||||
*/
|
||||
export function formatDateFromYYMMDD(
|
||||
dateString: string,
|
||||
isDOB: boolean = false,
|
||||
): string {
|
||||
if (dateString.length !== 6) {
|
||||
return dateString;
|
||||
}
|
||||
|
||||
const yy = parseInt(dateString.substring(0, 2), 10);
|
||||
const mm = dateString.substring(2, 4);
|
||||
const dd = dateString.substring(4, 6);
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const century = Math.floor(currentYear / 100) * 100;
|
||||
let year = century + yy;
|
||||
|
||||
if (isDOB) {
|
||||
// For birth: if year is in the future, assume previous century
|
||||
if (year > currentYear) {
|
||||
year -= 100;
|
||||
}
|
||||
}
|
||||
|
||||
return `${dd}/${mm}/${year}`;
|
||||
}
|
||||
|
||||
// Helper functions to safely extract document data
|
||||
export function getDocumentAttributes(
|
||||
document: PassportData | AadhaarData,
|
||||
): DocumentAttributes {
|
||||
if (isAadhaarDocument(document)) {
|
||||
return getAadhaarAttributes(document);
|
||||
} else if (isMRZDocument(document)) {
|
||||
return getPassportAttributes(document.mrz, document.documentCategory);
|
||||
} else {
|
||||
// Fallback for unknown document types
|
||||
return {
|
||||
nameSlice: '',
|
||||
dobSlice: '',
|
||||
yobSlice: '',
|
||||
issuingStateSlice: '',
|
||||
nationalitySlice: '',
|
||||
passNoSlice: '',
|
||||
sexSlice: '',
|
||||
expiryDateSlice: '',
|
||||
isPassportType: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses name from MRZ format (surname<<given names)
|
||||
* Returns separated surname and names arrays
|
||||
*/
|
||||
export function getNameAndSurname(nameSlice: string): {
|
||||
surname: string[];
|
||||
names: string[];
|
||||
} {
|
||||
// Split by double << to separate surname from names
|
||||
const parts = nameSlice.split('<<');
|
||||
if (parts.length < 2) {
|
||||
return { surname: [], names: [] };
|
||||
}
|
||||
|
||||
// First part is surname, second part contains names separated by single <
|
||||
const surname = parts[0].replace(/</g, '').trim();
|
||||
const namesString = parts[1];
|
||||
|
||||
// Split names by single < and filter out empty strings
|
||||
const names = namesString.split('<').filter(name => name.length > 0);
|
||||
|
||||
return {
|
||||
surname: surname ? [surname] : [],
|
||||
names: names[0] ? [names[0]] : [],
|
||||
};
|
||||
}
|
||||
@@ -19,6 +19,7 @@ interface HeldPrimaryButtonProveScreenProps {
|
||||
selectedAppSessionId: string | undefined | null;
|
||||
hasScrolledToBottom: boolean;
|
||||
isReadyToProve: boolean;
|
||||
isDocumentExpired: boolean;
|
||||
}
|
||||
|
||||
interface ButtonContext {
|
||||
@@ -26,6 +27,7 @@ interface ButtonContext {
|
||||
hasScrolledToBottom: boolean;
|
||||
isReadyToProve: boolean;
|
||||
onVerify: () => void;
|
||||
isDocumentExpired: boolean;
|
||||
}
|
||||
|
||||
type ButtonEvent =
|
||||
@@ -34,6 +36,7 @@ type ButtonEvent =
|
||||
selectedAppSessionId: string | undefined | null;
|
||||
hasScrolledToBottom: boolean;
|
||||
isReadyToProve: boolean;
|
||||
isDocumentExpired: boolean;
|
||||
}
|
||||
| { type: 'VERIFY' };
|
||||
|
||||
@@ -51,6 +54,7 @@ const buttonMachine = createMachine(
|
||||
hasScrolledToBottom: false,
|
||||
isReadyToProve: false,
|
||||
onVerify: input.onVerify,
|
||||
isDocumentExpired: false,
|
||||
}),
|
||||
on: {
|
||||
PROPS_UPDATED: {
|
||||
@@ -88,7 +92,7 @@ const buttonMachine = createMachine(
|
||||
},
|
||||
{
|
||||
target: 'ready',
|
||||
guard: ({ context }) => context.isReadyToProve,
|
||||
guard: ({ context }) => context.isReadyToProve && !context.isDocumentExpired,
|
||||
},
|
||||
],
|
||||
after: {
|
||||
@@ -107,7 +111,7 @@ const buttonMachine = createMachine(
|
||||
},
|
||||
{
|
||||
target: 'ready',
|
||||
guard: ({ context }) => context.isReadyToProve,
|
||||
guard: ({ context }) => context.isReadyToProve && !context.isDocumentExpired,
|
||||
},
|
||||
],
|
||||
after: {
|
||||
@@ -126,7 +130,7 @@ const buttonMachine = createMachine(
|
||||
},
|
||||
{
|
||||
target: 'ready',
|
||||
guard: ({ context }) => context.isReadyToProve,
|
||||
guard: ({ context }) => context.isReadyToProve && !context.isDocumentExpired,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -167,12 +171,14 @@ const buttonMachine = createMachine(
|
||||
if (
|
||||
context.selectedAppSessionId !== event.selectedAppSessionId ||
|
||||
context.hasScrolledToBottom !== event.hasScrolledToBottom ||
|
||||
context.isReadyToProve !== event.isReadyToProve
|
||||
context.isReadyToProve !== event.isReadyToProve ||
|
||||
context.isDocumentExpired !== event.isDocumentExpired
|
||||
) {
|
||||
return {
|
||||
selectedAppSessionId: event.selectedAppSessionId,
|
||||
hasScrolledToBottom: event.hasScrolledToBottom,
|
||||
isReadyToProve: event.isReadyToProve,
|
||||
isDocumentExpired: event.isDocumentExpired,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -190,6 +196,7 @@ export const HeldPrimaryButtonProveScreen: React.FC<HeldPrimaryButtonProveScreen
|
||||
selectedAppSessionId,
|
||||
hasScrolledToBottom,
|
||||
isReadyToProve,
|
||||
isDocumentExpired,
|
||||
}) => {
|
||||
const [state, send] = useMachine(buttonMachine, {
|
||||
input: { onVerify },
|
||||
@@ -201,12 +208,16 @@ export const HeldPrimaryButtonProveScreen: React.FC<HeldPrimaryButtonProveScreen
|
||||
selectedAppSessionId,
|
||||
hasScrolledToBottom,
|
||||
isReadyToProve,
|
||||
isDocumentExpired,
|
||||
});
|
||||
}, [selectedAppSessionId, hasScrolledToBottom, isReadyToProve, send]);
|
||||
}, [selectedAppSessionId, hasScrolledToBottom, isReadyToProve, isDocumentExpired, send]);
|
||||
|
||||
const isDisabled = !state.matches('ready');
|
||||
|
||||
const renderButtonContent = () => {
|
||||
if (isDocumentExpired) {
|
||||
return 'Document expired';
|
||||
}
|
||||
if (state.matches('waitingForSession')) {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
|
||||
Reference in New Issue
Block a user