mirror of
https://github.com/selfxyz/self.git
synced 2026-01-07 22:04:03 -05:00
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:
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user