Skip to content

Commit

Permalink
ENH Convert to functional components
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Jan 8, 2025
1 parent 82cb034 commit 0adc403
Show file tree
Hide file tree
Showing 5 changed files with 992 additions and 989 deletions.
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

256 changes: 103 additions & 153 deletions client/src/components/TOTP/Register.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* global window */

import React, { Component } from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { QRCodeSVG } from 'qrcode.react';
import { formatCode } from 'lib/formatCode';
Expand All @@ -15,222 +15,172 @@ const VIEWS = {
* This component provides the user interface for registering one-time time-based passwords (TOTP)
* with a user.
*/
class Register extends Component {
constructor(props) {
super(props);

this.state = {
error: props.error,
view: props.error ? VIEWS.VALIDATE : VIEWS.SCAN,
};

this.handleBack = this.handleBack.bind(this);
this.handleBackToScan = this.handleBackToScan.bind(this);
this.handleNext = this.handleNext.bind(this);
}
function Register(props) {
const {
code,
onBack,
onCompleteRegistration,
error,
errors,
method,
uri,
TOTPVerifyComponent,
} = props;
const [stateError, setStateError] = useState(error);
const [view, setView] = useState(error ? VIEWS.VALIDATE : VIEWS.SCAN);
const i18n = window.ss.i18n;

/**
* Send the user back to the "select method" screen
*/
handleBack() {
this.props.onBack();
function handleBack() {
onBack();
}

/**
* Send the user back to the "scan QR code" screen
*/
handleBackToScan() {
this.setState({
view: VIEWS.SCAN,
error: null,
});
function handleBackToScan() {
setView(VIEWS.SCAN);
setStateError(null);
}

/**
* After user has scanned the QR code, handle the transition to the verify screen
*/
handleNext() {
this.setState({ view: VIEWS.VALIDATE });
function handleNext() {
setView(VIEWS.VALIDATE);
}

/**
* Renders an action button menu with a Next and Back button, using a different handler for
* the click of each button depending on which view we're in.
*
* @returns {HTMLElement}
*/
renderActionsMenu() {
const { ss: { i18n } } = window;

return (
<ul className="mfa-action-list">
<li className="mfa-action-list__item">
<button
type="button"
className="btn btn-primary"
onClick={this.handleNext}
>
{ i18n._t('TOTPRegister.NEXT', 'Next') }
</button>
</li>
<li className="mfa-action-list__item">
<button
type="button"
className="btn btn-secondary"
onClick={this.handleBack}
>
{ i18n._t('TOTPRegister.BACK', 'Back') }
</button>
</li>
</ul>
);
function renderActionsMenu() {
return <ul className="mfa-action-list">
<li className="mfa-action-list__item">
<button
type="button"
className="btn btn-primary"
onClick={handleNext}
>
{ i18n._t('TOTPRegister.NEXT', 'Next') }
</button>
</li>
<li className="mfa-action-list__item">
<button
type="button"
className="btn btn-secondary"
onClick={handleBack}
>
{ i18n._t('TOTPRegister.BACK', 'Back') }
</button>
</li>
</ul>;
}

/**
* Handles rendering of errors returned from the backend API requests, e.g.
* your session has timed out.
*
* @returns {HTMLElement}
*/
renderErrorScreen() {
const { errors } = this.props;

function renderErrorScreen() {
if (!errors.length) {
return null;
}
return <div className="mfa-totp__errors">
{errors.join(', ')}
</div>;
}

return (
<div className="mfa-totp__errors">
{errors.join(', ')}
</div>
);
/**
* If there is a configured support link, will render a link to the TOTP authenticator's
* support documentation (e.g. userhelp).
*/
function renderSupportLink() {
const { supportLink, supportText } = method;
if (!supportLink) {
return null;
}
return <a href={supportLink} target="_blank" rel="noopener noreferrer">
{supportText || i18n._t('TOTPRegister.HOW_TO_USE', 'How to use authenticator apps.')}
</a>;
}

/**
* Renders the screen to scan a QR code with an authenticator app.
*
* @returns {HTMLElement}
*/
renderScanCodeScreen() {
const { uri, code, errors } = this.props;
const { view } = this.state;
const { ss: { i18n } } = window;

function renderScanCodeScreen() {
if (view !== VIEWS.SCAN || errors.length) {
return null;
}

const formattedCode = formatCode(code);

return (
<div>
<div className="mfa-totp__scan">
<p>{ i18n._t(
'TOTPRegister.INTRO',
'Verification codes are created by an app on your phone. '
) }{ this.renderSupportLink() }</p>

<div className="mfa-totp__scan-code">
<div className="mfa-totp__scan-left">
<QRCodeSVG value={uri} size={160} />
</div>

<div className="mfa-totp__scan-middle">
{i18n._t('TOTPRegister.OR', 'Or')}
</div>

<div className="mfa-totp__scan-right">
<p>{i18n._t(
'TOTPRegister.MANUAL',
'Enter manually the following code into authentication app:'
)}</p>
<p className="mfa-totp__manual-code">
{ formattedCode }
</p>
</div>
return <div>
<div className="mfa-totp__scan">
<p>{ i18n._t(
'TOTPRegister.INTRO',
'Verification codes are created by an app on your phone. '
) }{ renderSupportLink() }</p>
<div className="mfa-totp__scan-code">
<div className="mfa-totp__scan-left">
<QRCodeSVG value={uri} size={160} />
</div>
<div className="mfa-totp__scan-middle">
{i18n._t('TOTPRegister.OR', 'Or')}
</div>
<div className="mfa-totp__scan-right">
<p>{i18n._t(
'TOTPRegister.MANUAL',
'Enter manually the following code into authentication app:'
)}</p>
<p className="mfa-totp__manual-code">
{ formattedCode }
</p>
</div>
</div>
{ this.renderActionsMenu() }
</div>
);
}

/**
* If there is a configured support link, will render a link to the TOTP authenticator's
* support documentation (e.g. userhelp).
*
* @returns {HTMLElement}
*/
renderSupportLink() {
const { method: { supportLink, supportText } } = this.props;
const { ss: { i18n } } = window;

if (!supportLink) {
return null;
}

return (
<a href={supportLink} target="_blank" rel="noopener noreferrer">
{supportText || i18n._t('TOTPRegister.HOW_TO_USE', 'How to use authenticator apps.')}
</a>
);
{ renderActionsMenu() }
</div>;
}

/**
* The back button for the verification screen should send you back to the register screen
*
* @return HTMLElement|null
*/
renderBackButtonForVerify() {
const { ss: { i18n } } = window;

return (
<button
type="button"
className="mfa-actions__action mfa-actions__action--back btn btn-secondary"
onClick={this.handleBackToScan}
>
{ i18n._t('TOTPRegister.BACK', 'Back') }
</button>
);
function renderBackButtonForVerify() {
return <button
type="button"
className="mfa-actions__action mfa-actions__action--back btn btn-secondary"
onClick={handleBackToScan}
>
{ i18n._t('TOTPRegister.BACK', 'Back') }
</button>;
}

/**
* Renders the screen to input and validate the TOTP code, after having registered it via QR
* code with an authenticator app.
*
* @returns {HTMLElement}
*/
renderValidateCodeScreen() {
const { error, view } = this.state;
const { TOTPVerifyComponent, onCompleteRegistration, errors } = this.props;

function renderValidateCodeScreen() {
if (view !== VIEWS.VALIDATE || errors.length) {
return null;
}

const verifyProps = {
...this.props,
...props,
// Override the error prop to come from the state instead of props
error,
moreOptionsControl: this.renderBackButtonForVerify(),
error: stateError,
moreOptionsControl: renderBackButtonForVerify(),
// Renaming registration callback so it fits in the Verify context
onCompleteVerification: onCompleteRegistration,
onCompleteRegistration: null,
onCompleteRegistration: null
};

return <TOTPVerifyComponent {...verifyProps} />;
}

render() {
return (
<div className="mfa-totp__container mfa-totp__container--register">
{ this.renderErrorScreen() }
{ this.renderScanCodeScreen() }
{ this.renderValidateCodeScreen() }
</div>
);
}
// Render the component
return <div className="mfa-totp__container mfa-totp__container--register">
{ renderErrorScreen() }
{ renderScanCodeScreen() }
{ renderValidateCodeScreen() }
</div>;
}

Register.propTypes = {
Expand Down
Loading

0 comments on commit 0adc403

Please sign in to comment.