Skip to content

Commit

Permalink
add: mvp age verification
Browse files Browse the repository at this point in the history
  • Loading branch information
sirpy committed Nov 24, 2024
1 parent d10e292 commit 7ff12f5
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 28 deletions.
94 changes: 94 additions & 0 deletions src/components/faceVerification/components/AgeCheckError.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { useEffect } from 'react'
import { View } from 'react-native'
import { t } from '@lingui/macro'

import Text from '../../common/view/Text'
import Separator from '../../common/layout/Separator'
import { Section } from '../../common'
import FaceVerificationErrorSmiley from '../../common/animations/FaceVerificationErrorSmiley'

import { isMobileOnly } from '../../../lib/utils/platform'
import { getDesignRelativeHeight, getDesignRelativeWidth } from '../../../lib/utils/sizes'
import { withStyles } from '../../../lib/styles'

import { fireEvent, FV_AGECHECKERROR } from '../../../lib/analytics/analytics'

const AgeCheckError = ({ styles, displayTitle, onRetry, nav, exception }) => {
useEffect(() => {
if (!exception) {
return
}

fireEvent(FV_AGECHECKERROR)
}, [])

return (
<Section style={styles.descriptionContainer} justifyContent="space-evenly">
<Section.Title fontWeight="medium" textTransform="none" color="red">
{displayTitle}
{(displayTitle ? `,\n` : '') +
t`Unfortunately,
it seems your are under 18`}
</Section.Title>
<Section.Row justifyContent="space-evenly">
<View style={styles.halfIllustration}>
<FaceVerificationErrorSmiley />
</View>
<View style={styles.halfIllustration}>
<FaceVerificationErrorSmiley />
</View>
</Section.Row>
<Section style={styles.errorSection}>
<Separator width={2} />
<View style={styles.descriptionWrapper}>
<Text color="primary" fontWeight="bold" fontSize={18} lineHeight={25}>
{t`Only persons 18 years or older can use our service`}
</Text>
<Text color="primary" fontSize={18} lineHeight={25}>
{t`If you think this is a mistake
please contact our support`}
</Text>
</View>
<Separator width={2} />
</Section>
</Section>
)
}

const getStylesFromProps = ({ theme }) => {
return {
halfIllustration: {
marginTop: isMobileOnly ? getDesignRelativeHeight(25) : 0,
marginBottom: isMobileOnly ? getDesignRelativeHeight(30) : 0,
width: getDesignRelativeWidth(130, false),
maxHeight: isMobileOnly ? getDesignRelativeHeight(97) : 'auto',
display: 'flex',
justifyContent: 'center',
marginRight: 0,
marginLeft: 0,
},
descriptionContainer: {
flex: 1,
marginBottom: 0,
paddingBottom: getDesignRelativeHeight(theme.sizes.defaultDouble),
paddingLeft: getDesignRelativeWidth(theme.sizes.default),
paddingRight: getDesignRelativeWidth(theme.sizes.default),
paddingTop: getDesignRelativeHeight(theme.sizes.default),
width: '100%',
},
actionsSpace: {
marginBottom: getDesignRelativeHeight(16),
},
errorSection: {
paddingBottom: 0,
paddingTop: 0,
marginBottom: 0,
},
descriptionWrapper: {
paddingTop: getDesignRelativeHeight(25),
paddingBottom: getDesignRelativeHeight(25),
},
}
}

export default withStyles(getStylesFromProps)(AgeCheckError)
58 changes: 42 additions & 16 deletions src/components/faceVerification/components/Instructions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { CustomButton, Section, Wrapper } from '../../common'
import { getDesignRelativeHeight, getDesignRelativeWidth, isLargeDevice } from '../../../lib/utils/sizes'
import normalize from '../../../lib/utils/normalizeText'
import { withStyles } from '../../../lib/styles'
import { isBrowser } from '../../../lib/utils/platform'
import { isBrowser, isMobile } from '../../../lib/utils/platform'

// assets
import illustration from '../../../assets/FRInstructions.png'
Expand All @@ -32,26 +32,41 @@ const Dot = () => (
</Text>
)

