Skip to content
Snippets Groups Projects
Verified Commit a3a7410d authored by Andreas Ellewsen's avatar Andreas Ellewsen
Browse files

Add identity verification to frontend

The profile page of a guest now shows a verification button if the guest
has a passport or national identificaiton number that has not been
verified. Clicking the button shows a dialog, with a confirmation button
which triggers a PATCH request to the backend and reloads the page when
it returns.

Resolves: GREG-101
parent 4ae7757f
No related branches found
No related tags found
1 merge request!170Greg 101 verify identity
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
} from '@mui/material'
import React from 'react'
type ConfirmDialogProps = {
title: string
open: boolean
setOpen: (value: boolean) => void
onConfirm: () => void
children: React.ReactNode
}
const ConfirmDialog = (props: ConfirmDialogProps) => {
const { title, children, open, setOpen, onConfirm } = props
return (
<Dialog
open={open}
onClose={() => setOpen(false)}
aria-labelledby="confirm-dialog"
>
<DialogTitle id="confirm-dialog">{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button
variant="contained"
onClick={() => setOpen(false)}
color="secondary"
>
No
</Button>
<Button
variant="contained"
onClick={() => {
setOpen(false)
onConfirm()
}}
color="error"
>
Yes
</Button>
</DialogActions>
</Dialog>
)
}
export default ConfirmDialog
import { Button, TableCell, TableRow } from '@mui/material'
import ConfirmDialog from 'components/confirmDialog'
import { Identity } from 'interfaces'
import { useState } from 'react'
import { submitJsonOpts } from 'utils'
import CheckIcon from '@mui/icons-material/Check'
import { useHistory } from 'react-router-dom'
interface IdentityLineProps {
text: string
identity: Identity | null
}
const IdentityLine = ({ text, identity }: IdentityLineProps) => {
// Make a line with a confirmation button if the identity has not been verified
const [confirmOpen, setConfirmOpen] = useState(false)
const history = useHistory()
const verifyIdentity = (id: string) => async () => {
await fetch(`/api/ui/v1/identity/${id}`, submitJsonOpts('PATCH', {}))
history.go(0)
}
if (identity == null) {
return <></>
}
return (
<TableRow>
<TableCell align="left">{text}</TableCell>
<TableCell align="left">{identity ? identity.value : ''}</TableCell>
<TableCell>
{!identity.verified_at ? (
<div>
<Button
aria-label="verify"
onClick={() => setConfirmOpen(true)}
disabled={!identity}
>
Verify
</Button>
<ConfirmDialog
title="TODO transaltion Confirm?"
open={confirmOpen}
setOpen={setConfirmOpen}
onConfirm={verifyIdentity(identity.id)}
>
TODO: translation Are you sure you want to verify this identity?
</ConfirmDialog>
</div>
) : (
<CheckIcon sx={{ fill: 'green' }} />
)}
</TableCell>
</TableRow>
)
}
export default IdentityLine
import { FetchedRole, Guest } from 'interfaces'
import { useEffect, useState } from 'react'
import { parseRole } from 'utils'
import { parseRole, parseIdentity } from 'utils'
const useGuest = (pid: string) => {
const [guestInfo, setGuest] = useState<Guest>({
......@@ -8,7 +8,8 @@ const useGuest = (pid: string) => {
first: '',
last: '',
email: '',
fnr: '',
fnr: null,
passport: null,
mobile: '',
active: false,
registered: false,
......@@ -28,7 +29,8 @@ const useGuest = (pid: string) => {
last: rjson.last,
email: rjson.email,
mobile: rjson.mobile,
fnr: rjson.fnr,
fnr: parseIdentity(rjson.fnr),
passport: parseIdentity(rjson.passport),
active: rjson.active,
registered: rjson.registered,
verified: rjson.verified,
......
import { useEffect, useState } from 'react'
import { FetchedGuest, Guest } from '../../interfaces'
import { parseRole } from '../../utils'
import { FetchedGuest, Guest } from 'interfaces'
import { parseIdentity, parseRole } from 'utils'
const useGuests = () => {
const [guests, setGuests] = useState<Guest[]>([])
......@@ -19,7 +19,8 @@ const useGuests = () => {
last: person.last,
email: person.email,
mobile: person.mobile,
fnr: person.fnr,
fnr: parseIdentity(person.fnr),
passport: parseIdentity(person.passport),
active: person.active,
roles: person.roles.map((role) => parseRole(role)),
registered: person.registered,
......
......@@ -4,20 +4,36 @@ export type Guest = {
last: string
email: string
mobile: string
fnr: string
fnr: Identity | null
passport: Identity | null
active: boolean
registered: boolean
verified: boolean
roles: Role[]
}
export type Identity = {
id: string
type: string
verified_at: Date | null
value: string
}
export type FetchedIdentity = {
id: string
type: string
verified_at: string | null
value: string
}
export interface FetchedGuest {
pid: string
first: string
last: string
email: string
mobile: string
fnr: string
fnr: FetchedIdentity | null
passport: FetchedIdentity | null
active: boolean
registered: boolean
verified: boolean
......
......@@ -23,7 +23,8 @@ import { Guest } from 'interfaces'
import SponsorInfoButtons from 'routes/components/sponsorInfoButtons'
import { useEffect, useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { isValidEmail, submitJsonOpts } from '../../../../utils'
import IdentityLine from 'components/identityLine'
import { isValidEmail, submitJsonOpts } from 'utils'
type GuestInfoParams = {
pid: string
......@@ -164,6 +165,7 @@ export default function GuestInfo({
<TableRow>
<TableCell align="left">{t('guestInfo.contactInfo')}</TableCell>
<TableCell />
<TableCell />
</TableRow>
</TableHead>
<TableBody>
......@@ -172,6 +174,7 @@ export default function GuestInfo({
<TableCell align="left">
{`${guest.first} ${guest.last}`}
</TableCell>
<TableCell />
</TableRow>
<TableRow>
<TableCell align="left">{t('input.email')}</TableCell>
......@@ -224,11 +227,16 @@ export default function GuestInfo({
onClose={handleDialogClose}
/>
</TableCell>
<TableCell />
</TableRow>
<TableRow>
<TableCell align="left">{t('input.nationalIdNumber')}</TableCell>
<TableCell align="left">{guest.fnr}</TableCell>
</TableRow>
<IdentityLine
text={t('input.nationalIdNumber')}
identity={guest.fnr}
/>
<IdentityLine
text={t('input.passportNumber')}
identity={guest.passport}
/>
<TableRow>
<TableCell align="left">{t('input.mobilePhone')}</TableCell>
<TableCell align="left">{guest.mobile}</TableCell>
......
import validator from '@navikt/fnrvalidator'
import { parseISO } from 'date-fns'
import i18n from 'i18next'
import { FetchedRole, Role } from 'interfaces'
import { FetchedIdentity, FetchedRole, Identity, Role } from 'interfaces'
import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js'
const validEmailRegex =
......@@ -122,3 +122,17 @@ export function parseRole(role: FetchedRole): Role {
max_days: role.max_days,
}
}
export function parseIdentity(
identity: FetchedIdentity | null
): Identity | null {
if (identity == null) {
return null
}
return {
id: identity.id,
type: identity.type,
value: identity.value,
verified_at: identity.verified_at ? parseISO(identity.verified_at) : null,
}
}
......@@ -33,7 +33,20 @@ class PersonView(APIView):
"last": person.last_name,
"email": person.private_email and person.private_email.value,
"mobile": person.private_mobile and person.private_mobile.value,
"fnr": person.fnr and "".join((person.fnr.value[:-5], "*****")),
"fnr": person.fnr
and {
"id": person.fnr.id,
"value": person.fnr.value,
"type": person.fnr.type,
"verified_at": person.fnr.verified_at,
},
"passport": person.passport
and {
"id": person.passport.id,
"value": person.passport.value,
"type": person.passport.type,
"verified_at": person.passport.verified_at,
},
"active": person.is_registered and person.is_verified,
"registered": person.is_registered,
"verified": person.is_verified,
......
......@@ -104,7 +104,20 @@ class GuestInfoView(APIView):
"last": person.last_name,
"email": person.private_email and person.private_email.value,
"mobile": person.private_mobile and person.private_mobile.value,
"fnr": person.fnr and "".join((person.fnr.value[:-5], "*****")),
"fnr": person.fnr
and {
"id": person.fnr.id,
"value": "".join((person.fnr.value[:-5], "*****")),
"type": person.fnr.type,
"verified_at": person.fnr.verified_at,
},
"passport": person.passport
and {
"id": person.passport.id,
"value": person.passport.value,
"type": person.passport.type,
"verified_at": person.passport.verified_at,
},
"active": person.is_registered and person.is_verified,
"registered": person.is_registered,
"verified": person.is_verified,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment