This is a form where we scan the passport for each passenger and the rest of the fields get filled automatically
import PropTypes from 'prop-types'
import React, { useEffect, useRef, useState } from 'react'
import Stack from '@kiwicom/orbit-components/lib/Stack/index'
import InputField from '@kiwicom/orbit-components/lib/InputField/index'
import useTranslate from '@kiwicom/orbit-components/lib/hooks/useTranslate'
import { getIn, useFormikContext } from 'formik'
import InputFile from '@kiwicom/orbit-components/lib/InputFile'
import Select from '@kiwicom/orbit-components/lib/Select'
import { GDS_CABIN_CLASSES } from '../../../../../constants/support'
import { useSelector } from 'react-redux'
import { gdsAvailableSeatsDatabase } from '../../../../../shared/utils'
import Translate from '@kiwicom/orbit-components/lib/Translate'
import { Overlay, ViewerWrapper, Body, ImageOuter } from '../../../../../routes/SamplePage/styles'
import moment from 'moment'
import { scanImage, scanPdf } from '../../../../../util/mrzReader'
import {
Alert,
Button,
Modal,
ModalFooter,
ModalHeader,
ModalSection,
} from '@kiwicom/orbit-components'
const parseDate = (yymmddDate) => {
if (!yymmddDate) {
return ''
}
return moment(yymmddDate, 'YYMMDD', 'fr').format('DD MMM YYYY')
}
const preScanImageFirstRender = (imgObject) => {
if (imgObject) {
const { clientWidth, clientHeight } = imgObject
return {
orentation: clientWidth > clientHeight ? 'landscape' : 'portrait',
renderWidth: clientWidth,
renderHeight: clientHeight,
width: 'not calculated',
height: 'not calculated',
}
}
return {}
}
const IFRAME_PROCESS = true
const GdsFormFields = ({ passengerIndex, form, passengersType, onCabinChange }) => {
const shoppingCart = useSelector((state) => state.gdsBookings.cart)
const translate = useTranslate()
const cabinClassOptions = GDS_CABIN_CLASSES.map((cabin) => ({
label: translate(cabin),
value: cabin,
disabled: gdsAvailableSeatsDatabase(shoppingCart?.flight, shoppingCart?.trip_type, cabin) === 0,
}))
const { setFieldValue, values } = useFormikContext()
useEffect(() => {
// At this point firstName it's updated with the lastest user input
}, [values[`passengers.${passengersType}.${passengerIndex}.cabin_class`]])
const [rotageAngle, setRotateAngle] = useState(0)
const [showPreview, setShowPreview] = useState(false)
const [fileData, setFileData] = useState({})
const [toggleWith, setToggleWidth] = useState({ width: 0, height: 0 })
const [ocrData, setOcrData] = useState({})
const [passportModal, setPassportModal] = useState(false)
const inputRef = useRef(null)
const imgRef = useRef(null)
const iframeRef = useRef(null)
const handleRotate = (evt) => {
evt.preventDefault()
setRotateAngle(rotageAngle + 90)
const { width, height } = toggleWith
setToggleWidth({ width: height, height: width })
}
const [imgSrc, setImgSrc] = useState(null)
const [imgUrl, setImgUrl] = useState(null)
const handleRetake = (evt) => {
evt.preventDefault()
if (fileData.url) {
URL.revokeObjectURL(fileData.url)
setFileData({})
inputRef.current.value = ''
inputRef.current.click()
}
setOcrData({})
return true
}
const updateInfo = (info) => {
const { scanStatus, data } = info || {}
const passengerDataKey = `passengers.${passengersType}.${passengerIndex}`
if (scanStatus === 'success') {
if (data && data.parsed && data.parsed.fields) {
const passportData = { ...data.parsed.fields }
passportData.birthDate = parseDate(passportData.birthDate)
passportData.expirationDate = parseDate(passportData.expirationDate)
setOcrData({ scanStatus, passportData })
setPassportModal(true)
form.setFieldValue(`${passengerDataKey}.first_name`, passportData.firstName)
form.setFieldValue(`${passengerDataKey}.last_name`, passportData.lastName)
form.setFieldValue(`${passengerDataKey}.passport_nbr`, passportData.documentNumber)
const formattedBirthDate = moment(passportData.birthDate, 'DD-MMM-YYYY').format(
'YYYY-MM-DD',
)
form.setFieldValue(`${passengerDataKey}.birth_date`, formattedBirthDate)
const formattedPassportExp = moment(passportData.expirationDate, 'DD-MMM-YYYY').format(
'YYYY-MM-DD',
)
form.setFieldValue(`${passengerDataKey}.passport_expire_at`, formattedPassportExp)
console.log('Ocr data ' + JSON.stringify(ocrData, null, 't'))
} else {
setOcrData({ scanStatus, error: 'No OCR Information' })
}
}
if (scanStatus === 'progress') {
setOcrData({ scanStatus, status: data })
}
if (scanStatus === 'error') {
setOcrData({ scanStatus, error: 'Error in file scan' })
}
if (scanStatus === 'default') {
setOcrData({ scanStatus, error: 'Unknown error' })
}
if (scanStatus !== 'progress') {
setShowPreview(false)
}
}
const processOCR = () => {
let postData = {}
if (fileData.type && fileData.type.indexOf('pdf') > 0) {
if (!IFRAME_PROCESS) {
scanPdf(fileData.url, updateInfo)
}
postData = { action: 'SCAN_PDF', payload: { url: fileData.url } }
} else {
if (!IFRAME_PROCESS) {
scanImage(fileData.url, updateInfo)
}
postData = { action: 'SCAN_IMAGE', payload: { url: fileData.url } }
}
if (!IFRAME_PROCESS) {
return false
}
const iframeContentWindow = iframeRef.current.contentWindow
iframeContentWindow.postMessage(postData)
}
useEffect(() => {
if (fileData.url && fileData.url.length > 0) {
setShowPreview(true)
setTimeout(() => {
const { renderWidth: width, renderHeight: height } = preScanImageFirstRender(imgRef.current)
setToggleWidth({ width, height })
}, 500)
} else {
setShowPreview(false)
}
}, [fileData])
const receiveMessage = (evt) => {
if (evt.data) {
const { action, payload } = evt.data || {}
if (action === 'SENDING_OCR_DATA') {
updateInfo(payload.ocrData)
}
}
}
useEffect(() => {
setTimeout(() => {
window.addEventListener('message', receiveMessage, false)
}, 200)
return () => {
window.removeEventListener('message', receiveMessage, false)
}
}, [])
const handleFileInputChange = (event) => {
const file = event.target.files[0]
const fileUrl = URL.createObjectURL(file)
setFileData({
url: fileUrl,
type: file.type,
})
setImgUrl(fileUrl)
form.setFieldValue(`passengers.${passengersType}.${passengerIndex}.passport_scan`, file)
}
return (
<>
{passengersType === 'adults' && passengerIndex === 0 && (
<>
<Stack
spaceAfter="large"
key={passengerIndex}
direction="row"
spacing="medium"
align="center"
>
<InputField
placeholder={translate('package.passenger.email')}
name={`passengers.${passengersType}.${passengerIndex}.email`}
value={form.values.passengers[passengersType][passengerIndex].email}
type="email"
inputMode="email"
onChange={form.handleChange}
onBlur={form.handleBlur}
error={
getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.email`) &&
getIn(form.touched, `passengers.${passengersType}.${passengerIndex}.email`)
? getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.email`)
: null
}
/>
<InputField
placeholder={translate('package.passenger.phone')}
name={`passengers.${passengersType}.${passengerIndex}.phone`}
value={form.values.passengers[passengersType][passengerIndex].phone}
type="text"
inputMode="tel"
onChange={form.handleChange}
onBlur={form.handleBlur}
error={
getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.phone`) &&
getIn(form.touched, `passengers.${passengersType}.${passengerIndex}.phone`)
? getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.phone`)
: null
}
/>
</Stack>
<hr className="border" />
</>
)}
<Stack spaceAfter="large" direction="row" spacing="medium" align="center">
<Select
options={cabinClassOptions}
placeholder={translate('gds.passenger.cabin_class')}
name={`passengers.${passengersType}.${passengerIndex}.cabin_class`}
value={form.values.passengers[passengersType][passengerIndex].cabin_class}
inputMode="text"
onChange={(event) => {
form.setFieldValue(
`passengers.${passengersType}.${passengerIndex}.cabin_class`,
event.target.value,
)
onCabinChange(event.target.value, passengersType, passengerIndex)
}}
onBlur={form.handleBlur}
error={
getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.cabin_class`) &&
getIn(form.touched, `passengers.${passengersType}.${passengerIndex}.cabin_class`)
? getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.cabin_class`)
: null
}
/>
</Stack>
<Stack flex direction="row" grow align="start" spaceAfter="medium" key={passengerIndex}>
<InputFile
placeholder={translate('package.pic.format')}
fileName={
form.values.passengers[passengersType][passengerIndex].last_name +
' ' +
form.values.passengers[passengersType][passengerIndex].passport_nbr ||
translate('package.pic.format')
}
buttonLabel={translate('package.passenger.passport_scan')}
allowedFileTypes={['.png', '.jpg', '.jpeg']}
name={`passengers.${passengersType}.${passengerIndex}.passport_scan`}
value={form.values.passengers[passengersType][passengerIndex].passport_scan}
onChange={handleFileInputChange}
ref={inputRef}
onRemoveFile={() =>
form.setFieldValue(`passengers.${passengersType}.${passengerIndex}.passport_scan`, null)
}
onBlur={form.handleBlur}
/>
<div>
{IFRAME_PROCESS ? (
<iframe
width="0"
height="0"
ref={iframeRef}
src="ocr-frame/ocr.html"
title="ocr-processor"
/>
) : null}
</div>
{ocrData.error ? <p style={{ color: 'red' }}> {ocrData.error}</p> : null}
<>{imgSrc && <img src={imgSrc} alt="" />}</>
{ocrData && ocrData.scanStatus === 'success' && ocrData.passportData && passportModal ? (
<>
<Modal onClose={() => setPassportModal(false)}>
<div
style={{
textAlign: 'left',
display: 'flex',
flexDirection: 'column',
}}
>
<ModalHeader
title={
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Translate tKey="profile.personal_info" />
</div>
}
>
<div style={{ display: 'flex', justifyContent: 'center' }}>
{fileData.url && fileData.type && fileData.type.indexOf('pdf') === -1 ? (
<img src={fileData.url} width="600" alt="preview passport" />
) : null}
</div>
</ModalHeader>
<ModalSection>
<Alert spaceAfter="medium">
<Translate tKey="confirm.passport.alert" />
</Alert>
{Object.keys(ocrData.passportData).map((itemKey, index) => (
<>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
{itemKey === 'issuingState' && (
<div key={index}>
<div style={{ display: 'flex' }}>
<span className="passport-scan-info">
<Translate tKey="passport.issuing_state" />
</span>
<span className="passport-scan-info">
{ocrData.passportData[itemKey]}
</span>
</div>
</div>
)}
{itemKey === 'lastName' && (
<div key={index}>
<div style={{ display: 'flex' }}>
<span className="passport-scan-info">
<Translate tKey="user.last_name" />
</span>
<span className="passport-scan-info">
{ocrData.passportData[itemKey]}
</span>
</div>
</div>
)}
{itemKey === 'firstName' && (
<div key={index}>
<div style={{ display: 'flex' }}>
<span className="passport-scan-info">
<Translate tKey="user.first_name" />
</span>
<span className="passport-scan-info">
{ocrData.passportData[itemKey]}
</span>
</div>
</div>
)}
{itemKey === 'documentNumber' && (
<div key={index}>
<div style={{ display: 'flex' }}>
<span className="passport-scan-info">
<Translate tKey="passport.document_number" />
</span>
<span className="passport-scan-info">
{ocrData.passportData[itemKey]}
</span>
</div>
</div>
)}
{itemKey === 'nationality' && (
<div key={index}>
<div style={{ display: 'flex' }}>
<span className="passport-scan-info">
<Translate tKey="form.nationality" />
</span>
<span className="passport-scan-info">
{ocrData.passportData[itemKey]}
</span>
</div>
</div>
)}
{itemKey === 'birthDate' && (
<div key={index}>
<div style={{ display: 'flex' }}>
<span className="passport-scan-info">
<Translate tKey="form.birth" />
</span>
<span className="passport-scan-info">
{ocrData.passportData[itemKey]}
</span>
</div>
</div>
)}
{itemKey === 'sex' && (
<div key={index}>
<div style={{ display: 'flex' }}>
<span className="passport-scan-info">
<Translate tKey="form.gender" />
</span>
<span className="passport-scan-info">
{ocrData.passportData[itemKey]}
</span>
</div>
</div>
)}
{itemKey === 'expirationDate' && (
<div key={index}>
<div style={{ display: 'flex' }}>
<span className="passport-scan-info">
<Translate tKey="form.expiry" />
</span>
<span className="passport-scan-info">
{ocrData.passportData[itemKey]}
</span>
</div>
</div>
)}
</div>
</>
))}
</ModalSection>
<ModalFooter>
<Button onClick={() => setPassportModal(false)}>
<Translate tKey="app.confirm" />
</Button>
</ModalFooter>
</div>
</Modal>
</>
) : null}
{showPreview && fileData.url ? (
<Overlay>
<ViewerWrapper>
<header style={{ display: 'flex', justifyContent: 'space-around' }}>
<Button onClick={handleRotate}>
<Translate tKey="app.rotate" />
</Button>
<Button
onClick={() => {
setShowPreview(false)
}}
>
<Translate tKey="app.close" />
</Button>
</header>
<Body loading={ocrData.scanStatus === 'progress' ? 'loaging' : ''}>
<ImageOuter angle={rotageAngle} toggleWith={toggleWith}>
<div className="img-inner">
{fileData.type && fileData.type.indexOf('pdf') === -1 ? (
<img id="previewImage" src={fileData.url} alt="user uploads" ref={imgRef} />
) : null}
</div>
</ImageOuter>
</Body>
<footer style={{ display: 'flex', justifyContent: 'space-around' }}>
<Button onClick={handleRetake}>
<Translate tKey="app.retake" />
</Button>
<Button primary onClick={processOCR}>
<Translate tKey="app.use" />
</Button>
</footer>
</ViewerWrapper>
</Overlay>
) : null}
</Stack>
<Stack spaceAfter="large" direction="row" spacing="medium" align="center">
<InputField
placeholder={translate('package.passenger.first_name')}
name={`passengers.${passengersType}.${passengerIndex}.first_name`}
type="text"
value={form.values.passengers[passengersType][passengerIndex].first_name}
inputMode="text"
onChange={(event) => {
const uppercaseValue = event.target.value.toUpperCase()
form.setFieldValue(
`passengers.${passengersType}.${passengerIndex}.first_name`,
uppercaseValue,
)
}}
onBlur={form.handleBlur}
error={
getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.first_name`) &&
getIn(form.touched, `passengers.${passengersType}.${passengerIndex}.first_name`)
? getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.first_name`)
: null
}
/>
<InputField
placeholder={translate('package.passenger.last_name')}
name={`passengers.${passengersType}.${passengerIndex}.last_name`}
value={form.values.passengers[passengersType][passengerIndex].last_name}
type="text"
inputMode="text"
onChange={(event) => {
const uppercaseValue = event.target.value.toUpperCase()
form.setFieldValue(
`passengers.${passengersType}.${passengerIndex}.last_name`,
uppercaseValue,
)
}}
onBlur={form.handleBlur}
error={
getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.last_name`) &&
getIn(form.touched, `passengers.${passengersType}.${passengerIndex}.last_name`)
? getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.last_name`)
: null
}
/>
</Stack>
<Stack spaceAfter="large" direction="row" spacing="medium" align="center">
<InputField
label={translate('package.passenger.birth_date')}
inlineLabel
placeholder={translate('package.passenger.phone')}
name={`passengers.${passengersType}.${passengerIndex}.birth_date`}
value={form.values.passengers[passengersType][passengerIndex].birth_date}
onChange={form.handleChange}
onBlur={form.handleBlur}
type="date"
error={
getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.birth_date`) &&
getIn(form.touched, `passengers.${passengersType}.${passengerIndex}.birth_date`)
? getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.birth_date`)
: null
}
/>
<InputField
placeholder={translate('package.passenger.passport_number')}
name={`passengers.${passengersType}.${passengerIndex}.passport_nbr`}
value={form.values.passengers[passengersType][passengerIndex].passport_nbr}
type="passportid"
inputMode="numeric"
onChange={form.handleChange}
onBlur={form.handleBlur}
error={
getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.passport_nbr`) &&
getIn(form.touched, `passengers.${passengersType}.${passengerIndex}.passport_nbr`)
? getIn(form.errors, `passengers.${passengersType}.${passengerIndex}.passport_nbr`)
: null
}
/>
</Stack>
<Stack spaceAfter="large" direction="row" spacing="medium" align="center">
<InputField
label={translate('package.passenger.passport_expire_at')}
inlineLabel
placeholder={translate('package.passenger.passport_expire_at')}
name={`passengers.${passengersType}.${passengerIndex}.passport_expire_at`}
value={form.values.passengers[passengersType][passengerIndex].passport_expire_at}
onChange={form.handleChange}
onBlur={form.handleBlur}
type="date"
error={
getIn(
form.errors,
`passengers.${passengersType}.${passengerIndex}.passport_expire_at`,
) &&
getIn(form.touched, `passengers.${passengersType}.${passengerIndex}.passport_expire_at`)
? getIn(
form.errors,
`passengers.${passengersType}.${passengerIndex}.passport_expire_at`,
)
: null
}
/>
</Stack>
</>
)
}
export default GdsFormFields
GdsFormFields.propTypes = {
form: PropTypes.any.isRequired,
passengerIndex: PropTypes.number.isRequired,
passengersType: PropTypes.string.isRequired,
}
The problem when I scan the passport in the first form it also fill the second form’s details, while I only want it to fill the first one (where I uploaded the passport pic). How to make it so that every form fills the info of the passport pic I passed to it ?