Skip to content

Commit

Permalink
Merge pull request #316 from onfido/feature/privacy-statement-cx-1711
Browse files Browse the repository at this point in the history
Feature/privacy statement cx 1711
  • Loading branch information
seewah committed Mar 21, 2018
2 parents b3b9abd + 4e713a2 commit 4479088
Show file tree
Hide file tree
Showing 21 changed files with 264 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This project adheres to the Node [default version scheme](https://docs.npmjs.com

### Added
- Public: Added `onModalRequestClose` options, which is a callback that fires when the user attempts to close the modal.
- UI: Added legal documentation view before first document capture to assist client with user privacy notifications and compliance.

### Fixed
- Public: Fixed `complete` step to allow string customization at initialization time.
Expand Down
2 changes: 1 addition & 1 deletion dist/onfido.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/style.css

Large diffs are not rendered by default.

25 changes: 16 additions & 9 deletions src/components/Capture/capture.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import randomId from '../utils/randomString'
import { Uploader } from '../Uploader'
import Camera from '../Camera'
import Title from '../Title'
import PrivacyStatement from '../PrivacyStatement'
import { functionalSwitch, isDesktop, checkIfHasWebcam } from '../utils'
import { canvasToBase64Images } from '../utils/canvas.js'
import { base64toBlob, fileToBase64, isOfFileType, fileToLossyBase64Image } from '../utils/file.js'
Expand All @@ -22,7 +23,7 @@ class Capture extends Component {
this.state = {
uploadFallback: false,
error: null,
hasWebcam: hasWebcamStartupValue,
hasWebcam: hasWebcamStartupValue
}
}

Expand All @@ -43,6 +44,10 @@ class Capture extends Component {
if (allInvalid) this.onFileGeneralError()
}

acceptTerms = () => {
this.props.actions.acceptTerms()
}

checkWebcamSupport = () => {
checkIfHasWebcam( hasWebcam => this.setState({hasWebcam}) )
}
Expand Down Expand Up @@ -196,16 +201,18 @@ class Capture extends Component {
this.setState({error: null})
}

render ({useWebcam, ...other}) {
render ({useWebcam, back, i18n, termsAccepted, ...other}) {
const useCapture = (!this.state.uploadFallback && useWebcam && isDesktop && this.state.hasWebcam)
return (
<CaptureMode {...{useCapture,
onScreenshot: this.onScreenshot,
onUploadFallback: this.onUploadFallback,
onImageSelected: this.onImageFileSelected,
onWebcamError: this.onWebcamError,
error: this.state.error,
...other}}/>
!termsAccepted ?
<PrivacyStatement {...{i18n, back, acceptTerms: this.acceptTerms, ...other}}/> :
<CaptureMode {...{useCapture, i18n,
onScreenshot: this.onScreenshot,
onUploadFallback: this.onUploadFallback,
onImageSelected: this.onImageFileSelected,
onWebcamError: this.onWebcamError,
error: this.state.error,
...other}}/>
)
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/components/Modal/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import style from './style.css'
import ReactModal from 'react-modal'
import { h, Component } from 'preact'
import theme from '../Theme/style.css'
import { getCSSMilisecsValue, wrapWithClass } from '../utils'

const MODAL_ANIMATION_DURATION = getCSSMilisecsValue(style.modal_animation_duration)

const WrapperContent = ({children}) =>
wrapWithClass(style.content, wrapWithClass(theme.step, children))
wrapWithClass(style.content, children)

const Wrapper = ({children}) =>
wrapWithClass(style.inner, <WrapperContent>{children}</WrapperContent>)
Expand Down
7 changes: 3 additions & 4 deletions src/components/NavigationBar/style.css
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
@import (less) "../Theme/constants.css";

.navigation {
float: left;
position: absolute;
top: 16px;
margin-left: 16px;
margin: 0 16px;
margin-top: -16px;
text-align: left;
@media (--small-viewport) {
margin-left: 12px;
margin-top: -32px;
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/components/PrivacyStatement/assets/tick-grey.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions src/components/PrivacyStatement/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { h, Component} from 'preact'

import theme from '../Theme/style.css'
import style from './style.css'
import Title from '../Title'
import {preventDefaultOnClick} from '../utils'
import {sendScreen} from '../../Tracker'
import {parseI18nWithXmlTags} from '../../locales'

const externalUrls = {
terms: process.env.ONFIDO_TERMS_URL,
privacy: process.env.ONFIDO_PRIVACY_URL
}

class PrivacyStatement extends Component {
componentDidMount() {
sendScreen(['privacy'])
}

render({i18n, back, acceptTerms}) {
const title = i18n.t('privacy.title')
return (
<div className={style.privacy}>
<Title {...{title}} />
<div className={`${theme.thickWrapper} ${style.content}`}>
<ul className={style.list}>
<li className={style.item}>{i18n.t('privacy.item_1')}</li>
<li className={style.item}>{i18n.t('privacy.item_2')}</li>
<li className={style.item}>{i18n.t('privacy.item_3')}</li>
</ul>

<div>
<div className={style.smallPrint}>
{ parseI18nWithXmlTags(i18n, 'privacy.small_print', tagElement => (
<a href={externalUrls[tagElement.type]} target='_blank'>{tagElement.text}</a>
))}
</div>
<div className={style.actions}>
<button onClick={preventDefaultOnClick(back)}
className={`${theme.btn} ${style.decline}`}>
{i18n.t('privacy.decline')}
</button>
<button href='#' className={`${theme.btn} ${theme["btn-primary"]} ${style.primary}`}
onClick={preventDefaultOnClick(acceptTerms)}>
{i18n.t('privacy.continue')}
</button>
</div>
</div>
</div>
</div>
)
}
}

export default PrivacyStatement
94 changes: 94 additions & 0 deletions src/components/PrivacyStatement/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
@import (less) "../Theme/constants.css";

.privacy {
/*
The height is calculated by subtracting 112px (parent's margin top 48px + parent's margin bottom (64px)) from the height of its parent.
This will allow consistent spacing between its children when using flexbox
and for the actions to always sit above the "powered-by-onfido" logo.
*/
height: calc(~"100% - 112px");
width: 100%;
position: absolute;
}

.content {
height: calc(~"100% - 112px");
display: flex;
justify-content: space-around;
flex-direction: column;
color: #0F2536;
}

.actions {
display: flex;
justify-content: space-between;
flex-direction: row;
margin-bottom: 24px;
@media (--small-viewport) {
margin-bottom: 0;
}
}

.list {
font-size: 16px;
line-height: 24px;
text-align: left;
padding-left: 64px;
position: relative;
margin-bottom: 0;
@media (--small-viewport) {
padding-left: 32px;
}
}

.item {
list-style: none;
margin-bottom: 16px;
}

.item:before{
content: '';
display: inline-block;
height: 16px;
width: 32px;
background-image: url('assets/tick-grey.svg');
background-repeat: no-repeat;
position: relative;
margin-left: -32px;
@media (--small-viewport) {
margin-left: -32px;
}
}

.smallPrint {
text-align: left;
font-size: 11px;
font-weight: 400;
line-height: 18px;
margin-bottom: 24px;
}

.smallPrint a {
color: #0F2536;
margin-top: 10px;
margin-bottom: 10px;
}

.decline {
width: 136px;
border: 1px solid #8A9293;
border-radius: 28px;
background-color: transparent;
color: #585E5F;
line-height: 16px;
@media (--small-viewport) {
width: 112px;
padding: 0px 16px;
}
}

.primary {
@media (--small-viewport) {
width: 152px;
}
}
3 changes: 2 additions & 1 deletion src/components/Router/StepsRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { h, Component } from 'preact'
import {sendScreen} from '../../Tracker'
import {wrapArray} from '../utils/array'
import NavigationBar from '../NavigationBar'
import theme from '../Theme/style.css'

class StepsRouter extends Component {
constructor(props) {
Expand All @@ -21,7 +22,7 @@ class StepsRouter extends Component {
const componentBlob = this.currentComponent()
const CurrentComponent = componentBlob.component
return (
<div>
<div className={theme.step}>
{!this.props.disableBackNavigation && <NavigationBar back={this.props.back} i18n={this.props.i18n} />}
<CurrentComponent
{...{...componentBlob.step.options, ...globalUserOptions, ...otherProps}}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ class CrossDeviceMobileRouter extends Component {
sendError(`Token has expired: ${token}`)
return this.setError()
}
this.setState({token, steps, step, loading: false})
this.setState({i18n: initializeI18n(language)})
this.setState({token, steps, step, loading: false, i18n: initializeI18n(language)})
actions.setDocumentType(documentType)
actions.acceptTerms()
}

setError = () =>
Expand Down
1 change: 1 addition & 0 deletions src/core/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const SET_SOCKET = 'SET_SOCKET'
export const SET_MOBILE_NUMBER = 'SET_MOBILE_NUMBER'
export const SET_CLIENT_SUCCESS = 'SET_CLIENT_SUCCESS'
export const MOBILE_CONNECTED = 'MOBILE_CONNECTED'
export const ACCEPT_TERMS = 'ACCEPT_TERMS'

export const CAPTURE_CREATE = 'CAPTURE_CREATE'
export const CAPTURE_VALIDATE = 'CAPTURE_VALIDATE'
Expand Down
6 changes: 6 additions & 0 deletions src/core/store/actions/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ export function mobileConnected(payload) {
payload
}
}

export function acceptTerms() {
return {
type: constants.ACCEPT_TERMS
}
}
3 changes: 3 additions & 0 deletions src/core/store/reducers/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const initialState = {
sms: {number: null, valid: false},
clientSuccess: false,
i18n: null,
termsAccepted: false
}


Expand All @@ -24,6 +25,8 @@ export default function globals(state = initialState, action) {
return {...state, clientSuccess: action.payload}
case constants.MOBILE_CONNECTED:
return {...state, mobileConnected: action.payload}
case constants.ACCEPT_TERMS:
return {...state, termsAccepted: true}
default:
return state
}
Expand Down
9 changes: 9 additions & 0 deletions src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ export const en = {
},
tips: 'Tips',
},
privacy: {
title: 'You\'ll have to upload a photo of your identity document',
item_1: 'All your document details must be visible',
item_2: 'Your document must be in colour',
item_3: 'Avoid light reflections',
small_print: 'By continuing, you agree to the <terms>Onfido Terms of Use</terms> and understand that your information, including your facial identifiers, will be processed in accordance with the <privacy>Onfido Privacy Policy</privacy>',
decline: 'Decline',
continue: 'Continue'
},
errors: {
invalid_capture: {message:'No document detected', instruction: 'Make sure all the document is in the photo'},
invalid_type: {message: 'File not uploading', instruction: 'Try using another file type'},
Expand Down
9 changes: 9 additions & 0 deletions src/locales/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ export const es = {
},
tips: 'Tips',
},
privacy: {
title: 'Tendrá que subir una foto de su documento de identidad',
item_1: 'Todos los datos de su documento tienen que ser visibles',
item_2: 'El documento debe ser en color',
item_3: 'Evite los reflejos',
small_print: 'Al continuar, usted está de acuerdo con los <terms>Términos de Uso de Onfido</terms> y comprende que su información, incluidos sus identificadores faciales, se procesará de acuerdo con la <privacy>Política de privacidad de Onfido</privacy>',
decline: 'Declinar',
continue: 'Continuar'
},
errors: {
invalid_capture: {message:'Documento no detectado', instruction: 'Asegúrese de que todo el documento esté en la foto'},
invalid_type: {message: 'Archivo no cargado', instruction: 'Intenta usar otro tipo de archivo'},
Expand Down
10 changes: 10 additions & 0 deletions src/locales/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,13 @@ export const initializeI18n = (language) => {
if (!language) return polyglot
return overrideTranslations(language, polyglot) || polyglot
}

export const parseI18nWithXmlTags = (i18n, translationKey, handleTag) => {
const str = i18n.t(translationKey)
const parser = new DOMParser();
const stringToXml = parser.parseFromString(`<l>${str}</l>`, 'application/xml')
const xmlToNodesArray = Array.from(stringToXml.firstChild.childNodes)
return xmlToNodesArray.map(
node => node.nodeType === document.TEXT_NODE ? node.textContent : handleTag({type: node.tagName, text: node.textContent})
)
}
8 changes: 8 additions & 0 deletions test/features/page_objects/sdk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ def confirm
@driver.find_element(:css, '.onfido-sdk-ui-Confirm-actions > .onfido-sdk-ui-Theme-btn-primary')
end

def confirm_privacy_terms
@driver.find_element(:css, '.onfido-sdk-ui-PrivacyStatement-primary')
end

def decline_privacy_terms
@driver.find_element(:css, '.onfido-sdk-ui-PrivacyStatement-decline')
end

def page_title
@driver.find_element(:css, '.onfido-sdk-ui-Title-title')
end
Expand Down

0 comments on commit 4479088

Please sign in to comment.