Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH Convert to functional components #583

Draft
wants to merge 1 commit into
base: 6.0
Choose a base branch
from
Draft
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
4,375 changes: 4,374 additions & 1 deletion client/dist/js/bundle-cms.js

Large diffs are not rendered by default.

64,522 changes: 64,521 additions & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

21 changes: 20 additions & 1 deletion client/dist/js/injector.js

Large diffs are not rendered by default.

409 changes: 408 additions & 1 deletion client/dist/styles/bundle-cms.css

Large diffs are not rendered by default.

416 changes: 415 additions & 1 deletion client/dist/styles/bundle.css

Large diffs are not rendered by default.

215 changes: 76 additions & 139 deletions client/src/components/BackupCodes/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, useRef } from 'react';
import PropTypes from 'prop-types';
import Printd from 'printd';
import { CopyToClipboard } from 'react-copy-to-clipboard';
Expand All @@ -10,148 +10,105 @@ import { formatCode } from 'lib/formatCode';
* This component provides the user interface for registering backup codes with a user. This process
* only involves showing the user the backup codes. User input is not required to set up the codes.
*/
class Register extends Component {
constructor(props) {
super(props);

this.state = {
recentlyCopied: false
};

// Prepare a ref (in a React 15 compatible way) to use for the DOM node that will be printed
this.printRef = null;
this.setPrintRef = element => {
this.printRef = element;
};
// Prepare a class member to store a timeout ref that provides feedback on copy to clipboard
this.copyMessageTimeout = null;

this.handlePrint = this.handlePrint.bind(this);
this.handleCopy = this.handleCopy.bind(this);
}
function Register(props) {
const {
codes,
method,
onCompleteRegistration,
copyFeedbackDuration,
} = props;
const [recentlyCopied, setRecentlyCopied] = useState(false);
const copyMessageTimeout = useRef(null);
const printRef = useRef(null);
const i18n = window.ss.i18n;

/**
* Get codes from component properties and format them with spaces every 3 (or 4) characters.
* The number of characters in each group will never be less than 3 - the groups towards the end
* will have four characters instead.
*
* @return {string[]}
*/
getFormattedCodes() {
const { codes } = this.props;

function getFormattedCodes() {
return codes.map(code => formatCode(code));
}

/**
* Handle an event triggered requesting the backup codes to be printed
*
* @param {Event} event
*/
handlePrint(event) {
function handlePrint(event) {
event.preventDefault();

(new Printd()).print(
this.printRef,
printRef,
['body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif }']
);
}

/**
* Handle an event triggered requesting the backup codes to be copied to clipboard
*
* @param {Event} event
*/
handleCopy(event) {
function handleCopy(event) {
event.preventDefault();
const { copyFeedbackDuration } = this.props;

this.setState({
recentlyCopied: true,
});

setRecentlyCopied(true);
// Clear an existing timeout to reset the text on the copy link
if (this.copyMessageTimeout) {
clearTimeout(this.copyMessageTimeout);
if (copyMessageTimeout.current) {
clearTimeout(copyMessageTimeout.current);
}

// And set that timeout too
this.copyMessageTimeout = setTimeout(() => {
this.setState({
recentlyCopied: false,
});
copyMessageTimeout.current = setTimeout(() => {
setRecentlyCopied(false);
}, copyFeedbackDuration);
}

/**
* Render a grid of formatted backup codes
*
* @return {HTMLElement}
*/
renderCodes() {
function renderCodes() {
return (
<pre ref={this.setPrintRef} className="mfa-register-backup-codes__code-grid">
{this.getFormattedCodes().map(code => <div key={code}>{code}</div>)}
<pre ref={printRef} className="mfa-register-backup-codes__code-grid">
{getFormattedCodes().map(code => <div key={code}>{code}</div>)}
</pre>
);
}

/**
* Render the description for registering in with this method
*
* @return {HTMLElement}
*/
renderDescription() {
const { ss: { i18n } } = window;
const { method: { supportLink, supportText } } = this.props;

return (
<p>
{i18n._t(
'MFABackupCodesRegister.DESCRIPTION',
'Recovery codes enable you to log into your account in the event your primary ' +
'authentication is not available. Each code can only be used once. Store these codes ' +
'somewhere safe, as they will not be viewable after leaving this page.'
)}
&nbsp;
{supportLink &&
<a
href={supportLink}
target="_blank"
rel="noopener noreferrer"
>
{supportText || i18n._t('MFARegister.RECOVERY_HELP', 'Learn more about recovery codes.')}
</a>
}
</p>
);
function renderDescription() {
const { supportLink, supportText } = method;
return <p>
{i18n._t(
'MFABackupCodesRegister.DESCRIPTION',
'Recovery codes enable you to log into your account in the event your primary ' +
'authentication is not available. Each code can only be used once. Store these codes ' +
'somewhere safe, as they will not be viewable after leaving this page.'
)}
&nbsp;
{supportLink &&
<a
href={supportLink}
target="_blank"
rel="noopener noreferrer"
>
{supportText || i18n._t('MFARegister.RECOVERY_HELP', 'Learn more about recovery codes.')}
</a>
}
</p>;
}

/**
* Render the "print" action. A link allowing the user to trigger a print dialog for the codes
*
* @return {HTMLElement}
*/
renderPrintAction() {
const { ss: { i18n } } = window;

return (
<button type="button" onClick={this.handlePrint} className="btn btn-link">
{i18n._t('MFABackupCodesRegister.PRINT', 'Print codes')}
</button>
);
function renderPrintAction() {
return <button type="button" onClick={handlePrint} className="btn btn-link">
{i18n._t('MFABackupCodesRegister.PRINT', 'Print codes')}
</button>;
}

/**
* Render the "download" action. A link allowing the user to trigger a download of a text file
* containing the codes
*
* @return {HTMLElement}
*/
renderDownloadAction() {
const { codes, method } = this.props;
const { Blob, URL, ss: { i18n }, navigator } = window;

function renderDownloadAction() {
const { Blob, URL, navigator } = window;
const filename = `${method.name}.txt`;
const codesText = codes.join('\r\n');
const codesBlob = new Blob([codesText], { type: 'text/plain;charset=UTF-8' });
Expand All @@ -162,62 +119,42 @@ class Register extends Component {
navigator.msSaveBlob(codesBlob, filename);
}
};

return (
<a download={filename} href={codesURL} className="btn btn-link" onClick={supportInternetExplorer}>
{i18n._t('MFABackupCodesRegister.DOWNLOAD', 'Download')}
</a>
);
return <a download={filename} href={codesURL} className="btn btn-link" onClick={supportInternetExplorer}>
{i18n._t('MFABackupCodesRegister.DOWNLOAD', 'Download')}
</a>;
}

/**
* Render the "copy" action. A link allowing the user to easily copy the backup codes to clipboard
*
* @return {CopyToClipboard}
*/
renderCopyAction() {
const { codes } = this.props;
const { recentlyCopied } = this.state;
const { ss: { i18n } } = window;

function renderCopyAction() {
const label = recentlyCopied
? i18n._t('MFABackupCodesRegister.COPY_RECENT', 'Copied!')
: i18n._t('MFABackupCodesRegister.COPY', 'Copy codes');

return (
<CopyToClipboard text={codes.join('\n')}>
<button
type="button"
className="mfa-register-backup-codes__copy-to-clipboard btn btn-link"
onClick={this.handleCopy}
>
{label}
</button>
</CopyToClipboard>
);
return <CopyToClipboard text={codes.join('\n')}>
<button
type="button"
className="mfa-register-backup-codes__copy-to-clipboard btn btn-link"
onClick={() => handleCopy()}
>
{label}
</button>
</CopyToClipboard>;
}

render() {
const { onCompleteRegistration } = this.props;
const { ss: { i18n } } = window;

return (
<div className="mfa-register-backup-codes__container">
{this.renderDescription()}
{this.renderCodes()}

<div className="mfa-register-backup-codes__helper-links">
{this.renderPrintAction()}
{this.renderDownloadAction()}
{this.renderCopyAction()}
</div>

<button className="btn btn-primary" onClick={() => onCompleteRegistration()}>
{i18n._t('MFABackupCodesRegister.FINISH', 'Finish')}
</button>
</div>
);
}
// Render component
return <div className="mfa-register-backup-codes__container">
{renderDescription()}
{renderCodes()}
<div className="mfa-register-backup-codes__helper-links">
{renderPrintAction()}
{renderDownloadAction()}
{renderCopyAction()}
</div>
<button className="btn btn-primary" onClick={() => onCompleteRegistration()}>
{i18n._t('MFABackupCodesRegister.FINISH', 'Finish')}
</button>
</div>;
}

Register.propTypes = {
Expand Down
Loading
Loading