const Instructions = ({ styles, onDismiss = noop, ready }) => (
const Instructions = ({ styles, onDismiss = noop, ready, fvStarted = false }) => (
<Wrapper>
<Section style={styles.topContainer} grow>
<View style={styles.mainContent}>
<Image source={illustration} resizeMode="contain" style={styles.illustration} />
<View style={styles.descriptionContainer}>
<View style={styles.descriptionWrapper}>
<Text style={styles.text}>
<Dot />
{t`Hold Your Camera at Eye Level`}
</Text>
<Text style={styles.text}>
<Dot />
{t`Light Your Face Evenly`}
</Text>
<Text style={styles.text}>
<Dot />
{t`Avoid Smiling & Back Light`}
</Text>
</View>
{!fvStarted ? (
<View style={styles.descriptionWrapper}>
{!isMobile && (
<Text style={styles.text}>
<Dot />
{t`Face Directly In Front of the Camera`}
</Text>
)}
<Text style={styles.text}>
<Dot />
{t`Hold Your Camera at Eye Level`}
</Text>
<Text style={styles.text}>
<Dot />
{t`Light Your Face Evenly`}
</Text>
<Text style={styles.text}>
<Dot />
{t`Avoid Smiling & Back Light`}
</Text>
</View>
) : (
<View style={styles.warnDescriptionWrapper}>
<Text style={styles.warnText}>{t`Notice: do not continue if this is not your account`}</Text>
<Text
style={styles.warnText}
>{t`Doing this for someone else may result in lose of funds, and your account being blocked`}</Text>
</View>
)}
</View>
<CustomButton
loading={!ready}
Expand Down Expand Up @@ -123,6 +138,10 @@ const getStylesFromProps = ({ theme }) => ({
flexDirection: 'column',
alignItems: 'flex-start',
},
warnDescriptionWrapper: {
flexDirection: 'column',
alignItems: 'center',
},
descriptionWrapperB: {
backgroundColor: theme.colors.darkGray,
borderRadius: 8,
Expand All @@ -136,6 +155,13 @@ const getStylesFromProps = ({ theme }) => ({
fontSize: normalize(isLargeDevice ? 22 : 20),
lineHeight: isLargeDevice ? 36 : 34,
},
warnText: {
// textAlign: 'center',
fontSize: normalize(isLargeDevice ? 22 : 20),
lineHeight: isLargeDevice ? 36 : 34,
fontWeight: 'bold',
color: 'red',
},
textB: {
textAlign: 'left',
fontSize: 16,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ export default (options = null) => {
name = 'DuplicateFoundError'
} else if (/face.+n.t\s+match/.test(message)) {
name = 'NotMatchError'
} else if (/age check failed/.test(message)) {
name = 'AgeCheckError'
} else {
// the following code is needed to categorize exceptions
// then we could display specific error messages
Expand Down
4 changes: 3 additions & 1 deletion src/components/faceVerification/screens/ErrorScreen.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import NotMatchError from '../components/NotMatchError'
import GeneralError from '../components/GeneralError'
import UnrecoverableError from '../components/UnrecoverableError'
import SwitchToAnotherDevice from '../components/SwitchToAnotherDevice'
import AgeCheckError from '../components/AgeCheckError'

import useVerificationAttempts from '../hooks/useVerificationAttempts'

Expand Down Expand Up @@ -55,7 +56,7 @@ const ErrorScreen = ({ styles, screenProps, navigation }) => {
return getFirstWord(fullName)
}, [profile])

const onRetry = useCallback(() => screenProps.navigateTo('FaceVerificationIntro'), [screenProps])
const onRetry = useCallback(() => screenProps.navigateTo('FaceVerification'), [screenProps])

useEffectOnce(() => {
// determining error component to display
Expand Down Expand Up @@ -97,6 +98,7 @@ const ErrorScreen = ({ styles, screenProps, navigation }) => {
}

ErrorScreen.kindOfTheIssue = {
AgeCheckError,
NotMatchError,
UnrecoverableError,
DuplicateFoundError,
Expand Down
33 changes: 25 additions & 8 deletions src/components/faceVerification/screens/IntroScreen.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const IntroReVerification = ({ styles, firstName, ready, onVerify, onLearnMore }
</Wrapper>
)

const Intro = ({ styles, firstName, ready, onVerify, onLearnMore }) => (
const Intro = ({ styles, firstName, ready, onVerify, onLearnMore, onDeny }) => (
<Wrapper withMaxHeight={false}>
<Section style={styles.topContainer} grow>
<View style={styles.mainContent}>
Expand All @@ -127,7 +127,7 @@ const Intro = ({ styles, firstName, ready, onVerify, onLearnMore }) => (
lineHeight={25}
letterSpacing={0.18}
fontWeight="700"
>{t`To claim G$, you need to be a unique human and prove it with your camera.`}</Section.Text>
>{t`To continue, you need to be a unique human and prove it with your camera.`}</Section.Text>
<Section.Text fontSize={18} lineHeight={25} letterSpacing={0.18}>
{t`Your image is only used to ensure you’re you and prevent duplicate accounts.`}
</Section.Text>
Expand All @@ -144,9 +144,19 @@ const Intro = ({ styles, firstName, ready, onVerify, onLearnMore }) => (
<View style={styles.illustrationContainer} marginTop={0}>
<FashionShootSVG />
</View>
<CustomButton style={[styles.button]} onPress={onVerify} disabled={!ready}>
{t`OK, VERIFY ME`}
</CustomButton>
<View>
<CustomButton style={[styles.button]} onPress={onVerify} disabled={!ready}>
{t`I'M OVER 18, CONTINUE`}
</CustomButton>
<CustomButton
style={[styles.button]}
onPress={() => onDeny(`not 18 or didn't accept`)}
disabled={!ready}
mode="outlined"
>
{t`I Don't agree Or I'M NOT OVER 18`}
</CustomButton>
</View>
</View>
</Section>
</Wrapper>
Expand All @@ -160,7 +170,6 @@ const IntroScreen = ({ styles, screenProps, navigation }) => {
const goodWallet = useWallet()
const { account } = goodWallet ?? {}
const [expiryDate, , state] = useIdentityExpiryDate(externalAccount || account)

const isReverify = expiryDate?.lastAuthenticated?.isZero() === false

const { goToRoot, navigateTo, push } = screenProps
Expand All @@ -171,6 +180,13 @@ const IntroScreen = ({ styles, screenProps, navigation }) => {
[isFVFlow, firstName, fullName],
)

const onDeny = useCallback(
reason => {
return isFVFlow ? fvRedirect(false, reason) : goToRoot()
},
[isFVFlow],
)

const [disposing, checkDisposalState] = useDisposingState(
{
requestOnMounted: false,
Expand All @@ -182,14 +198,14 @@ const IntroScreen = ({ styles, screenProps, navigation }) => {
}

const dialogData = showQueueDialog(WalletDeletedPopupText, true, {
onDismiss: isFVFlow ? () => fvRedirect(false, 'Wait 24 hours') : goToRoot,
onDismiss: () => onDeny('Wait 24 hours'),
imageSource: Wait24HourSVG,
})

showDialog(dialogData)
},
},
[enrollmentIdentifier],
[enrollmentIdentifier, onDeny],

Check warning

Code scanning / CodeQL

Superfluous trailing arguments Warning

Superfluous argument passed to
anonymous function
.
)

const openPrivacy = useOnPress(() => openLink(Config.faceVerificationPrivacyUrl), [])
Expand Down Expand Up @@ -267,6 +283,7 @@ const IntroScreen = ({ styles, screenProps, navigation }) => {
firstName={userName}
onLearnMore={openPrivacy}
onVerify={handleVerifyClick}
onDeny={onDeny}
ready={false === disposing}
/>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useContext, useMemo } from 'react'
import React, { useCallback, useContext, useMemo, useState } from 'react'

import { identity } from 'lodash'

Expand Down Expand Up @@ -33,6 +33,7 @@ import AsyncStorage from '../../../lib/utils/asyncStorage'
const log = logger.child({ from: 'FaceVerification' })

const FaceVerification = ({ screenProps, navigation }) => {
const [fvStarted, setFVStarted] = useState()
const { attemptsCount, trackAttempt, resetAttempts } = useVerificationAttempts()
const goodWallet = useWallet()
const userStorage = useUserStorage()
Expand Down Expand Up @@ -192,6 +193,7 @@ const FaceVerification = ({ screenProps, navigation }) => {
}

fireEvent(FV_START)
setFVStarted(true)
startVerification()
}, [startVerification, enrollmentIdentifier])

Expand All @@ -205,7 +207,7 @@ const FaceVerification = ({ screenProps, navigation }) => {
// othwerise page will stuck on 'loading' "GOT IT" button
useFVLoginInfoCheck(navigation)

return <Instructions onDismiss={verifyFace} ready={initialized} />
return <Instructions onDismiss={verifyFace} ready={initialized} fvStarted={fvStarted} />
}

export default FaceVerification
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default ({ onDismiss }) => {
return (
<ExplanationDialog
title={t`Enable camera access
to claim G$'s`}
to get verified`}
image={illustration}
imageHeight={128}
buttons={[
Expand Down
1 change: 1 addition & 0 deletions src/lib/analytics/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const FV_GENERALERROR = 'FV_GENERALERROR'
export const FV_WRONGORIENTATION = 'FV_WRONGORIENTATION'
export const FV_DUPLICATEERROR = 'FV_DUPLICATEERROR'
export const FV_NOTMATCHERROR = 'FV_NOTMATCHERROR'
export const FV_AGECHECKERROR = 'FV_AGECHECKERROR'
export const FV_TRYAGAINLATER = 'FV_TRYAGAINLATER'
export const FV_CANTACCESSCAMERA = 'FV_CANTACCESSCAMERA'
export const FV_GIVEUP = 'FV_GIVEUP'
Expand Down

0 comments on commit 7ff12f5

Please sign in to comment.