Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ jobs:
name: Run Lints
command: |
yarn run lint
yarn run typecheck
bundle exec slim-lint app/views
build-latest-container:
working_directory: ~/identity-idp
Expand Down
14 changes: 3 additions & 11 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
"implicit-arrow-linebreak": "off",
"object-curly-newline": "off",
"operator-linebreak": "off",
"react/prop-types": ["error", { "skipUndeclared": true }],
"react/jsx-one-expression-per-line": "off"
"react/jsx-one-expression-per-line": "off",
"react/prop-types": "off"
},
"parserOptions": {
"ecmaVersion": 6,
Expand All @@ -50,13 +50,5 @@
"app/phone-internationalization",
"app/i18n-dropdown"
]
},
"overrides": [
{
"files": ["spec/javascripts/**/*"],
"rules": {
"react/prop-types": "off"
}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';

/**
* @typedef AcuantCameraUI
*
* @prop {(AcuantSuccessCallback,AcuantFailureCallback)=>void} start Start capture.
* @prop {()=>void} end End capture.
*/

/**
* @typedef AcuantGlobals
*
* @prop {AcuantCameraUI} AcuantCameraUI Acuant camera UI API.
*/

/**
* @typedef {typeof window & AcuantGlobals} AcuantGlobal
*/

/**
* @typedef AcuantImage
Expand All @@ -21,22 +37,36 @@ import PropTypes from 'prop-types';
* @see https://github.com/Acuant/JavascriptWebSDKV11/tree/11.3.3/SimpleHTMLApp#acuantcamera
*/

/**
* @typedef {(response:AcuantSuccessResponse)=>void} AcuantSuccessCallback
*/

/**
* @typedef {(error:Error)=>void} AcuantFailureCallback
*/

/**
* @typedef AcuantCaptureCanvasProps
*
* @prop {(response:AcuantSuccessResponse)=>void} onImageCaptureSuccess Success callback.
* @prop {(error:Error)=>void} onImageCaptureFailure Failure callback.
* @prop {AcuantSuccessCallback} onImageCaptureSuccess Success callback.
* @prop {AcuantFailureCallback} onImageCaptureFailure Failure callback.
*/

/**
* @param {AcuantCaptureCanvasProps} props Component props.
*/
function AcuantCaptureCanvas({ onImageCaptureSuccess, onImageCaptureFailure }) {
function AcuantCaptureCanvas({
onImageCaptureSuccess = () => {},
onImageCaptureFailure = () => {},
}) {
useEffect(() => {
window.AcuantCameraUI.start(onImageCaptureSuccess, onImageCaptureFailure);
/** @type {AcuantGlobal} */ (window).AcuantCameraUI.start(
onImageCaptureSuccess,
onImageCaptureFailure,
);

return () => {
window.AcuantCameraUI.end();
/** @type {AcuantGlobal} */ (window).AcuantCameraUI.end();
};
}, []);

Expand All @@ -54,14 +84,4 @@ function AcuantCaptureCanvas({ onImageCaptureSuccess, onImageCaptureFailure }) {
);
}

AcuantCaptureCanvas.propTypes = {
onImageCaptureSuccess: PropTypes.func,
onImageCaptureFailure: PropTypes.func,
};

AcuantCaptureCanvas.defaultProps = {
onImageCaptureSuccess: () => {},
onImageCaptureFailure: () => {},
};

export default AcuantCaptureCanvas;
46 changes: 22 additions & 24 deletions app/javascript/app/document-capture/components/button.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';

/** @typedef {import('react').MouseEvent} ReactMouseEvent */
/** @typedef {import('react').ReactNode} ReactNode */
/** @typedef {"button"|"reset"|"submit"} ButtonType */

/**
* @typedef ButtonProps
*
* @prop {ButtonType=} type Button type, defaulting to "button".
* @prop {(ReactMouseEvent)=>void=} onClick Click handler.
* @prop {ReactNode=} children Element children.
* @prop {boolean=} isPrimary Whether button should be styled as primary button.
* @prop {boolean=} isSecondary Whether button should be styled as secondary button.
* @prop {boolean=} isDisabled Whether button is disabled.
* @prop {boolean=} isUnstyled Whether button should be unstyled, visually as a
* link.
* @prop {string=} className Optional additional class names.
*/

/**
* @param {ButtonProps} props Props object.
*/
function Button({
type,
type = 'button',
onClick,
children,
isPrimary,
Expand Down Expand Up @@ -30,26 +50,4 @@ function Button({
);
}

Button.propTypes = {
type: PropTypes.string,
onClick: PropTypes.func,
children: PropTypes.node,
isPrimary: PropTypes.bool,
isSecondary: PropTypes.bool,
isDisabled: PropTypes.bool,
isUnstyled: PropTypes.bool,
className: PropTypes.string,
};

Button.defaultProps = {
type: 'button',
onClick: undefined,
children: null,
isPrimary: false,
isSecondary: false,
isDisabled: false,
isUnstyled: false,
className: undefined,
};

export default Button;
21 changes: 11 additions & 10 deletions app/javascript/app/document-capture/components/document-capture.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import React, { useState, useContext } from 'react';
import PropTypes from 'prop-types';
import FormSteps from './form-steps';
import DocumentsStep, { isValid as isDocumentsStepValid } from './documents-step';
import SelfieStep, { isValid as isSelfieStepValid } from './selfie-step';
import MobileIntroStep from './mobile-intro-step';
import DeviceContext from '../context/device';
import Submission from './submission';

function DocumentCapture({ isLivenessEnabled }) {
/**
* @typedef DocumentCaptureProps
*
* @prop {boolean=} isLivenessEnabled Whether liveness capture should be expected from the user.
* Defaults to false.
*/

/**
* @param {DocumentCaptureProps} props Props object.
*/
function DocumentCapture({ isLivenessEnabled = true }) {
const [formValues, setFormValues] = useState(null);
const { isMobile } = useContext(DeviceContext);

Expand Down Expand Up @@ -35,12 +44,4 @@ function DocumentCapture({ isLivenessEnabled }) {
);
}

DocumentCapture.propTypes = {
isLivenessEnabled: PropTypes.bool,
};

DocumentCapture.defaultProps = {
isLivenessEnabled: true,
};

export default DocumentCapture;
36 changes: 20 additions & 16 deletions app/javascript/app/document-capture/components/documents-step.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import AcuantCapture from './acuant-capture';
import PageHeading from './page-heading';
import useI18n from '../hooks/use-i18n';
import DeviceContext from '../context/device';
import DataURLFile from '../models/data-url-file';

/** @typedef {import('../models/data-url-file')} DataURLFile */

/**
* @typedef DocumentsStepValue
*
* @prop {DataURLFile=} front_image Front image value.
* @prop {DataURLFile=} back_image Back image value.
*/

/**
* @typedef DocumentsStepProps
*
* @prop {DocumentsStepValue=} value Current value.
* @prop {(nextValue:Partial<DocumentsStepValue>)=>void=} onChange Value change handler.
*/

/**
* Sides of document to present as file input.
Expand All @@ -13,7 +27,10 @@ import DataURLFile from '../models/data-url-file';
*/
const DOCUMENT_SIDES = ['front', 'back'];

function DocumentsStep({ value, onChange }) {
/**
* @param {DocumentsStepProps} props Props object.
*/
function DocumentsStep({ value = {}, onChange = () => {} }) {
const { t } = useI18n();
const { isMobile } = useContext(DeviceContext);

Expand Down Expand Up @@ -51,19 +68,6 @@ function DocumentsStep({ value, onChange }) {
);
}

DocumentsStep.propTypes = {
value: PropTypes.shape({
front_image: PropTypes.instanceOf(DataURLFile),
back_image: PropTypes.instanceOf(DataURLFile),
}),
onChange: PropTypes.func,
};

DocumentsStep.defaultProps = {
value: {},
onChange: () => {},
};

/**
* Returns true if the step is valid for the given values, or false otherwise.
*
Expand Down
53 changes: 30 additions & 23 deletions app/javascript/app/document-capture/components/file-input.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import React, { useContext, useState, useMemo, forwardRef } from 'react';
import PropTypes from 'prop-types';
import DeviceContext from '../context/device';
import useInstanceId from '../hooks/use-instance-id';
import useIfStillMounted from '../hooks/use-if-still-mounted';
import useI18n from '../hooks/use-i18n';
import DataURLFile from '../models/data-url-file';

/** @typedef {import('react').MouseEvent} ReactMouseEvent */
/** @typedef {import('react').ChangeEvent} ReactChangeEvent */
/** @typedef {import('react').RefAttributes} ReactRefAttributes */

/**
* @typedef FileInputProps
*
* @prop {string} label Input label.
* @prop {string=} hint Optional hint text.
* @prop {string=} bannerText Optional banner overlay text.
* @prop {string[]=} accept Optional array of file input accept patterns.
* @prop {DataURLFile=} value Current value.
* @prop {string[]=} errors Errors to show.
* @prop {(ReactMouseEvent)=>void=} onClick Input click handler.
* @prop {(ReactChangeEvent)=>void=} onChange Input change handler.
*/

/**
* Given a data URL string, returns the MIME type.
*
Expand Down Expand Up @@ -89,8 +105,20 @@ export function toDataURL(file) {
});
}

/**
* @type {import('react').ForwardRefExoticComponent<FileInputProps & ReactRefAttributes>}
*/
const FileInput = forwardRef((props, ref) => {
const { label, hint, bannerText, accept, value, errors, onClick, onChange } = props;
const {
label,
hint,
bannerText,
accept,
value,
errors = [],
onClick = () => {},
onChange = () => {},
} = props;
const { t, formatHTML } = useI18n();
const ifStillMounted = useIfStillMounted();
const instanceId = useInstanceId();
Expand Down Expand Up @@ -217,25 +245,4 @@ const FileInput = forwardRef((props, ref) => {
);
});

FileInput.propTypes = {
label: PropTypes.string.isRequired,
hint: PropTypes.string,
bannerText: PropTypes.string,
accept: PropTypes.arrayOf(PropTypes.string),
value: PropTypes.instanceOf(DataURLFile),
errors: PropTypes.arrayOf(PropTypes.string),
onClick: PropTypes.func,
onChange: PropTypes.func,
};

FileInput.defaultProps = {
hint: null,
bannerText: null,
accept: null,
value: undefined,
errors: [],
onClick: () => {},
onChange: () => {},
};

export default FileInput;
Loading