mirror of
https://github.com/selfxyz/self.git
synced 2026-02-19 02:24:25 -05:00
add new home screen (#1019)
* add new home screen * fix typing issue * yarn nice
This commit is contained in:
committed by
GitHub
parent
ff678b359a
commit
78b2341091
@@ -13,9 +13,10 @@ import type { SelfApp } from '@selfxyz/common/utils/appType';
|
||||
|
||||
import { NavBar } from '@/components/NavBar/BaseNavBar';
|
||||
import ActivityIcon from '@/images/icons/activity.svg';
|
||||
import ScanIcon from '@/images/icons/qr_scan.svg';
|
||||
import SettingsIcon from '@/images/icons/settings.svg';
|
||||
import { useSelfAppStore } from '@/stores/selfAppStore';
|
||||
import { black, neutral400, white } from '@/utils/colors';
|
||||
import { black, charcoal, neutral400, slate50, white } from '@/utils/colors';
|
||||
import { extraYPadding } from '@/utils/constants';
|
||||
import { buttonTap } from '@/utils/haptic';
|
||||
|
||||
@@ -70,44 +71,56 @@ export const HomeNavBar = (props: NativeStackHeaderProps) => {
|
||||
};
|
||||
return (
|
||||
<NavBar.Container
|
||||
backgroundColor={black}
|
||||
backgroundColor={slate50}
|
||||
barStyle={'light'}
|
||||
padding={16}
|
||||
padding={8}
|
||||
justifyContent="space-between"
|
||||
paddingTop={Math.max(insets.top, 15) + extraYPadding}
|
||||
>
|
||||
<NavBar.LeftAction
|
||||
component={
|
||||
<Button
|
||||
size={'$3'}
|
||||
unstyled
|
||||
icon={
|
||||
<ActivityIcon width={'24'} height={'100%'} color={neutral400} />
|
||||
}
|
||||
/>
|
||||
<XStack alignItems="center">
|
||||
<Button
|
||||
size={'$3'}
|
||||
unstyled
|
||||
icon={<ScanIcon width={'24'} height={'100%'} color={charcoal} />}
|
||||
onPress={() => {
|
||||
buttonTap();
|
||||
props.navigation.navigate('QRCodeViewFinder');
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size={'$3'}
|
||||
unstyled
|
||||
marginTop={10}
|
||||
icon={<ClipboardIcon size={24} color={charcoal} />}
|
||||
onPress={handleConsumeToken}
|
||||
/>
|
||||
</XStack>
|
||||
}
|
||||
// disable icon click for now
|
||||
onPress={() => {
|
||||
buttonTap();
|
||||
props.navigation.navigate('ProofHistory');
|
||||
}}
|
||||
/>
|
||||
<NavBar.Title size="large" color={white}>
|
||||
<NavBar.Title size="large" color={black}>
|
||||
{props.options.title}
|
||||
</NavBar.Title>
|
||||
<NavBar.RightAction
|
||||
component={
|
||||
<XStack alignItems="center" gap={10}>
|
||||
<ClipboardIcon
|
||||
size={24}
|
||||
color={neutral400}
|
||||
onPress={handleConsumeToken}
|
||||
<XStack alignItems="center">
|
||||
<Button
|
||||
size={'$3'}
|
||||
unstyled
|
||||
icon={
|
||||
<ActivityIcon width={'24'} height={'100%'} color={charcoal} />
|
||||
}
|
||||
onPress={() => {
|
||||
buttonTap();
|
||||
props.navigation.navigate('ProofHistory');
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size={'$3'}
|
||||
unstyled
|
||||
icon={
|
||||
<SettingsIcon width={'24'} height={'100%'} color={neutral400} />
|
||||
<SettingsIcon width={'24'} height={'100%'} color={charcoal} />
|
||||
}
|
||||
onPress={() => {
|
||||
buttonTap();
|
||||
|
||||
56
app/src/components/NavBar/IdDetailsNavBar.tsx
Normal file
56
app/src/components/NavBar/IdDetailsNavBar.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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 React from 'react';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Button, Text, View, XStack } from 'tamagui';
|
||||
import type { NativeStackHeaderProps } from '@react-navigation/native-stack';
|
||||
import { ChevronLeft } from '@tamagui/lucide-icons';
|
||||
|
||||
import { NavBar } from '@/components/NavBar/BaseNavBar';
|
||||
import { black, charcoal, slate50 } from '@/utils/colors';
|
||||
import { extraYPadding } from '@/utils/constants';
|
||||
import { buttonTap } from '@/utils/haptic';
|
||||
|
||||
export const IdDetailsNavBar = (props: NativeStackHeaderProps) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const backButtonWidth = 50; // Adjusted for text
|
||||
|
||||
return (
|
||||
<NavBar.Container
|
||||
backgroundColor={slate50}
|
||||
barStyle={'light'}
|
||||
justifyContent="space-between"
|
||||
paddingTop={Math.max(insets.top, 15) + extraYPadding}
|
||||
>
|
||||
<NavBar.LeftAction
|
||||
component={
|
||||
<Button
|
||||
unstyled
|
||||
marginLeft={'$3.5'}
|
||||
padding={'$3'}
|
||||
width={'$10'}
|
||||
onPress={() => {
|
||||
buttonTap();
|
||||
props.navigation.goBack();
|
||||
}}
|
||||
>
|
||||
<Text color={charcoal} fontSize={17} fontWeight="bold">
|
||||
Done
|
||||
</Text>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<NavBar.Title size="large" color={black}>
|
||||
{props.options.title}
|
||||
</NavBar.Title>
|
||||
<NavBar.RightAction
|
||||
component={
|
||||
// Spacer to balance the back button and center the title
|
||||
<View style={{ width: backButtonWidth }} />
|
||||
}
|
||||
/>
|
||||
</NavBar.Container>
|
||||
);
|
||||
};
|
||||
@@ -4,4 +4,5 @@
|
||||
|
||||
export { DefaultNavBar } from '@/components/NavBar/DefaultNavBar';
|
||||
export { HomeNavBar } from '@/components/NavBar/HomeNavBar';
|
||||
export { IdDetailsNavBar } from '@/components/NavBar/IdDetailsNavBar';
|
||||
export { ProgressNavBar } from '@/components/NavBar/ProgressNavBar';
|
||||
|
||||
578
app/src/components/homeScreen/idCard.tsx
Normal file
578
app/src/components/homeScreen/idCard.tsx
Normal file
@@ -0,0 +1,578 @@
|
||||
// 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 React, { useCallback, useState } from 'react';
|
||||
import { Dimensions, Pressable } from 'react-native';
|
||||
import { SvgXml } from 'react-native-svg';
|
||||
import { Button, Image, Separator, Text, XStack, YStack } from 'tamagui';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
|
||||
import {
|
||||
attributeToPosition,
|
||||
attributeToPosition_ID,
|
||||
formatMrz,
|
||||
PassportData,
|
||||
} from '@selfxyz/common/dist/esm';
|
||||
import { pad } from '@selfxyz/common/dist/esm/src/utils/passports/passport';
|
||||
|
||||
import EPassport from '@/images/icons/epassport.svg';
|
||||
import LogoGray from '@/images/logo_gray.svg';
|
||||
import LogoInversed from '@/images/logo_inversed.svg';
|
||||
import { usePassport } from '@/providers/passportDataProvider';
|
||||
import {
|
||||
black,
|
||||
blue600,
|
||||
green500,
|
||||
red500,
|
||||
slate50,
|
||||
slate100,
|
||||
slate300,
|
||||
slate400,
|
||||
slate500,
|
||||
white,
|
||||
} from '@/utils/colors';
|
||||
import { dinot, plexMono } from '@/utils/fonts';
|
||||
|
||||
// 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">
|
||||
<path d="M12.7814 13.2168C12.7814 12.7057 13.1992 12.2969 13.7214 12.2969H30.0017L42.5676 0H11.2408L0 11.0001V29.0973H12.7814V13.2104V13.2168Z" fill="white"/>
|
||||
<path d="M34.2186 16.8515V32.3552C34.2186 32.8663 33.8008 33.2751 33.2786 33.2751H17.4357L4.43236 46H35.7592L47 34.9999V16.8579H34.2186V16.8515Z" fill="white"/>
|
||||
<path d="M28.9703 17.6525H18.0362V28.3539H28.9703V17.6525Z" fill="#00FFB6"/>
|
||||
</svg>`;
|
||||
|
||||
interface IdCardLayoutAttributes {
|
||||
idDocument: PassportData;
|
||||
selected: boolean;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
// This layout should be fully adaptative. I should perfectly fit in any screen size.
|
||||
// the font size should adapt according to the size available to fit perfectly.
|
||||
// only svg are allowed.
|
||||
// each element size should be determined as % of the screen or the parent element
|
||||
// the border radius should be adaptative too, as well as the padding
|
||||
// this is the v0 of this component so we should only use placholders for now, no need to pass the real passport data as parameters.
|
||||
const IdCardLayout: React.FC<IdCardLayoutAttributes> = ({
|
||||
idDocument,
|
||||
selected,
|
||||
hidden,
|
||||
}) => {
|
||||
// Function to mask MRZ characters except '<' and spaces
|
||||
const maskMrzValue = (text: string): string => {
|
||||
return text.replace(/./g, 'X');
|
||||
};
|
||||
|
||||
// Get screen dimensions for adaptive sizing
|
||||
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
|
||||
|
||||
// Calculate adaptive sizes based on screen dimensions
|
||||
// Reduce width slightly to account for horizontal margins (8px each side = 16px total)
|
||||
const cardWidth = screenWidth * 0.95 - 16; // 90% of screen width minus margin space
|
||||
const cardHeight = selected ? cardWidth * 0.645 : cardWidth * 0.645 * 0.3; // ID card aspect ratio (roughly 1.6:1)
|
||||
const borderRadius = cardWidth * 0.04; // 4% of card width
|
||||
const padding = cardWidth * 0.035; // 4% of card width
|
||||
const fontSize = {
|
||||
large: cardWidth * 0.045,
|
||||
medium: cardWidth * 0.032,
|
||||
small: cardWidth * 0.028,
|
||||
xsmall: cardWidth * 0.022,
|
||||
};
|
||||
|
||||
// Image dimensions (standard ID photo ratio)
|
||||
const imageSize = {
|
||||
width: cardWidth * 0.2, // 25% of card width
|
||||
height: cardWidth * 0.29, // ID photo aspect ratio
|
||||
};
|
||||
|
||||
// Shared left offset for content that should align with the start of the attributes block
|
||||
const contentLeftOffset = imageSize.width + padding;
|
||||
|
||||
return (
|
||||
// Container wrapper to handle shadow space properly
|
||||
<YStack
|
||||
width="100%" // Add space for horizontal margins
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<YStack
|
||||
width={cardWidth}
|
||||
height={cardHeight}
|
||||
backgroundColor={white}
|
||||
borderRadius={borderRadius}
|
||||
borderWidth={0.75}
|
||||
borderColor={'#E0E0E0'}
|
||||
padding={padding}
|
||||
// Improved shadow configuration for better visibility and containment
|
||||
shadowColor={black}
|
||||
shadowOffset={{ width: 0, height: 2 }}
|
||||
shadowOpacity={0.1}
|
||||
shadowRadius={4}
|
||||
elevation={4}
|
||||
// Add margins to provide space for shadow bleeding
|
||||
marginBottom={8}
|
||||
justifyContent="center"
|
||||
>
|
||||
{/* Header Section */}
|
||||
<XStack>
|
||||
<XStack alignItems="center">
|
||||
<EPassport
|
||||
width={fontSize.large * 3}
|
||||
height={fontSize.large * 3 * 0.617}
|
||||
/>
|
||||
<YStack marginLeft={imageSize.width - fontSize.large * 3}>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontFamily={dinot}
|
||||
fontSize={fontSize.large * 1.4}
|
||||
color="black"
|
||||
>
|
||||
{idDocument.documentCategory === 'passport'
|
||||
? 'Passport'
|
||||
: 'ID Card'}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize={fontSize.small}
|
||||
color={'#9193A2'}
|
||||
fontFamily={dinot}
|
||||
>
|
||||
Verified{' '}
|
||||
{idDocument.documentCategory === 'passport'
|
||||
? 'Biometric Passport'
|
||||
: ' Biometric ID Card'}
|
||||
</Text>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<XStack flex={1} justifyContent="flex-end">
|
||||
{idDocument.mock && (
|
||||
<YStack
|
||||
marginTop={padding / 4}
|
||||
borderWidth={1}
|
||||
borderColor={slate300}
|
||||
borderRadius={100}
|
||||
paddingHorizontal={padding / 2}
|
||||
alignSelf="flex-start"
|
||||
backgroundColor={slate100}
|
||||
paddingVertical={padding / 8}
|
||||
>
|
||||
<Text
|
||||
fontSize={fontSize.xsmall}
|
||||
color={slate400}
|
||||
fontFamily={dinot}
|
||||
letterSpacing={fontSize.xsmall * 0.15}
|
||||
>
|
||||
DEVELOPER
|
||||
</Text>
|
||||
</YStack>
|
||||
)}
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
{selected && (
|
||||
<Separator
|
||||
backgroundColor={'#E0E0E0'}
|
||||
height={1}
|
||||
width={cardWidth - 1}
|
||||
marginLeft={-padding}
|
||||
marginTop={padding}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Main Content Section */}
|
||||
{selected && (
|
||||
<XStack height="60%" paddingVertical={padding}>
|
||||
{/* Person Image */}
|
||||
<YStack
|
||||
width={imageSize.width}
|
||||
height={imageSize.height}
|
||||
backgroundColor="#F5F5F5"
|
||||
borderRadius={borderRadius * 0.5}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
marginRight={padding}
|
||||
opacity={hidden ? 0.3 : 1}
|
||||
>
|
||||
<SvgXml
|
||||
xml={logoSvg}
|
||||
width={imageSize.width * 0.6}
|
||||
height={imageSize.height * 0.6}
|
||||
/>
|
||||
</YStack>
|
||||
|
||||
{/* ID Attributes */}
|
||||
<YStack
|
||||
flex={1}
|
||||
justifyContent="space-between"
|
||||
height={imageSize.height}
|
||||
>
|
||||
<XStack flex={1} gap={padding * 0.3}>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="TYPE"
|
||||
value={
|
||||
idDocument.documentCategory === 'passport'
|
||||
? 'PASSPORT'
|
||||
: 'ID CARD'
|
||||
}
|
||||
maskValue={
|
||||
idDocument.documentCategory === 'passport'
|
||||
? 'PASSPORT'
|
||||
: 'ID CARD'
|
||||
}
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="CODE"
|
||||
value={idDocument.mock ? 'SELF DEV' : 'SELF ID'}
|
||||
maskValue={idDocument.mock ? 'SELF DEV' : 'SELF ID'}
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="DOC NO."
|
||||
value={
|
||||
getPassportAttributes(
|
||||
idDocument.mrz,
|
||||
idDocument.documentCategory,
|
||||
).passNoSlice
|
||||
}
|
||||
maskValue="XX-XXXXXXX"
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<XStack flex={1} gap={padding * 0.3}>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="SURNAME"
|
||||
value={getNameAndSurname(
|
||||
getPassportAttributes(
|
||||
idDocument.mrz,
|
||||
idDocument.documentCategory,
|
||||
).nameSlice,
|
||||
).surname.join(' ')}
|
||||
maskValue="XXXXXXXX"
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="NAME"
|
||||
value={getNameAndSurname(
|
||||
getPassportAttributes(
|
||||
idDocument.mrz,
|
||||
idDocument.documentCategory,
|
||||
).nameSlice,
|
||||
).names.join(' ')}
|
||||
maskValue="XXXXX"
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="SEX"
|
||||
value={
|
||||
getPassportAttributes(
|
||||
idDocument.mrz,
|
||||
idDocument.documentCategory,
|
||||
).sexSlice
|
||||
}
|
||||
maskValue="X"
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<XStack flex={1} gap={padding * 0.3}>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="NATIONALITY"
|
||||
value={
|
||||
getPassportAttributes(
|
||||
idDocument.mrz,
|
||||
idDocument.documentCategory,
|
||||
).nationalitySlice
|
||||
}
|
||||
maskValue="XXX"
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="DOB"
|
||||
value={formatDateFromYYMMDD(
|
||||
getPassportAttributes(
|
||||
idDocument.mrz,
|
||||
idDocument.documentCategory,
|
||||
).dobSlice,
|
||||
)}
|
||||
maskValue="XX/XX/XXXX"
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="EXPIRY DATE"
|
||||
value={formatDateFromYYMMDD(
|
||||
getPassportAttributes(
|
||||
idDocument.mrz,
|
||||
idDocument.documentCategory,
|
||||
).expiryDateSlice,
|
||||
true,
|
||||
)}
|
||||
maskValue="XX/XX/XXXX"
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
</XStack>
|
||||
<XStack flex={1} gap={padding * 0.3}>
|
||||
<YStack flex={1}>
|
||||
<IdAttribute
|
||||
name="AUTHORITY"
|
||||
value={
|
||||
getPassportAttributes(
|
||||
idDocument.mrz,
|
||||
idDocument.documentCategory,
|
||||
).issuingStateSlice
|
||||
}
|
||||
maskValue="XXX"
|
||||
hidden={hidden}
|
||||
/>
|
||||
</YStack>
|
||||
<YStack flex={1} />
|
||||
<YStack flex={1} />
|
||||
</XStack>
|
||||
</YStack>
|
||||
</XStack>
|
||||
)}
|
||||
|
||||
{/* Footer Section - MRZ */}
|
||||
{selected && (
|
||||
<XStack
|
||||
alignItems="center"
|
||||
backgroundColor={slate100}
|
||||
borderRadius={borderRadius / 3}
|
||||
paddingHorizontal={padding / 2}
|
||||
paddingVertical={padding / 4}
|
||||
>
|
||||
{/* Fixed-width spacer to align MRZ content with the attributes block */}
|
||||
<XStack width={contentLeftOffset} alignItems="center">
|
||||
<LogoGray width={fontSize.large} height={fontSize.large} />
|
||||
</XStack>
|
||||
|
||||
<YStack marginLeft={-padding / 2}>
|
||||
{idDocument.documentCategory === 'passport' ? (
|
||||
// Passport: 2 lines, 88 chars total (44 chars each)
|
||||
<>
|
||||
<Text
|
||||
fontSize={fontSize.xsmall}
|
||||
letterSpacing={fontSize.xsmall * 0.1}
|
||||
fontFamily={plexMono}
|
||||
color={slate400}
|
||||
>
|
||||
{hidden
|
||||
? maskMrzValue(idDocument.mrz.slice(0, 44))
|
||||
: idDocument.mrz.slice(0, 44)}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize={fontSize.xsmall}
|
||||
letterSpacing={fontSize.xsmall * 0.1}
|
||||
fontFamily={plexMono}
|
||||
color={slate400}
|
||||
>
|
||||
{hidden
|
||||
? maskMrzValue(idDocument.mrz.slice(44, 88))
|
||||
: idDocument.mrz.slice(44, 88)}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
// ID Card: 3 lines, 90 chars total (30 chars each)
|
||||
<>
|
||||
<Text
|
||||
fontSize={fontSize.xsmall}
|
||||
letterSpacing={fontSize.xsmall * 0.44}
|
||||
fontFamily={plexMono}
|
||||
color={slate400}
|
||||
>
|
||||
{hidden
|
||||
? maskMrzValue(idDocument.mrz.slice(0, 30))
|
||||
: idDocument.mrz.slice(0, 30)}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize={fontSize.xsmall}
|
||||
letterSpacing={fontSize.xsmall * 0.44}
|
||||
fontFamily={plexMono}
|
||||
color={slate400}
|
||||
>
|
||||
{hidden
|
||||
? maskMrzValue(idDocument.mrz.slice(30, 60))
|
||||
: idDocument.mrz.slice(30, 60)}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize={fontSize.xsmall}
|
||||
letterSpacing={fontSize.xsmall * 0.44}
|
||||
fontFamily={plexMono}
|
||||
color={slate400}
|
||||
>
|
||||
{hidden
|
||||
? maskMrzValue(idDocument.mrz.slice(60, 90))
|
||||
: idDocument.mrz.slice(60, 90)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</YStack>
|
||||
</XStack>
|
||||
)}
|
||||
</YStack>
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
// Interface for IdAttribute props
|
||||
interface IdAttributeProps {
|
||||
name: string;
|
||||
value: string;
|
||||
maskValue: string;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
// This layout should be fully adaptative. I should perfectly fit in any screen size.
|
||||
// the font size should adapt according to the size available to fit perfectly.
|
||||
// only svg are allowed.
|
||||
// each element size should be determined as % of the screen or the parent element
|
||||
const IdAttribute: React.FC<IdAttributeProps> = ({
|
||||
name,
|
||||
value,
|
||||
maskValue,
|
||||
hidden = false,
|
||||
}) => {
|
||||
const { width: screenWidth } = Dimensions.get('window');
|
||||
const fontSize = {
|
||||
label: screenWidth * 0.024,
|
||||
value: screenWidth * 0.02,
|
||||
};
|
||||
|
||||
const displayValue = hidden ? maskValue : value;
|
||||
|
||||
return (
|
||||
<YStack>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontSize={fontSize.label}
|
||||
color={slate500}
|
||||
fontFamily={dinot}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
<Text fontSize={fontSize.value} color={slate400} fontFamily={dinot}>
|
||||
{displayValue}
|
||||
</Text>
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default IdCardLayout;
|
||||
|
||||
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}`;
|
||||
}
|
||||
Reference in New Issue
Block a user