From 6da018f5367cfc2dc190f025e4ce2ee79c0e6e4d Mon Sep 17 00:00:00 2001 From: StevenDufresne Date: Mon, 15 Jul 2024 15:32:11 +0900 Subject: [PATCH 01/49] Try a setup wizard. --- settings/src/components/first-time.js | 113 ++++++++++++++++++ settings/src/components/first-time.scss | 18 +++ settings/src/components/screen-link.js | 1 - settings/src/components/screen-navigation.js | 15 ++- settings/src/components/settings.js | 43 +++++++ settings/src/components/setup-progress-bar.js | 8 +- settings/src/components/totp.js | 4 +- settings/src/script.js | 37 +----- settings/src/style.scss | 1 + 9 files changed, 199 insertions(+), 41 deletions(-) create mode 100644 settings/src/components/first-time.js create mode 100644 settings/src/components/first-time.scss create mode 100644 settings/src/components/settings.js diff --git a/settings/src/components/first-time.js b/settings/src/components/first-time.js new file mode 100644 index 00000000..5c23b818 --- /dev/null +++ b/settings/src/components/first-time.js @@ -0,0 +1,113 @@ +/** + * WordPress dependencies + */ +import { useContext, useState } from "@wordpress/element"; +import { Button, Flex } from "@wordpress/components"; + +/** + * Internal dependencies + */ +import ScreenNavigation from "./screen-navigation"; +import TOTP from "./totp"; +import WebAuthn from "./webauthn/webauthn"; +import BackupCodes from "./backup-codes"; +import SetupProgressBar from "./setup-progress-bar"; +import { GlobalContext } from "../script"; + +function DefaultView({ onSelect }) { + const [selectedOption, setSelectedOption] = useState("webauthn"); + + const handleOptionChange = (e) => { + setSelectedOption(e.target.value); + }; + + const handleButtonClick = () => { + if (selectedOption === "") { + return; + } + + onSelect(selectedOption); + }; + + return ( + <> +

+ As of September 10, 2024, all plugin committers will be required to have + Two-Factor Authentication enabled. +

+
+
+ + +
+
+ + +
+
+ + + + + + ); +} + +/** + * Render the correct component based on the URL. + * + */ +export default function InitialSetup() { + const { navigateToScreen, screen } = useContext(GlobalContext); + + // The index is the URL slug and the value is the React component. + const components = { + totp: , + "backup-codes": , + webauthn: , + home: ( + { + navigateToScreen( val); + } } + /> + ), + }; + + const titles = { + 'home': 'Secure your account', + } + + + const currentScreenComponent = ( + + + {components[screen]} + ) + + return ( +
+
+ {currentScreenComponent} +
+
+ ); +} diff --git a/settings/src/components/first-time.scss b/settings/src/components/first-time.scss new file mode 100644 index 00000000..9a362ccd --- /dev/null +++ b/settings/src/components/first-time.scss @@ -0,0 +1,18 @@ +.wporg-2fa__first-time { + display: flex; + justify-content: center; + position: fixed; + background: #fff; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + padding-top: 100px; + + .wporg-2fa__first-time__inner { + height: 50vh; + width: 60vw; + max-width: 800px; + } +} \ No newline at end of file diff --git a/settings/src/components/screen-link.js b/settings/src/components/screen-link.js index 2519c3db..415b2710 100644 --- a/settings/src/components/screen-link.js +++ b/settings/src/components/screen-link.js @@ -31,7 +31,6 @@ export default function ScreenLink( { screen, anchorText, buttonStyle = false, a // implicitly verifying them, or at least needs to be treated that way. This should be removed once // `two-factor/#507` is fixed, though. setBackupCodesVerified( true ); - navigateToScreen( screen ); }, [ navigateToScreen ] diff --git a/settings/src/components/screen-navigation.js b/settings/src/components/screen-navigation.js index ea8a5278..ef9f7932 100644 --- a/settings/src/components/screen-navigation.js +++ b/settings/src/components/screen-navigation.js @@ -13,12 +13,13 @@ import ScreenLink from './screen-link'; * @param props * @param props.children * @param props.screen + * @param props.title */ -const ScreenNavigation = ( { screen, children } ) => ( +const ScreenNavigation = ( { screen, children, title = '' } ) => ( @@ -29,10 +30,12 @@ const ScreenNavigation = ( { screen, children } ) => ( />

- { screen - .replace( '-', ' ' ) - .replace( 'totp', 'Two-Factor Authentication' ) - .replace( 'webauthn', 'Two-Factor Security Key' ) } + { title.length + ? title + : screen + .replace( '-', ' ' ) + .replace( 'totp', 'Two-Factor Authentication' ) + .replace( 'webauthn', 'Two-Factor Security Key' ) }

{ children } diff --git a/settings/src/components/settings.js b/settings/src/components/settings.js new file mode 100644 index 00000000..7cc3e08a --- /dev/null +++ b/settings/src/components/settings.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ScreenNavigation from './screen-navigation'; +import AccountStatus from './account-status'; +import Password from './password'; +import EmailAddress from './email-address'; +import TOTP from './totp'; +import WebAuthn from './webauthn/webauthn'; +import BackupCodes from './backup-codes'; + +import { GlobalContext } from '../script'; + +/** + * Render the correct component based on the URL. + */ +export default function Settings() { + const { screen } = useContext( GlobalContext ); + + // The index is the URL slug and the value is the React component. + const components = { + home: , + email: , + password: , + totp: , + 'backup-codes': , + webauthn: , + }; + + const currentScreenComponent = + 'home' === screen ? ( + components[ screen ] + ) : ( + { components[ screen ] } + ); + + return <>{ currentScreenComponent }; +} diff --git a/settings/src/components/setup-progress-bar.js b/settings/src/components/setup-progress-bar.js index 963d188f..2d8346b0 100644 --- a/settings/src/components/setup-progress-bar.js +++ b/settings/src/components/setup-progress-bar.js @@ -1,12 +1,18 @@ /** * WordPress dependencies */ -import { Icon, lock, reusableBlock } from '@wordpress/icons'; +import { Icon, lock, edit, reusableBlock } from '@wordpress/icons'; export default function SetupProgressBar( { step } ) { return (
    +
  • + +
    + Select type +
  • +

  • diff --git a/settings/src/components/totp.js b/settings/src/components/totp.js index 1f74bc6c..0f2d8953 100644 --- a/settings/src/components/totp.js +++ b/settings/src/components/totp.js @@ -12,6 +12,7 @@ import { RawHTML, useCallback, useContext, useEffect, useRef, useState } from '@ import ScreenLink from './screen-link'; import AutoTabbingInput from './auto-tabbing-input'; import { refreshRecord } from '../utilities/common'; +import SetupProgressBar from './setup-progress-bar'; import { GlobalContext } from '../script'; import Success from './success'; @@ -28,7 +29,7 @@ export default function TOTP() { if ( ! backupCodesEnabled ) { navigateToScreen( 'backup-codes' ); } else { - navigateToScreen( 'account-status' ); + navigateToScreen( 'home' ); } }, [ backupCodesEnabled, navigateToScreen ] ); @@ -79,7 +80,6 @@ function Setup( { setSuccess } ) { } ); setSecretKey( response.secret_key ); - setQrCodeUrl( response.qr_code_url ); }; fetchSetupData(); diff --git a/settings/src/script.js b/settings/src/script.js index 62456ae6..694daa20 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -15,15 +15,10 @@ import { Spinner } from '@wordpress/components'; * Internal dependencies */ import { useUser } from './hooks/useUser'; -import ScreenNavigation from './components/screen-navigation'; -import AccountStatus from './components/account-status'; -import Password from './components/password'; -import EmailAddress from './components/email-address'; -import TOTP from './components/totp'; -import WebAuthn from './components/webauthn/webauthn'; -import BackupCodes from './components/backup-codes'; import GlobalNotice from './components/global-notice'; import RevalidateModal from './components/revalidate-modal'; +//import Settings from './components/settings'; +import FirstTime from './components/first-time'; export const GlobalContext = createContext( null ); @@ -65,25 +60,11 @@ function Main( { userId } ) { let currentUrl = new URL( document.location.href ); const initialScreen = currentUrl.searchParams.get( 'screen' ); - const [ screen, setScreen ] = useState( initialScreen ); - - // The index is the URL slug and the value is the React component. - const components = { - 'account-status': , - email: , - password: , - totp: , - 'backup-codes': , - webauthn: , - }; + const [ screen, setScreen ] = useState( initialScreen === null ? 'home' : initialScreen ); // The screens where a recent two factor challenge is required. const twoFactorRequiredScreens = [ 'webauthn', 'totp', 'backup-codes' ]; - if ( ! components[ screen ] ) { - setScreen( 'account-status' ); - } - // Listen for back/forward button clicks. useEffect( () => { window.addEventListener( 'popstate', handlePopState ); @@ -130,7 +111,6 @@ function Main( { userId } ) { currentUrl = new URL( document.location.href ); currentUrl.searchParams.set( 'screen', nextScreen ); window.history.pushState( {}, '', currentUrl ); - setError( '' ); setGlobalNotice( '' ); setScreen( nextScreen ); @@ -142,13 +122,6 @@ function Main( { userId } ) { return ; } - const currentScreenComponent = - 'account-status' === screen ? ( - components[ screen ] - ) : ( - { components[ screen ] } - ); - const isRevalidationExpired = twoFactorRequiredScreens.includes( screen ) && hasPrimaryProvider && @@ -166,10 +139,12 @@ function Main( { userId } ) { error, backupCodesVerified, setBackupCodesVerified, + setScreen, + screen, } } > - { currentScreenComponent } + { shouldRevalidate && } ); diff --git a/settings/src/style.scss b/settings/src/style.scss index d9320531..2baeca41 100644 --- a/settings/src/style.scss +++ b/settings/src/style.scss @@ -138,3 +138,4 @@ $alert-blue: #72aee6; @import "components/auto-tabbing-input"; @import "components/revalidate-modal"; @import "components/success"; +@import "components/first-time"; From 64d38efa547e883aabe5aea66b6542b71fb7972a Mon Sep 17 00:00:00 2001 From: StevenDufresne Date: Tue, 16 Jul 2024 15:46:08 +0900 Subject: [PATCH 02/49] More changes. --- settings/src/components/backup-codes.js | 11 +- settings/src/components/first-time.js | 179 +++++++++++++----- settings/src/components/first-time.scss | 65 ++++++- settings/src/components/screen-navigation.js | 25 +-- settings/src/components/settings.js | 14 +- settings/src/components/setup-progress-bar.js | 68 ++++--- .../src/components/setup-progress-bar.scss | 60 +++--- settings/src/components/totp.js | 10 +- 8 files changed, 298 insertions(+), 134 deletions(-) diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index b9be260b..23da6a01 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -19,7 +19,7 @@ import DownloadButton from './download-button'; /** * Setup and manage backup codes. */ -export default function BackupCodes() { +export default function BackupCodes( { onSuccess = () => {} } ) { const { user: { backupCodesEnabled, hasPrimaryProvider, backupCodesRemaining }, backupCodesVerified, @@ -47,7 +47,7 @@ export default function BackupCodes() { regenerating || ! backupCodesVerified ) { - return ; + return ; } return ; @@ -59,7 +59,7 @@ export default function BackupCodes() { * @param props * @param props.setRegenerating */ -function Setup( { setRegenerating } ) { +function Setup( { setRegenerating, onSuccess } ) { const { setGlobalNotice, user: { userRecord }, @@ -150,7 +150,10 @@ function Setup( { setRegenerating } ) { { + setHasPrinted( ! hasPrinted ); + onSuccess(); + } } disabled={ error } /> diff --git a/settings/src/components/first-time.js b/settings/src/components/first-time.js index 5c23b818..2ce6c101 100644 --- a/settings/src/components/first-time.js +++ b/settings/src/components/first-time.js @@ -1,71 +1,92 @@ /** * WordPress dependencies */ -import { useContext, useState } from "@wordpress/element"; -import { Button, Flex } from "@wordpress/components"; +import { useCallback, useContext, useState } from '@wordpress/element'; +import { Button, Flex } from '@wordpress/components'; +import { lock, edit, reusableBlock } from '@wordpress/icons'; /** * Internal dependencies */ -import ScreenNavigation from "./screen-navigation"; -import TOTP from "./totp"; -import WebAuthn from "./webauthn/webauthn"; -import BackupCodes from "./backup-codes"; -import SetupProgressBar from "./setup-progress-bar"; -import { GlobalContext } from "../script"; +import ScreenNavigation from './screen-navigation'; +import TOTP from './totp'; +import WebAuthn from './webauthn/webauthn'; +import BackupCodes from './backup-codes'; +import SetupProgressBar from './setup-progress-bar'; +import { GlobalContext } from '../script'; -function DefaultView({ onSelect }) { - const [selectedOption, setSelectedOption] = useState("webauthn"); +const WordPressLogo = () => ( + + + + + +); - const handleOptionChange = (e) => { - setSelectedOption(e.target.value); +function DefaultView( { onSelect } ) { + const [ selectedOption, setSelectedOption ] = useState( 'totp' ); + + const handleOptionChange = ( event ) => { + setSelectedOption( event.target.value ); }; const handleButtonClick = () => { - if (selectedOption === "") { + if ( selectedOption === '' ) { return; } - onSelect(selectedOption); + onSelect( selectedOption ); }; return ( <>

    - As of September 10, 2024, all plugin committers will be required to have - Two-Factor Authentication enabled. + As of September 10, 2024, all plugin committers will be required to have Two-Factor + Authentication enabled.

    -
    -
    + +
    -
    +
    + Setup One Time Password +

    Use an application to get two-factor authentication codes.

    +
    + +
    +
    + Setup Security Key +

    Use biometrics, digital cryptography, or hardware keys.

    +
    +
    - - - ); @@ -76,38 +97,92 @@ function DefaultView({ onSelect }) { * */ export default function InitialSetup() { - const { navigateToScreen, screen } = useContext(GlobalContext); + const { navigateToScreen, screen } = useContext( GlobalContext ); + const [ steps, setSteps ] = useState( [ + { + id: 'home', + title: 'Select Method', + icon: edit, + }, + { + id: 'totp', + title: 'Configure', + icon: lock, + }, + { + id: 'backup-codes', + title: 'Backup Codes', + icon: reusableBlock, + }, + ] ); // The index is the URL slug and the value is the React component. const components = { - totp: , - "backup-codes": , + totp: ( + { + navigateToScreen( 'backup-codes' ); + } } + /> + ), + 'backup-codes': ( + { + navigateToScreen( 'congratulations' ); + } } + /> + ), webauthn: , home: ( { - navigateToScreen( val); + onSelect={ ( val ) => { + navigateToScreen( val ); + + /* This updates the second item in the steps array to be the right id */ + setSteps( ( prevSteps ) => { + const updatedSteps = [ ...prevSteps ]; + + updatedSteps[ 1 ] = { + ...updatedSteps[ 1 ], + id: val, + }; + + return updatedSteps; + } ); } } /> ), + congratulations: ( +
    +

    Two-Factor Authentication is now enabled.

    +

    + You can now + + page. +

    +
    + ), }; - const titles = { - 'home': 'Secure your account', - } - + const currentStepIndex = useCallback( () => { + return steps.findIndex( ( step ) => step.id === screen ); + }, [ screen, steps ] )(); const currentScreenComponent = ( - - - {components[screen]} - ) + + + { components[ screen ] } + + ); return (
    -
    - {currentScreenComponent} -
    + +
    { currentScreenComponent }
    ); } diff --git a/settings/src/components/first-time.scss b/settings/src/components/first-time.scss index 9a362ccd..c3c30a04 100644 --- a/settings/src/components/first-time.scss +++ b/settings/src/components/first-time.scss @@ -1,6 +1,7 @@ .wporg-2fa__first-time { display: flex; - justify-content: center; + flex-direction: column; + align-items: center; position: fixed; background: #fff; top: 0; @@ -8,11 +9,65 @@ width: 100%; height: 100%; z-index: 1000; - padding-top: 100px; + padding-top: 80px; .wporg-2fa__first-time__inner { + margin-top: 20px; height: 50vh; - width: 60vw; - max-width: 800px; + width: 90vw; + max-width: 700px; } -} \ No newline at end of file + + .wporg-2fa__first-time-default { + padding: 16px 0 0; + display: flex; + flex-direction: column; + gap: 24px; + } + + .wporg-2fa__first-time-default-item { + margin: 6px; + position: relative; + display: flex; + gap: 10px; + align-items: flex-start; + + span { + font-weight: 600; + z-index: 1; + } + + input { + position: relative; + z-index: 1; + margin-top: 4px; + margin-left: 4px; + } + + input:checked + div::before { + position: absolute; + content: ''; + border: 1px solid var(--wp--preset--color--charcoal-3, #40464d); + border-radius: 4px; + height: 140%; + width: 102%; + left: -1%; + top: -20%; + z-index: 0; + } + + > div { + display: flex; + flex-direction: column; + align-items: flex-start; + + p { + color: var(--wp--preset--color--charcoal-4, #656a71); + margin: 0; + z-index: 1; + } + } + } + +} + diff --git a/settings/src/components/screen-navigation.js b/settings/src/components/screen-navigation.js index ef9f7932..2fc32d88 100644 --- a/settings/src/components/screen-navigation.js +++ b/settings/src/components/screen-navigation.js @@ -15,19 +15,22 @@ import ScreenLink from './screen-link'; * @param props.screen * @param props.title */ -const ScreenNavigation = ( { screen, children, title = '' } ) => ( +// eslint-disable-next-line no-unused-vars +const ScreenNavigation = ( { screen, children, title = '', canNavigate = true } ) => ( - - - Back - - } - /> + { canNavigate && ( + + + Back + + } + /> + ) }

    { title.length diff --git a/settings/src/components/settings.js b/settings/src/components/settings.js index 7cc3e08a..bcb156de 100644 --- a/settings/src/components/settings.js +++ b/settings/src/components/settings.js @@ -20,14 +20,24 @@ import { GlobalContext } from '../script'; * Render the correct component based on the URL. */ export default function Settings() { - const { screen } = useContext( GlobalContext ); + const { backupCodesEnabled, navigateToScreen, screen } = useContext( GlobalContext ); // The index is the URL slug and the value is the React component. const components = { home: , email: , password: , - totp: , + totp: ( + { + if ( ! backupCodesEnabled ) { + navigateToScreen( 'backup-codes' ); + } else { + navigateToScreen( 'home' ); + } + } } + /> + ), 'backup-codes': , webauthn: , }; diff --git a/settings/src/components/setup-progress-bar.js b/settings/src/components/setup-progress-bar.js index 2d8346b0..b6fca99c 100644 --- a/settings/src/components/setup-progress-bar.js +++ b/settings/src/components/setup-progress-bar.js @@ -1,43 +1,49 @@ /** * WordPress dependencies */ -import { Icon, lock, edit, reusableBlock } from '@wordpress/icons'; +import { useCallback } from '@wordpress/element'; +import { Icon, check } from '@wordpress/icons'; -export default function SetupProgressBar( { step } ) { - return ( -
    -
      -
    • - -
      - Select type -
    • - -
    • - -
      - Scan QR Code -
    • +export default function SetupProgressBar( { currentStep, steps } ) { + const currentIndex = steps.findIndex( ( step ) => step.id === currentStep ); + const getCompletionPercentage = useCallback( + () => ( ( currentIndex + 1 ) / steps.length ) * 100, + [ currentIndex, steps.length ] + ); -
    • - -
      - Backup Codes -
    • -
    + const getStepClass = ( index ) => { + if ( index === currentIndex ) { + return 'is-enabled'; + } -
      -
    • + if ( currentIndex > index ) { + return 'is-complete'; + } -
    • + return 'is-disabled'; + }; -
    • + return ( +
      +
        + { steps.map( ( step, index ) => ( +
      • + index ? check : step.icon } + /> + { step.title } +
      • + ) ) }
      + +
      ); } diff --git a/settings/src/components/setup-progress-bar.scss b/settings/src/components/setup-progress-bar.scss index b8146f30..543db4bc 100644 --- a/settings/src/components/setup-progress-bar.scss +++ b/settings/src/components/setup-progress-bar.scss @@ -1,7 +1,7 @@ .wporg-2fa__progress-bar, #bbpress-forums .wporg-2fa__progress-bar, #bbpress-forums.bbpress-wrapper .wporg-2fa__progress-bar { - --color-enabled: #0475c4; + --color-enabled: #1e1e1e; --color-disabled: #{$gray-400}; --color-disabled-text: #{$gray-700}; // Darker than `color-disabled` to meet a11y contrast standards. @@ -10,12 +10,20 @@ .wporg-2fa__setup-steps { position: relative; + margin-bottom: 40px; z-index: 2; /* On top of the separators. */ display: flex; justify-content: space-evenly; /* Align the steps up with the separators. */ li { text-align: center; + width: 125px; + + .wporg-2fa__setup-label { + display: block; + margin-top: 8px; + font-size: 12px; + } } svg { @@ -26,7 +34,6 @@ } li.is-enabled { - font-weight: bold; color: var(--color-enabled); svg { @@ -45,29 +52,38 @@ fill: var(--color-disabled); } } - } - - .wporg-2fa__setup-step-separators { - display: flex; - justify-content: space-between; /* Align the separators up with the steps. */ - position: absolute; - top: 27px; - z-index: 1; /* Under the steps. */ - width: 100%; - - li { - height: 3px; - flex-basis: 33%; - flex-grow: 1; - flex-shrink: 1; - &.is-enabled { - background-color: var(--color-enabled); - } + li.is-complete { + color: var(--color-disabled-text); - &.is-disabled { - background-color: var(--color-disabled); + svg { + background-color: var(--wp--preset--color--blueberry-1, #3858e9); + border-color: var(--wp--preset--color--blueberry-1, #3858e9); + fill: white; } } } } + +.wporg-2fa__progress-bar { + margin: 0; +} + +.wporg-2fa__progress-bar .wporg-2fa__setup-step-separator::before { + content: ''; + position: absolute; + height: 2px; + width: var(--wporg-separator-width, 0%); + background: var(--wp--preset--color--blueberry-1, #3858e9); + z-index: 1; +} + +.wporg-2fa__progress-bar .wporg-2fa__setup-step-separator { + position: absolute; + height: 2px; + width: 100%; + top: 25px; + z-index: 0; + background: var(--color-disabled); + z-index: 0; +} \ No newline at end of file diff --git a/settings/src/components/totp.js b/settings/src/components/totp.js index 0f2d8953..f7d29a61 100644 --- a/settings/src/components/totp.js +++ b/settings/src/components/totp.js @@ -12,11 +12,10 @@ import { RawHTML, useCallback, useContext, useEffect, useRef, useState } from '@ import ScreenLink from './screen-link'; import AutoTabbingInput from './auto-tabbing-input'; import { refreshRecord } from '../utilities/common'; -import SetupProgressBar from './setup-progress-bar'; import { GlobalContext } from '../script'; import Success from './success'; -export default function TOTP() { +export default function TOTP( { onSuccess } ) { const { user: { backupCodesEnabled, totpEnabled }, navigateToScreen, @@ -26,11 +25,7 @@ export default function TOTP() { const afterTimeout = useCallback( () => { setSuccess( false ); - if ( ! backupCodesEnabled ) { - navigateToScreen( 'backup-codes' ); - } else { - navigateToScreen( 'home' ); - } + onSuccess(); }, [ backupCodesEnabled, navigateToScreen ] ); if ( success ) { @@ -80,6 +75,7 @@ function Setup( { setSuccess } ) { } ); setSecretKey( response.secret_key ); + setQrCodeUrl( response.qr_code_url ); }; fetchSetupData(); From a3fc16e97e9d6135fe439790e80303ddc33c7870 Mon Sep 17 00:00:00 2001 From: StevenDufresne Date: Wed, 17 Jul 2024 15:40:31 +0900 Subject: [PATCH 03/49] More changes --- settings/src/components/backup-codes.js | 10 +- .../src/components/first-time/first-time.js | 147 +++++++++++++++ .../{ => first-time}/first-time.scss | 38 +++- settings/src/components/first-time/home.js | 62 +++++++ .../{ => first-time}/setup-progress-bar.js | 19 +- .../{ => first-time}/setup-progress-bar.scss | 14 +- .../wordpress-logo.js} | 174 +----------------- settings/src/components/webauthn/webauthn.js | 6 +- settings/src/script.js | 2 +- settings/src/style.scss | 4 +- 10 files changed, 269 insertions(+), 207 deletions(-) create mode 100644 settings/src/components/first-time/first-time.js rename settings/src/components/{ => first-time}/first-time.scss (69%) create mode 100644 settings/src/components/first-time/home.js rename settings/src/components/{ => first-time}/setup-progress-bar.js (61%) rename settings/src/components/{ => first-time}/setup-progress-bar.scss (86%) rename settings/src/components/{first-time.js => first-time/wordpress-logo.js} (54%) diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index 23da6a01..86bf2979 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -18,6 +18,9 @@ import DownloadButton from './download-button'; /** * Setup and manage backup codes. + * + * @param props + * @param props.onSuccess */ export default function BackupCodes( { onSuccess = () => {} } ) { const { @@ -58,6 +61,7 @@ export default function BackupCodes( { onSuccess = () => {} } ) { * * @param props * @param props.setRegenerating + * @param props.onSuccess */ function Setup( { setRegenerating, onSuccess } ) { const { @@ -111,6 +115,7 @@ function Setup( { setRegenerating, onSuccess } ) { await refreshRecord( userRecord ); // This has the intended side-effect of redirecting to the Manage screen. setGlobalNotice( 'Backup codes have been enabled.' ); setRegenerating( false ); + onSuccess(); } ); return ( @@ -150,10 +155,7 @@ function Setup( { setRegenerating, onSuccess } ) { { - setHasPrinted( ! hasPrinted ); - onSuccess(); - } } + onChange={ setHasPrinted } disabled={ error } /> diff --git a/settings/src/components/first-time/first-time.js b/settings/src/components/first-time/first-time.js new file mode 100644 index 00000000..17605841 --- /dev/null +++ b/settings/src/components/first-time/first-time.js @@ -0,0 +1,147 @@ +/** + * WordPress dependencies + */ +import { useEffect, useContext } from '@wordpress/element'; +import { Button } from '@wordpress/components'; +import { lock, edit, reusableBlock } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import ScreenNavigation from '../screen-navigation'; +import TOTP from '../totp'; +import WebAuthn from '../webauthn/webauthn'; +import BackupCodes from '../backup-codes'; +import SetupProgressBar from './setup-progress-bar'; +import Home from './home'; +import WordPressLogo from './wordpress-logo'; +import { GlobalContext } from '../../script'; + +/** + * Render the correct component based on the URL. + * + */ +export default function FirstTime() { + const { navigateToScreen, screen } = useContext( GlobalContext ); + const steps = [ + { + title: 'Choose an authentication method', + label: 'Select', + icon: edit, + }, + { + title: 'Set up your authentication method', + label: 'Configure', + icon: lock, + }, + { + title: 'Print Backup Codes', + label: 'Print', + icon: reusableBlock, + }, + ]; + + // The index is the URL slug and the value is the React component. + const screens = { + home: { + stepIndex: 0, + component: ( + { + navigateToScreen( val ); + } } + /> + ), + }, + totp: { + stepIndex: 1, + component: ( + { + navigateToScreen( 'backup-codes' ); + } } + /> + ), + }, + webauthn: { + stepIndex: 1, + component: ( + { + navigateToScreen( 'backup-codes' ); + } } + /> + ), + }, + 'backup-codes': { + stepIndex: 2, + component: ( + { + navigateToScreen( 'congratulations' ); + } } + /> + ), + }, + congratulations: { + stepIndex: 3, + component: ( +
      +

      Two-factor authentication setup is now complete! 🎉

      +

      + To ensure the highest level of security for your account, please remember to + keep your authentication methods up to date. We recommend configuring + multiple authentication methods to guarantee you always have access to your + account. +

      +
      + +
      +
      + ), + }, + }; + + // Lock the scroll when the modal is open. + useEffect( () => { + document.querySelector( 'html' ).style.overflow = 'hidden'; + + return () => { + document.querySelector( 'html' ).style.overflow = 'initial'; + }; + }, [] ); + + const currentStepIndex = screens[ screen ].stepIndex; + let currentScreenComponent = null; + + if ( 'congratulations' === screen ) { + currentScreenComponent = ( + + { screens[ screen ].component } + + ); + } else { + currentScreenComponent = ( + + + { screens[ screen ].component } + + ); + } + + return ( +
      +
      +
      + + { currentScreenComponent } + +
      +
      +
      + ); +} diff --git a/settings/src/components/first-time.scss b/settings/src/components/first-time/first-time.scss similarity index 69% rename from settings/src/components/first-time.scss rename to settings/src/components/first-time/first-time.scss index c3c30a04..26e6f728 100644 --- a/settings/src/components/first-time.scss +++ b/settings/src/components/first-time/first-time.scss @@ -1,7 +1,4 @@ .wporg-2fa__first-time { - display: flex; - flex-direction: column; - align-items: center; position: fixed; background: #fff; top: 0; @@ -9,13 +6,36 @@ width: 100%; height: 100%; z-index: 1000; - padding-top: 80px; + .wporg-2fa__first-time__inner { + padding: 80px 0; margin-top: 20px; - height: 50vh; - width: 90vw; - max-width: 700px; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + height: 100%; + overflow: scroll; + } + + .wporg-2fa__first-time__inner-content { + display: flex; + flex-direction: column; + align-items: center; + + @media (min-width: 600px) { + width: 600px; + } + + > svg { + margin-bottom: 16px; + } + + // Preven cards from shrinking. + .components-card { + width: 100%; + } } .wporg-2fa__first-time-default { @@ -68,6 +88,8 @@ } } } - } +.wporg-2fa__congratulations h3 { + margin-bottom: 14px; +} diff --git a/settings/src/components/first-time/home.js b/settings/src/components/first-time/home.js new file mode 100644 index 00000000..6d99ecff --- /dev/null +++ b/settings/src/components/first-time/home.js @@ -0,0 +1,62 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; +import { Button, Flex } from '@wordpress/components'; + +export default function Select( { onSelect } ) { + const [ selectedOption, setSelectedOption ] = useState( 'totp' ); + + const handleOptionChange = ( event ) => { + setSelectedOption( event.target.value ); + }; + + const handleButtonClick = () => { + if ( selectedOption === '' ) { + return; + } + + onSelect( selectedOption ); + }; + + return ( + <> +

      Select a method to configure two-factor authentication for your account.

      +
      + + +
      + + + + + ); +} diff --git a/settings/src/components/setup-progress-bar.js b/settings/src/components/first-time/setup-progress-bar.js similarity index 61% rename from settings/src/components/setup-progress-bar.js rename to settings/src/components/first-time/setup-progress-bar.js index b6fca99c..4d7652be 100644 --- a/settings/src/components/setup-progress-bar.js +++ b/settings/src/components/first-time/setup-progress-bar.js @@ -4,19 +4,18 @@ import { useCallback } from '@wordpress/element'; import { Icon, check } from '@wordpress/icons'; -export default function SetupProgressBar( { currentStep, steps } ) { - const currentIndex = steps.findIndex( ( step ) => step.id === currentStep ); +export default function SetupProgressBar( { currentStepIndex, steps } ) { const getCompletionPercentage = useCallback( - () => ( ( currentIndex + 1 ) / steps.length ) * 100, - [ currentIndex, steps.length ] + () => ( currentStepIndex / ( steps.length - 1 ) ) * 100, + [ currentStepIndex, steps.length ] ); const getStepClass = ( index ) => { - if ( index === currentIndex ) { + if ( index === currentStepIndex ) { return 'is-enabled'; } - if ( currentIndex > index ) { + if ( currentStepIndex > index ) { return 'is-complete'; } @@ -29,11 +28,11 @@ export default function SetupProgressBar( { currentStep, steps } ) { { steps.map( ( step, index ) => (
    • index ? check : step.icon } + width="16px" + height="16px" + icon={ currentStepIndex > index ? check : step.icon } /> - { step.title } + { step.label }
    • ) ) }
    diff --git a/settings/src/components/setup-progress-bar.scss b/settings/src/components/first-time/setup-progress-bar.scss similarity index 86% rename from settings/src/components/setup-progress-bar.scss rename to settings/src/components/first-time/setup-progress-bar.scss index 543db4bc..fcf4eed8 100644 --- a/settings/src/components/setup-progress-bar.scss +++ b/settings/src/components/first-time/setup-progress-bar.scss @@ -6,29 +6,26 @@ --color-disabled-text: #{$gray-700}; // Darker than `color-disabled` to meet a11y contrast standards. position: relative; - margin: 0 -24px; /* Separators need to stretch to edges of container. */ .wporg-2fa__setup-steps { position: relative; - margin-bottom: 40px; + margin-bottom: 32px; z-index: 2; /* On top of the separators. */ display: flex; - justify-content: space-evenly; /* Align the steps up with the separators. */ + justify-content: space-between; li { text-align: center; - width: 125px; .wporg-2fa__setup-label { display: block; - margin-top: 8px; font-size: 12px; } } svg { box-sizing: content-box; - padding: 15px; + padding: 8px; border: 1px solid; border-radius: 30px; } @@ -80,9 +77,10 @@ .wporg-2fa__progress-bar .wporg-2fa__setup-step-separator { position: absolute; + width: 96%; height: 2px; - width: 100%; - top: 25px; + margin-left: 2%; + top: 16px; z-index: 0; background: var(--color-disabled); z-index: 0; diff --git a/settings/src/components/first-time.js b/settings/src/components/first-time/wordpress-logo.js similarity index 54% rename from settings/src/components/first-time.js rename to settings/src/components/first-time/wordpress-logo.js index 2ce6c101..aec32250 100644 --- a/settings/src/components/first-time.js +++ b/settings/src/components/first-time/wordpress-logo.js @@ -1,21 +1,4 @@ -/** - * WordPress dependencies - */ -import { useCallback, useContext, useState } from '@wordpress/element'; -import { Button, Flex } from '@wordpress/components'; -import { lock, edit, reusableBlock } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import ScreenNavigation from './screen-navigation'; -import TOTP from './totp'; -import WebAuthn from './webauthn/webauthn'; -import BackupCodes from './backup-codes'; -import SetupProgressBar from './setup-progress-bar'; -import { GlobalContext } from '../script'; - -const WordPressLogo = () => ( +export default () => ( ( > ); - -function DefaultView( { onSelect } ) { - const [ selectedOption, setSelectedOption ] = useState( 'totp' ); - - const handleOptionChange = ( event ) => { - setSelectedOption( event.target.value ); - }; - - const handleButtonClick = () => { - if ( selectedOption === '' ) { - return; - } - - onSelect( selectedOption ); - }; - - return ( - <> -

    - As of September 10, 2024, all plugin committers will be required to have Two-Factor - Authentication enabled. -

    -
    - - -
    - - - - - ); -} - -/** - * Render the correct component based on the URL. - * - */ -export default function InitialSetup() { - const { navigateToScreen, screen } = useContext( GlobalContext ); - const [ steps, setSteps ] = useState( [ - { - id: 'home', - title: 'Select Method', - icon: edit, - }, - { - id: 'totp', - title: 'Configure', - icon: lock, - }, - { - id: 'backup-codes', - title: 'Backup Codes', - icon: reusableBlock, - }, - ] ); - - // The index is the URL slug and the value is the React component. - const components = { - totp: ( - { - navigateToScreen( 'backup-codes' ); - } } - /> - ), - 'backup-codes': ( - { - navigateToScreen( 'congratulations' ); - } } - /> - ), - webauthn: , - home: ( - { - navigateToScreen( val ); - - /* This updates the second item in the steps array to be the right id */ - setSteps( ( prevSteps ) => { - const updatedSteps = [ ...prevSteps ]; - - updatedSteps[ 1 ] = { - ...updatedSteps[ 1 ], - id: val, - }; - - return updatedSteps; - } ); - } } - /> - ), - congratulations: ( -
    -

    Two-Factor Authentication is now enabled.

    -

    - You can now - - page. -

    -
    - ), - }; - - const currentStepIndex = useCallback( () => { - return steps.findIndex( ( step ) => step.id === screen ); - }, [ screen, steps ] )(); - - const currentScreenComponent = ( - - - { components[ screen ] } - - ); - - return ( -
    - -
    { currentScreenComponent }
    -
    - ); -} diff --git a/settings/src/components/webauthn/webauthn.js b/settings/src/components/webauthn/webauthn.js index 43fac9ae..d6f6f276 100644 --- a/settings/src/components/webauthn/webauthn.js +++ b/settings/src/components/webauthn/webauthn.js @@ -16,8 +16,11 @@ import RegisterKey from './register-key'; /** * Render the WebAuthn setting. + * + * @param {Object} props + * @param {Function} props.onKeyAdd */ -export default function WebAuthn() { +export default function WebAuthn( { onKeyAdd = () => {} } ) { const { user: { userRecord, webAuthnEnabled }, setGlobalNotice, @@ -96,6 +99,7 @@ export default function WebAuthn() { } updateFlow( 'manage' ); + onKeyAdd(); }, [ webAuthnEnabled, toggleProvider, updateFlow ] ); if ( 'register' === flow ) { diff --git a/settings/src/script.js b/settings/src/script.js index 694daa20..fd933276 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -18,7 +18,7 @@ import { useUser } from './hooks/useUser'; import GlobalNotice from './components/global-notice'; import RevalidateModal from './components/revalidate-modal'; //import Settings from './components/settings'; -import FirstTime from './components/first-time'; +import FirstTime from './components/first-time/first-time'; export const GlobalContext = createContext( null ); diff --git a/settings/src/style.scss b/settings/src/style.scss index 2baeca41..5dad1b15 100644 --- a/settings/src/style.scss +++ b/settings/src/style.scss @@ -131,11 +131,11 @@ $alert-blue: #72aee6; @import "components/webauthn/webauthn"; @import "components/totp"; @import "components/backup-codes"; -@import "components/setup-progress-bar"; @import "components/global-notice"; @import "components/screen-link"; @import "components/screen-navigation"; @import "components/auto-tabbing-input"; @import "components/revalidate-modal"; @import "components/success"; -@import "components/first-time"; +@import "components/first-time/first-time"; +@import "components/first-time/setup-progress-bar"; From 53131c5904a3debc3b0b3dc615a6447e4a4d530b Mon Sep 17 00:00:00 2001 From: StevenDufresne Date: Wed, 17 Jul 2024 16:00:29 +0900 Subject: [PATCH 04/49] Fix the linter. --- settings/src/components/first-time/first-time.js | 1 - settings/src/components/first-time/first-time.scss | 5 ++++- settings/src/components/first-time/home.js | 5 +++-- settings/src/components/screen-navigation.js | 1 + settings/src/components/totp.js | 5 ++--- settings/src/components/webauthn/webauthn.js | 2 +- settings/src/script.js | 9 +++++++-- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/settings/src/components/first-time/first-time.js b/settings/src/components/first-time/first-time.js index 17605841..8d8ba04d 100644 --- a/settings/src/components/first-time/first-time.js +++ b/settings/src/components/first-time/first-time.js @@ -139,7 +139,6 @@ export default function FirstTime() {
    { currentScreenComponent } -

diff --git a/settings/src/components/first-time/first-time.scss b/settings/src/components/first-time/first-time.scss index 26e6f728..e5efd855 100644 --- a/settings/src/components/first-time/first-time.scss +++ b/settings/src/components/first-time/first-time.scss @@ -7,7 +7,6 @@ height: 100%; z-index: 1000; - .wporg-2fa__first-time__inner { padding: 80px 0; margin-top: 20px; @@ -36,6 +35,10 @@ .components-card { width: 100%; } + + .components-card__body { + padding: 32px; + } } .wporg-2fa__first-time-default { diff --git a/settings/src/components/first-time/home.js b/settings/src/components/first-time/home.js index 6d99ecff..d7f9d95c 100644 --- a/settings/src/components/first-time/home.js +++ b/settings/src/components/first-time/home.js @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ /** * WordPress dependencies */ @@ -23,7 +24,7 @@ export default function Select( { onSelect } ) { <>

Select a method to configure two-factor authentication for your account.

-