SELF-1572: Update document scan prompt (#1511)

* Update document scan prompt

* formatting

* fix scan instruction location

* use helper for title text
This commit is contained in:
Justin Hernandez
2025-12-16 14:43:45 -08:00
committed by GitHub
parent 5321f3757b
commit a6194665ec
4 changed files with 119 additions and 68 deletions

View File

@@ -7,7 +7,11 @@ import { StyleSheet } from 'react-native';
import { View, XStack, YStack } from 'tamagui';
import { useIsFocused } from '@react-navigation/native';
import { DelayedLottieView, dinot } from '@selfxyz/mobile-sdk-alpha';
import {
DelayedLottieView,
dinot,
useSelfClient,
} from '@selfxyz/mobile-sdk-alpha';
import {
Additional,
Description,
@@ -31,14 +35,21 @@ import Scan from '@/assets/icons/passport_camera_scan.svg';
import { PassportCamera } from '@/components/native/PassportCamera';
import useHapticNavigation from '@/hooks/useHapticNavigation';
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';
import { getDocumentScanPrompt } from '@/utils/documentAttributes';
const DocumentCameraScreen: React.FC = () => {
const isFocused = useIsFocused();
const selfClient = useSelfClient();
const selectedDocumentType = selfClient.useMRZStore(
state => state.documentType,
);
// Add a ref to track when the camera screen is mounted
const scanStartTimeRef = useRef(Date.now());
const { onPassportRead } = useReadMRZ(scanStartTimeRef);
const scanPrompt = getDocumentScanPrompt(selectedDocumentType);
const navigateToHome = useHapticNavigation('Home', {
action: 'cancel',
});
@@ -63,7 +74,7 @@ const DocumentCameraScreen: React.FC = () => {
<ExpandableBottomLayout.BottomSection backgroundColor={white}>
<YStack alignItems="center" gap="$2.5">
<YStack alignItems="center" gap="$6" paddingBottom="$2.5">
<Title>Scan your ID</Title>
<Title>{scanPrompt}</Title>
<XStack gap="$6" alignSelf="flex-start" alignItems="flex-start">
<View paddingTop="$2">
<Scan height={40} width={40} color={slate800} />

View File

@@ -8,6 +8,7 @@ import { StyleSheet } from 'react-native';
import { SystemBars } from 'react-native-edge-to-edge';
import { useNavigation } from '@react-navigation/native';
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
import {
Additional,
ButtonsContainer,
@@ -28,12 +29,19 @@ import passportOnboardingAnimation from '@/assets/animations/passport_onboarding
import useHapticNavigation from '@/hooks/useHapticNavigation';
import { impactLight } from '@/integrations/haptics';
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';
import { getDocumentScanPrompt } from '@/utils/documentAttributes';
const DocumentOnboardingScreen: React.FC = () => {
const navigation = useNavigation();
const selfClient = useSelfClient();
const selectedDocumentType = selfClient.useMRZStore(
state => state.documentType,
);
const handleCameraPress = useHapticNavigation('DocumentCamera');
const animationRef = useRef<LottieView>(null);
const scanPrompt = getDocumentScanPrompt(selectedDocumentType);
const onCancelPress = () => {
impactLight();
navigation.goBack();
@@ -69,7 +77,7 @@ const DocumentOnboardingScreen: React.FC = () => {
</ExpandableBottomLayout.TopSection>
<ExpandableBottomLayout.BottomSection backgroundColor={white}>
<TextsContainer>
<Title>Scan your ID</Title>
<Title>{scanPrompt}</Title>
<Description textBreakStrategy="balanced">
Open to the photo page
</Description>

View File

@@ -10,6 +10,11 @@ import {
import type { PassportData } from '@selfxyz/common/types/passport';
import { isAadhaarDocument, isMRZDocument } from '@selfxyz/common/utils/types';
/**
* Gets the scan prompt for a document type.
* @param documentType - Document type code ('p' = Passport, 'i' = ID card, 'a' = Aadhaar)
* @returns Scan prompt text
*/
export interface DocumentAttributes {
nameSlice: string;
dobSlice: string;
@@ -22,6 +27,71 @@ export interface DocumentAttributes {
isPassportType: boolean;
}
/**
* 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}`;
}
/**
* Extracts attributes from Aadhaar document data
*/
@@ -119,71 +189,6 @@ function getPassportAttributes(
};
}
/**
* 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,
@@ -208,6 +213,31 @@ export function getDocumentAttributes(
}
}
/**
* Gets the display name for a document type code.
* @param documentType - Document type code ('p' = Passport, 'i' = ID card, 'a' = Aadhaar)
* @returns Human-readable document name
*/
export function getDocumentScanPrompt(
documentType: string | undefined,
): string {
const documentName = getDocumentTypeName(documentType);
return `Scan your ${documentName}`;
}
export function getDocumentTypeName(documentType: string | undefined): string {
switch (documentType) {
case 'p':
return 'Passport';
case 'i':
return 'ID';
case 'a':
return 'Aadhaar';
default:
return 'ID';
}
}
/**
* Parses name from MRZ format (surname<<given names)
* Returns separated surname and names arrays

View File

@@ -80,6 +80,8 @@ const IDSelectionScreen: React.FC<IDSelectionScreenProps> = props => {
const onSelectDocumentType = (docType: string) => {
buttonTap();
selfClient.getMRZState().update({ documentType: docType });
selfClient.emit(SdkEvents.DOCUMENT_TYPE_SELECTED, {
documentType: docType,
documentName: getDocumentNameForEvent(docType),