From 97f63a2c3ee4679c881ee27dac0bf62b08a13b73 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Fri, 1 Jul 2022 16:59:50 -0300 Subject: [PATCH 01/42] feat(reader-activation): account menu item --- assets/reader-activation/nav.js | 14 +++++++++ includes/class-reader-activation.php | 47 ++++++++++++++++++++++++++++ webpack.config.js | 1 + 3 files changed, 62 insertions(+) create mode 100644 assets/reader-activation/nav.js diff --git a/assets/reader-activation/nav.js b/assets/reader-activation/nav.js new file mode 100644 index 0000000000..68c25e74a4 --- /dev/null +++ b/assets/reader-activation/nav.js @@ -0,0 +1,14 @@ +( function ( readerActivation ) { + if ( ! readerActivation ) { + return; + } + [ ...document.querySelectorAll( '.newspack-reader-account-link' ) ].forEach( menuItem => { + menuItem.querySelector( 'a' ).addEventListener( 'click', function ( ev ) { + /** If logged in, allow page redirection. */ + if ( menuItem.classList.contains( 'logged-in' ) ) { + return; + } + ev.preventDefault(); + } ); + } ); +} )( window.newspackReaderActivation ); diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 457757ba19..95f5e826b6 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -34,6 +34,7 @@ public static function init() { \add_action( 'resetpass_form', [ __CLASS__, 'set_reader_verified' ] ); \add_action( 'password_reset', [ __CLASS__, 'set_reader_verified' ] ); \add_action( 'auth_cookie_expiration', [ __CLASS__, 'auth_cookie_expiration' ], 10, 3 ); + \add_action( 'wp_nav_menu_items', [ __CLASS__, 'nav_menu_items' ], 20, 2 ); } } @@ -63,6 +64,20 @@ public static function enqueue_scripts() { ); \wp_script_add_data( $handle, 'async', true ); \wp_script_add_data( $handle, 'amp-plus', true ); + + /** + * Nav menu items script. + */ + $nav_handle = 'newspack-reader-activation-nav'; + wp_enqueue_script( + $nav_handle, + Newspack::plugin_url() . '/dist/reader-activation-nav.js', + [ $handle ], + NEWSPACK_PLUGIN_VERSION, + true + ); + \wp_script_add_data( $nav_handle, 'async', true ); + \wp_script_add_data( $nav_handle, 'amp-plus', true ); } /** @@ -229,6 +244,38 @@ public static function auth_cookie_expiration( $length, $user_id, $remember ) { return $length; } + /** + * Setup nav menu items for reader account access. + * + * @param string $items The HTML list content for the menu items. + * @param stdClass $args An object containing wp_nav_menu() arguments. + * + * @return string The HTML list content for the menu items. + */ + public static function nav_menu_items( $items, $args ) { + + /** Do not alter items for authenticated non-readers */ + if ( is_user_logged_in() && ! self::is_user_reader( wp_get_current_user() ) ) { + return $items; + } + + /** Menu locations to add the account menu item */ + $menu_locations = [ 'primary-menu' ]; + if ( ! in_array( $args->theme_location, $menu_locations, true ) ) { + return $items; + } + $account_url = ''; + if ( function_exists( 'wc_get_account_endpoint_url' ) ) { + $account_url = \wc_get_account_endpoint_url( 'dashboard' ); + } + $classnames = [ 'menu-item', 'newspack-reader-account-link' ]; + $classnames[] = \is_user_logged_in() ? 'logged-in' : 'logged-out'; + $items .= '
  • '; + $items .= '' . \esc_html__( 'My Account', 'newspack' ) . ''; + $items .= '
  • '; + return $items; + } + /** * Check if current reader has its email verified. * diff --git a/webpack.config.js b/webpack.config.js index ef1e02a66d..1a96cc013f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -48,6 +48,7 @@ const webpackConfig = getBaseWebpackConfig( ...wizardsScriptFiles, blocks: path.join( __dirname, 'assets', 'blocks', 'index.js' ), 'reader-activation': path.join( __dirname, 'assets', 'reader-activation', 'index.js' ), + 'reader-activation-nav': path.join( __dirname, 'assets', 'reader-activation', 'nav.js' ), 'reader-registration-block': path.join( __dirname, 'assets', From 7306b58a27cca8d3e74c85cff5ed1971ad5220d3 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Fri, 1 Jul 2022 17:02:29 -0300 Subject: [PATCH 02/42] fix: tweaks --- includes/class-reader-activation.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 95f5e826b6..1d792292d4 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -259,7 +259,11 @@ public static function nav_menu_items( $items, $args ) { return $items; } - /** Menu locations to add the account menu item */ + /** + * Menu locations to add the account menu item + * + * TODO: Manage menu locations through Engagement wizard. + */ $menu_locations = [ 'primary-menu' ]; if ( ! in_array( $args->theme_location, $menu_locations, true ) ) { return $items; @@ -268,11 +272,13 @@ public static function nav_menu_items( $items, $args ) { if ( function_exists( 'wc_get_account_endpoint_url' ) ) { $account_url = \wc_get_account_endpoint_url( 'dashboard' ); } - $classnames = [ 'menu-item', 'newspack-reader-account-link' ]; - $classnames[] = \is_user_logged_in() ? 'logged-in' : 'logged-out'; - $items .= '
  • '; - $items .= '' . \esc_html__( 'My Account', 'newspack' ) . ''; - $items .= '
  • '; + $classnames = [ 'menu-item', 'newspack-reader-account-link' ]; + if ( \is_user_logged_in() ) { + $classnames[] = 'logged-in'; + } + $items .= '
  • '; + $items .= '' . \esc_html__( 'My Account', 'newspack' ) . ''; + $items .= '
  • '; return $items; } From 2a4097ba1370405e153938d943ff7c856f3501cf Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Fri, 1 Jul 2022 17:02:59 -0300 Subject: [PATCH 03/42] fix: namespace --- includes/class-reader-activation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 1d792292d4..507876d835 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -255,7 +255,7 @@ public static function auth_cookie_expiration( $length, $user_id, $remember ) { public static function nav_menu_items( $items, $args ) { /** Do not alter items for authenticated non-readers */ - if ( is_user_logged_in() && ! self::is_user_reader( wp_get_current_user() ) ) { + if ( \is_user_logged_in() && ! self::is_user_reader( \wp_get_current_user() ) ) { return $items; } From d74aada831ed36e1c8799b9c4b9d9b8b4efc0b6d Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Mon, 4 Jul 2022 18:10:50 -0300 Subject: [PATCH 04/42] feat: auth form --- assets/blocks/reader-registration/index.php | 5 +- assets/reader-activation/auth.js | 87 ++++++++++ assets/reader-activation/auth.scss | 72 ++++++++ assets/reader-activation/index.js | 18 +- assets/reader-activation/nav.js | 14 -- includes/class-reader-activation.php | 178 +++++++++++++++++--- webpack.config.js | 2 +- 7 files changed, 332 insertions(+), 44 deletions(-) create mode 100644 assets/reader-activation/auth.js create mode 100644 assets/reader-activation/auth.scss delete mode 100644 assets/reader-activation/nav.js diff --git a/assets/blocks/reader-registration/index.php b/assets/blocks/reader-registration/index.php index bc225816e0..33583e6adb 100644 --- a/assets/blocks/reader-registration/index.php +++ b/assets/blocks/reader-registration/index.php @@ -181,7 +181,10 @@ function process_form() { } return send_form_response( - [ 'email' => $email ], + [ + 'email' => $email, + 'authenticated' => false !== $user_id, + ], false === $user_id ? __( 'Check your email for a confirmation link!', 'newspack' ) : __( 'Thank you for registering!', 'newspack' ) ); } diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js new file mode 100644 index 0000000000..1010e92b65 --- /dev/null +++ b/assets/reader-activation/auth.js @@ -0,0 +1,87 @@ +/** + * WordPress dependencies. + */ +import domReady from '@wordpress/dom-ready'; + +/** + * Internal dependencies. + */ +import './auth.scss'; + +( function ( readerActivation ) { + if ( ! readerActivation ) { + return; + } + domReady( function () { + const form = document.querySelector( '#newspack-reader-activation-auth-form' ); + if ( ! form ) { + return; + } + const emailInput = form.querySelector( 'input[name="email"]' ); + const passwordInput = form.querySelector( 'input[name="password"]' ); + const redirectInput = form.querySelector( 'input[name="redirect"]' ); + const submitButton = form.querySelector( '[type="submit"]' ); + + const authLinkMessage = form.querySelector( '.auth-link-message' ); + authLinkMessage.hidden = true; + + /** + * Handle account links. + */ + [ ...document.querySelectorAll( '.newspack-reader-account-link' ) ].forEach( menuItem => { + menuItem.querySelector( 'a' ).addEventListener( 'click', function ( ev ) { + const reader = readerActivation.getReader(); + /** If logged in, allow page redirection. */ + if ( reader?.authenticated ) { + return; + } + ev.preventDefault(); + if ( readerActivation.hasAuthLink() ) { + authLinkMessage.hidden = false; + } else { + authLinkMessage.hidden = true; + } + form.hidden = false; + form.style.display = 'flex'; + if ( emailInput ) { + emailInput.focus(); + emailInput.value = reader?.email || ''; + } + if ( passwordInput && emailInput.value ) { + passwordInput.focus(); + } + redirectInput.value = ev.target.getAttribute( 'href' ); + } ); + } ); + + /** + * Handle auth form submission. + */ + form.addEventListener( 'submit', function ( ev ) { + ev.preventDefault(); + const body = new FormData( ev.target ); + if ( ! body.has( 'email' ) || ! body.get( 'email' ) ) { + return; + } + submitButton.disabled = true; + fetch( form.getAttribute( 'action' ) || window.location.pathname, { + method: 'POST', + headers: { + Accept: 'application/json', + }, + body, + } ) + .then( res => { + res.json().then( data => { + console.log( data ); + } ); + } ) + .catch( err => { + throw err; + } ) + .finally( () => { + submitButton.disabled = false; + } ); + } ); + } ); +} )( window.newspackReaderActivation ); diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss new file mode 100644 index 0000000000..830760b3e9 --- /dev/null +++ b/assets/reader-activation/auth.scss @@ -0,0 +1,72 @@ +#newspack-reader-activation-auth-form { + display: none; + background: rgba( 0, 0, 0, 0.75 ); + position: fixed; + width: 100% !important; + top: 0; + left: 0; + right: 0; + bottom: 0; + justify-content: center; + align-items: center; + z-index: 99999; + + .form-wrapper { + width: 100%; + max-width: 544px; + background: #fff; + position: relative; + form { + padding: 32px; + input[type='email'], + input[type='password'] { + width: 100%; + } + } + } + + .form-actions { + display: flex; + justify-content: space-between; + flex-direction: row-reverse; + align-items: center; + a { + font-size: 0.8em; + } + } + + .form-close { + align-items: center; + background: white; + border: none; + border-radius: 0; + box-shadow: none; + color: inherit; + cursor: pointer; + display: flex; + font-size: inherit; + height: 36px; + justify-content: center; + margin: 0; + padding: 6px; + position: absolute; + right: 0; + top: 0; + width: 36px; + + svg { + fill: currentColor; + flex: 0 0 24px; + } + + &:active, + &:hover { + opacity: 0.6; + } + + &:focus { + outline: 1px solid; + outline-offset: -1px; + } + } +} diff --git a/assets/reader-activation/index.js b/assets/reader-activation/index.js index f36a4cf16c..c4d765ae10 100644 --- a/assets/reader-activation/index.js +++ b/assets/reader-activation/index.js @@ -38,8 +38,9 @@ function getCookie( name ) { */ function init() { const data = window.newspack_reader_activation_data; - const initialEmail = data?.reader_email || getCookie( data?.auth_intention_cookie ); - store.reader = initialEmail ? { email: initialEmail } : null; + const initialEmail = data?.authenticated_email || getCookie( data?.auth_intention_cookie ); + const authenticated = !! data?.authenticated_email; + store.reader = initialEmail ? { email: initialEmail, authenticated } : null; } init(); @@ -132,7 +133,18 @@ export function getReader() { return store.reader; } -const readerActivation = { on, off, setReader, getReader }; +/** + * Whether the current reader has a valid email link attached to the session. + * + * @return {boolean} Whether the current reader has a valid email link attached to the session. + */ +export function hasAuthLink() { + const reader = getReader(); + const emailLinkSecret = getCookie( 'np_auth_link' ); + return !! ( reader?.email && emailLinkSecret ); +} + +const readerActivation = { on, off, setReader, getReader, hasAuthLink }; window.newspackReaderActivation = readerActivation; export default readerActivation; diff --git a/assets/reader-activation/nav.js b/assets/reader-activation/nav.js deleted file mode 100644 index 68c25e74a4..0000000000 --- a/assets/reader-activation/nav.js +++ /dev/null @@ -1,14 +0,0 @@ -( function ( readerActivation ) { - if ( ! readerActivation ) { - return; - } - [ ...document.querySelectorAll( '.newspack-reader-account-link' ) ].forEach( menuItem => { - menuItem.querySelector( 'a' ).addEventListener( 'click', function ( ev ) { - /** If logged in, allow page redirection. */ - if ( menuItem.classList.contains( 'logged-in' ) ) { - return; - } - ev.preventDefault(); - } ); - } ); -} )( window.newspackReaderActivation ); diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 507876d835..c905c5a9ab 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -22,6 +22,16 @@ final class Reader_Activation { const READER = 'np_reader'; const EMAIL_VERIFIED = 'np_reader_email_verified'; + /** + * Auth form. + */ + const AUTH_FORM_ACTION = 'reader-activation-auth-form'; + const AUTH_FORM_OPTIONS = [ + 'auth', + 'reset-pwd', + 'auth-link', + ]; + /** * Initialize hooks. */ @@ -34,7 +44,9 @@ public static function init() { \add_action( 'resetpass_form', [ __CLASS__, 'set_reader_verified' ] ); \add_action( 'password_reset', [ __CLASS__, 'set_reader_verified' ] ); \add_action( 'auth_cookie_expiration', [ __CLASS__, 'auth_cookie_expiration' ], 10, 3 ); - \add_action( 'wp_nav_menu_items', [ __CLASS__, 'nav_menu_items' ], 20, 2 ); + \add_filter( 'wp_nav_menu_items', [ __CLASS__, 'nav_menu_items' ], 20, 2 ); + \add_action( 'wp_footer', [ __CLASS__, 'render_auth_form' ] ); + \add_action( 'template_redirect', [ __CLASS__, 'process_auth_form' ] ); } } @@ -50,16 +62,16 @@ public static function enqueue_scripts() { NEWSPACK_PLUGIN_VERSION, true ); - $reader_email = ''; + $authenticated_email = ''; if ( \is_user_logged_in() && self::is_user_reader( \wp_get_current_user() ) ) { - $reader_email = \wp_get_current_user()->user_email; + $authenticated_email = \wp_get_current_user()->user_email; } \wp_localize_script( $handle, 'newspack_reader_activation_data', [ 'auth_intention_cookie' => self::AUTH_INTENTION_COOKIE, - 'reader_email' => $reader_email, + 'authenticated_email' => $authenticated_email, ] ); \wp_script_add_data( $handle, 'async', true ); @@ -68,16 +80,22 @@ public static function enqueue_scripts() { /** * Nav menu items script. */ - $nav_handle = 'newspack-reader-activation-nav'; - wp_enqueue_script( - $nav_handle, - Newspack::plugin_url() . '/dist/reader-activation-nav.js', + $auth_handle = 'newspack-reader-activation-auth'; + \wp_enqueue_script( + $auth_handle, + Newspack::plugin_url() . '/dist/reader-activation-auth.js', [ $handle ], NEWSPACK_PLUGIN_VERSION, true ); - \wp_script_add_data( $nav_handle, 'async', true ); - \wp_script_add_data( $nav_handle, 'amp-plus', true ); + \wp_script_add_data( $auth_handle, 'async', true ); + \wp_script_add_data( $auth_handle, 'amp-plus', true ); + \wp_enqueue_style( + $auth_handle, + Newspack::plugin_url() . '/dist/reader-activation-auth.css', + [], + NEWSPACK_PLUGIN_VERSION + ); } /** @@ -247,39 +265,149 @@ public static function auth_cookie_expiration( $length, $user_id, $remember ) { /** * Setup nav menu items for reader account access. * - * @param string $items The HTML list content for the menu items. - * @param stdClass $args An object containing wp_nav_menu() arguments. + * @param string $output The HTML for the menu items. + * @param stdClass $args An object containing wp_nav_menu() arguments. * * @return string The HTML list content for the menu items. */ - public static function nav_menu_items( $items, $args ) { + public static function nav_menu_items( $output, $args ) { /** Do not alter items for authenticated non-readers */ if ( \is_user_logged_in() && ! self::is_user_reader( \wp_get_current_user() ) ) { - return $items; + return $output; + } + + $pre_items = ''; + $after_items = ''; + if ( empty( $output ) ) { + $output = ''; + $pre_items = '
      '; + $after_items = '
    '; } /** - * Menu locations to add the account menu item - * - * TODO: Manage menu locations through Engagement wizard. + * Menu locations to add the account menu items to. */ - $menu_locations = [ 'primary-menu' ]; + $menu_locations = [ 'social' ]; if ( ! in_array( $args->theme_location, $menu_locations, true ) ) { - return $items; + return $output; } $account_url = ''; if ( function_exists( 'wc_get_account_endpoint_url' ) ) { $account_url = \wc_get_account_endpoint_url( 'dashboard' ); } + /** Do not render link for authenticated readers if account page doesn't exist. */ + if ( empty( $account_url ) && is_user_logged_in() ) { + return $output; + } $classnames = [ 'menu-item', 'newspack-reader-account-link' ]; - if ( \is_user_logged_in() ) { - $classnames[] = 'logged-in'; + $item = ''; + $item .= '
  • '; + $item .= '' . \esc_html__( 'My Account', 'newspack' ) . ''; + $item .= '
  • '; + $output = $item . $output; + return $pre_items . $output . $after_items; + } + + /** + * Renders reader authentication form + */ + public static function render_auth_form() { + if ( is_user_logged_in() ) { + return; + } + $element_id = sprintf( 'newspack-%s', self::AUTH_FORM_ACTION ); + ?> + + get_error_message() : __( 'You are authenticated!', 'newspack' ); + } + if ( \wp_is_json_request() ) { + \wp_send_json( compact( 'message', 'data' ), \is_wp_error( $data ) ? 400 : 200 ); + exit; + } elseif ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) { + \wp_safe_redirect( + \add_query_arg( + [ + 'message' => $message, + ] + ) + ); + exit; + } + } + + /** + * Process reader authentication form. + */ + public static function process_auth_form() { + if ( ! isset( $_POST[ self::AUTH_FORM_ACTION ] ) || ! \wp_verify_nonce( \sanitize_text_field( $_POST[ self::AUTH_FORM_ACTION ] ), self::AUTH_FORM_ACTION ) ) { + return; + } + $action = isset( $_POST['action'] ) ? \sanitize_text_field( $_POST['action'] ) : ''; + $email = isset( $_POST['email'] ) ? \sanitize_email( $_POST['email'] ) : ''; + $password = isset( $_POST['password'] ) ? \sanitize_text_field( $_POST['password'] ) : ''; + + if ( ! in_array( $action, self::AUTH_FORM_OPTIONS, true ) ) { + return self::send_auth_form_response( new \WP_Error( 'invalid_action', __( 'Invalid action.', 'newspack' ) ) ); + } + + if ( empty( $email ) ) { + return self::send_auth_form_response( new \WP_Error( 'invalid_email', __( 'You must enter a valid email address.', 'newspack' ) ) ); + } + + switch ( $action ) { + case 'auth': + if ( empty( $password ) ) { + return self::send_auth_form_response( new \WP_Error( 'invalid_password', __( 'You must enter a valid password.', 'newspack' ) ) ); + } + $user = \get_user_by( 'email', $email ); + if ( ! $user || ! self::is_user_reader( $user ) ) { + return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid email or password.', 'newspack' ) ) ); + } + $user = wp_authenticate( $user->user_login, $password ); + if ( is_wp_error( $user ) ) { + return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid email or password.', 'newspack' ) ) ); + } + \wp_set_auth_cookie( $user->ID, true ); + return self::send_auth_form_response(); + case 'auth-link': + $result = self::register_reader( $email ); + return self::send_auth_form_response( [], __( 'We have sent you an authentication link.', 'newspack' ) ); } - $items .= '
  • '; - $items .= '' . \esc_html__( 'My Account', 'newspack' ) . ''; - $items .= '
  • '; - return $items; } /** diff --git a/webpack.config.js b/webpack.config.js index 1a96cc013f..915c05cdff 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -48,7 +48,7 @@ const webpackConfig = getBaseWebpackConfig( ...wizardsScriptFiles, blocks: path.join( __dirname, 'assets', 'blocks', 'index.js' ), 'reader-activation': path.join( __dirname, 'assets', 'reader-activation', 'index.js' ), - 'reader-activation-nav': path.join( __dirname, 'assets', 'reader-activation', 'nav.js' ), + 'reader-activation-auth': path.join( __dirname, 'assets', 'reader-activation', 'auth.js' ), 'reader-registration-block': path.join( __dirname, 'assets', From 86a41b36e8be4807df5e189f8b240c2a308f0e67 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Tue, 5 Jul 2022 19:07:20 -0300 Subject: [PATCH 05/42] feat: handle auth and redirection --- assets/blocks/reader-registration/view.js | 2 +- assets/reader-activation/auth.js | 25 +++++++++++-- assets/reader-activation/auth.scss | 4 ++ assets/reader-activation/index.js | 30 +++++++++++++-- includes/class-reader-activation.php | 45 +++++++++++++++++------ 5 files changed, 86 insertions(+), 20 deletions(-) diff --git a/assets/blocks/reader-registration/view.js b/assets/blocks/reader-registration/view.js index 4be02c4de8..d7cc7620f9 100644 --- a/assets/blocks/reader-registration/view.js +++ b/assets/blocks/reader-registration/view.js @@ -44,7 +44,7 @@ import './style.scss'; if ( res.status === 200 ) { container.replaceChild( messageNode, form ); if ( data?.email ) { - readerActivation.setReader( data.email ); + readerActivation.setReaderEmail( data.email ); } } else { messageContainer.appendChild( messageNode ); diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index 1010e92b65..af46fb6543 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -22,13 +22,16 @@ import './auth.scss'; const redirectInput = form.querySelector( 'input[name="redirect"]' ); const submitButton = form.querySelector( '[type="submit"]' ); + const messageContainer = form.querySelector( '.form-response' ); + const authLinkMessage = form.querySelector( '.auth-link-message' ); authLinkMessage.hidden = true; /** * Handle account links. */ - [ ...document.querySelectorAll( '.newspack-reader-account-link' ) ].forEach( menuItem => { + const accountLinks = [ ...document.querySelectorAll( '.newspack-reader-account-link' ) ]; + accountLinks.forEach( menuItem => { menuItem.querySelector( 'a' ).addEventListener( 'click', function ( ev ) { const reader = readerActivation.getReader(); /** If logged in, allow page redirection. */ @@ -64,6 +67,7 @@ import './auth.scss'; return; } submitButton.disabled = true; + messageContainer.innerHTML = ''; fetch( form.getAttribute( 'action' ) || window.location.pathname, { method: 'POST', headers: { @@ -72,8 +76,23 @@ import './auth.scss'; body, } ) .then( res => { - res.json().then( data => { - console.log( data ); + res.json().then( ( { message, data } ) => { + const messageNode = document.createElement( 'p' ); + messageNode.innerHTML = message; + messageNode.className = `message status-${ res.status }`; + if ( res.status === 200 ) { + if ( body.get( 'redirect' ) ) { + window.location = body.get( 'redirect' ); + } else { + form.hidden = true; + if ( data?.email ) { + readerActivation.setReaderEmail( data.email ); + readerActivation.setReaderAuthenticated(); + } + } + } else { + messageContainer.appendChild( messageNode ); + } } ); } ) .catch( err => { diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss index 830760b3e9..341b0fca81 100644 --- a/assets/reader-activation/auth.scss +++ b/assets/reader-activation/auth.scss @@ -11,6 +11,10 @@ align-items: center; z-index: 99999; + &.visible { + display: flex; + } + .form-wrapper { width: 100%; max-width: 544px; diff --git a/assets/reader-activation/index.js b/assets/reader-activation/index.js index c4d765ae10..6a1ae14ba0 100644 --- a/assets/reader-activation/index.js +++ b/assets/reader-activation/index.js @@ -115,12 +115,27 @@ export function off( event, callback ) { * * @param {string} email Email. */ -export function setReader( email ) { - store.reader = null; +export function setReaderEmail( email ) { if ( ! email ) { return; } - store.reader = { email }; + if ( ! store.reader ) { + store.reader = {}; + } + store.reader.email = email; + emit( EVENTS.reader, store.reader ); +} + +/** + * Set whether the current reader is authenticated. + * + * @param {boolean} authenticated Whether the current reader is authenticated. Default is true. + */ +export function setReaderAuthenticated( authenticated = true ) { + if ( ! store.reader?.email ) { + throw 'Reader email not set'; + } + store.reader.authenticated = !! authenticated; emit( EVENTS.reader, store.reader ); } @@ -144,7 +159,14 @@ export function hasAuthLink() { return !! ( reader?.email && emailLinkSecret ); } -const readerActivation = { on, off, setReader, getReader, hasAuthLink }; +const readerActivation = { + on, + off, + setReaderEmail, + setReaderAuthenticated, + getReader, + hasAuthLink, +}; window.newspackReaderActivation = readerActivation; export default readerActivation; diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index c905c5a9ab..c08a141603 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -28,8 +28,8 @@ final class Reader_Activation { const AUTH_FORM_ACTION = 'reader-activation-auth-form'; const AUTH_FORM_OPTIONS = [ 'auth', - 'reset-pwd', 'auth-link', + 'reset-pwd', ]; /** @@ -303,7 +303,7 @@ public static function nav_menu_items( $output, $args ) { $classnames = [ 'menu-item', 'newspack-reader-account-link' ]; $item = ''; $item .= '
  • '; - $item .= '' . \esc_html__( 'My Account', 'newspack' ) . ''; + $item .= '' . \esc_html__( 'My Account', 'newspack' ) . ''; $item .= '
  • '; $output = $item . $output; return $pre_items . $output . $after_items; @@ -317,8 +317,16 @@ public static function render_auth_form() { return; } $element_id = sprintf( 'newspack-%s', self::AUTH_FORM_ACTION ); + $message = ''; + $classnames = []; + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['reader_authenticated'] ) && isset( $_GET['message'] ) ) { + $message = \sanitize_text_field( $_GET['message'] ); + $classnames[] = 'visible'; + } + // phpcs:enable ?> - @@ -347,10 +360,11 @@ public static function render_auth_form() { /** * Send the auth form response to the client, whether it's a JSON or POST request. * - * @param array|WP_Error $data The response to send to the client. - * @param string $message Optional custom message. + * @param array|WP_Error $data The response to send to the client. + * @param string $message Optional custom message. + * @param string $redirect_url Optional custom redirect URL. */ - private static function send_auth_form_response( $data = [], $message = '' ) { + private static function send_auth_form_response( $data = [], $message = false, $redirect_url = false ) { $is_error = \is_wp_error( $data ); if ( empty( $message ) ) { $message = $is_error ? $data->get_error_message() : __( 'You are authenticated!', 'newspack' ); @@ -362,8 +376,10 @@ private static function send_auth_form_response( $data = [], $message = '' ) { \wp_safe_redirect( \add_query_arg( [ - 'message' => $message, - ] + 'reader_authenticated' => $is_error ? '0' : '1', + 'message' => $message, + ], + $redirect_url ) ); exit; @@ -380,9 +396,14 @@ public static function process_auth_form() { $action = isset( $_POST['action'] ) ? \sanitize_text_field( $_POST['action'] ) : ''; $email = isset( $_POST['email'] ) ? \sanitize_email( $_POST['email'] ) : ''; $password = isset( $_POST['password'] ) ? \sanitize_text_field( $_POST['password'] ) : ''; + $redirect = isset( $_POST['redirect'] ) ? \esc_url_raw( $_POST['redirect'] ) : ''; if ( ! in_array( $action, self::AUTH_FORM_OPTIONS, true ) ) { - return self::send_auth_form_response( new \WP_Error( 'invalid_action', __( 'Invalid action.', 'newspack' ) ) ); + return self::send_auth_form_response( new \WP_Error( 'invalid_request', __( 'Invalid request.', 'newspack' ) ) ); + } + + if ( $redirect && false === strpos( $redirect, home_url(), 0 ) ) { + return self::send_auth_form_response( new \WP_Error( 'invalid_request', __( 'Invalid request.', 'newspack' ) ) ); } if ( empty( $email ) ) { @@ -398,12 +419,12 @@ public static function process_auth_form() { if ( ! $user || ! self::is_user_reader( $user ) ) { return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid email or password.', 'newspack' ) ) ); } - $user = wp_authenticate( $user->user_login, $password ); - if ( is_wp_error( $user ) ) { + $user = \wp_authenticate( $user->user_login, $password ); + if ( \is_wp_error( $user ) ) { return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid email or password.', 'newspack' ) ) ); } \wp_set_auth_cookie( $user->ID, true ); - return self::send_auth_form_response(); + return self::send_auth_form_response( [ 'email' => $email ], false, $redirect ); case 'auth-link': $result = self::register_reader( $email ); return self::send_auth_form_response( [], __( 'We have sent you an authentication link.', 'newspack' ) ); From 9df82aef57631aa73052d320090dd80cba3eb685 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Wed, 6 Jul 2022 14:29:54 -0300 Subject: [PATCH 06/42] fix: use onload callback --- assets/reader-activation/auth.js | 11 +++-------- assets/reader-activation/index.js | 4 ++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index af46fb6543..3801870ba1 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -1,8 +1,3 @@ -/** - * WordPress dependencies. - */ -import domReady from '@wordpress/dom-ready'; - /** * Internal dependencies. */ @@ -12,7 +7,7 @@ import './auth.scss'; if ( ! readerActivation ) { return; } - domReady( function () { + window.onload = function () { const form = document.querySelector( '#newspack-reader-activation-auth-form' ); if ( ! form ) { return; @@ -87,7 +82,7 @@ import './auth.scss'; form.hidden = true; if ( data?.email ) { readerActivation.setReaderEmail( data.email ); - readerActivation.setReaderAuthenticated(); + readerActivation.setAuthenticated(); } } } else { @@ -102,5 +97,5 @@ import './auth.scss'; submitButton.disabled = false; } ); } ); - } ); + }; } )( window.newspackReaderActivation ); diff --git a/assets/reader-activation/index.js b/assets/reader-activation/index.js index 6a1ae14ba0..0443f33fd7 100644 --- a/assets/reader-activation/index.js +++ b/assets/reader-activation/index.js @@ -131,7 +131,7 @@ export function setReaderEmail( email ) { * * @param {boolean} authenticated Whether the current reader is authenticated. Default is true. */ -export function setReaderAuthenticated( authenticated = true ) { +export function setAuthenticated( authenticated = true ) { if ( ! store.reader?.email ) { throw 'Reader email not set'; } @@ -163,7 +163,7 @@ const readerActivation = { on, off, setReaderEmail, - setReaderAuthenticated, + setAuthenticated, getReader, hasAuthLink, }; From 1a1e24015cd68d889d785d73195e09737973b1c2 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 7 Jul 2022 15:04:49 -0300 Subject: [PATCH 07/42] fix: temp workaround amp; style updates --- assets/reader-activation/auth.js | 22 ++++++++++++++++++---- assets/reader-activation/auth.scss | 12 +++++++++++- includes/class-reader-activation.php | 16 ++++++++++------ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index 3801870ba1..ed39a5f371 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -8,10 +8,22 @@ import './auth.scss'; return; } window.onload = function () { - const form = document.querySelector( '#newspack-reader-activation-auth-form' ); - if ( ! form ) { + const container = document.querySelector( '#newspack-reader-activation-auth-form' ); + if ( ! container ) { return; } + + const initialForm = container.querySelector( 'form' ); + let form; + /** Temporary way around AMP's enforced XHR strategy. */ + if ( initialForm.getAttribute( 'action-xhr' ) ) { + initialForm.removeAttribute( 'action-xhr' ); + form = initialForm.cloneNode( true ); + initialForm.replaceWith( form ); + } else { + form = initialForm; + } + const emailInput = form.querySelector( 'input[name="email"]' ); const passwordInput = form.querySelector( 'input[name="password"]' ); const redirectInput = form.querySelector( 'input[name="redirect"]' ); @@ -39,8 +51,8 @@ import './auth.scss'; } else { authLinkMessage.hidden = true; } - form.hidden = false; - form.style.display = 'flex'; + container.hidden = false; + container.style.display = 'flex'; if ( emailInput ) { emailInput.focus(); emailInput.value = reader?.email || ''; @@ -63,6 +75,7 @@ import './auth.scss'; } submitButton.disabled = true; messageContainer.innerHTML = ''; + form.style.opacity = 0.5; fetch( form.getAttribute( 'action' ) || window.location.pathname, { method: 'POST', headers: { @@ -94,6 +107,7 @@ import './auth.scss'; throw err; } ) .finally( () => { + form.style.opacity = 1; submitButton.disabled = false; } ); } ); diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss index 341b0fca81..e02afd4ac4 100644 --- a/assets/reader-activation/auth.scss +++ b/assets/reader-activation/auth.scss @@ -21,7 +21,11 @@ background: #fff; position: relative; form { - padding: 32px; + padding: 64px; + transition: opacity 0.2s ease-in-out; + p { + margin: 0.5rem 0; + } input[type='email'], input[type='password'] { width: 100%; @@ -39,6 +43,12 @@ } } + .form-response { + font-size: 0.8em; + position: absolute; + bottom: 32px; + } + .form-close { align-items: center; background: white; diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index c08a141603..0ed3a5f344 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -313,7 +313,7 @@ public static function nav_menu_items( $output, $args ) { * Renders reader authentication form */ public static function render_auth_form() { - if ( is_user_logged_in() ) { + if ( \is_user_logged_in() ) { return; } $element_id = sprintf( 'newspack-%s', self::AUTH_FORM_ACTION ); @@ -333,17 +333,21 @@ public static function render_auth_form() { -
    + + +

    +

    -

    -
    - -

    +
    +

    +
    +
    +

    From afbb4af51f47d5fd3db7263b52680e8dd52c2c8c Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 7 Jul 2022 15:22:53 -0300 Subject: [PATCH 08/42] feat: handle pwd and link actions --- assets/reader-activation/auth.js | 23 +++++++++++++++++++ assets/reader-activation/auth.scss | 8 +++---- includes/class-reader-activation.php | 33 ++++++++++++++++++---------- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index ed39a5f371..4a86ac7485 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -24,6 +24,7 @@ import './auth.scss'; form = initialForm; } + const actionInput = form.querySelector( 'input[name="action"]' ); const emailInput = form.querySelector( 'input[name="email"]' ); const passwordInput = form.querySelector( 'input[name="password"]' ); const redirectInput = form.querySelector( 'input[name="redirect"]' ); @@ -64,6 +65,28 @@ import './auth.scss'; } ); } ); + /** + * Handle auth form action selection. + */ + function setFormAction( action ) { + actionInput.value = action; + form.querySelectorAll( '.action-item ' ).forEach( item => { + item.hidden = true; + } ); + form.querySelectorAll( '.action-' + action ).forEach( item => { + item.hidden = false; + } ); + } + + setFormAction( actionInput.value ); + + form.querySelectorAll( '[data-set-action]' ).forEach( item => { + item.addEventListener( 'click', function ( ev ) { + ev.preventDefault(); + setFormAction( ev.target.getAttribute( 'data-set-action' ) ); + } ); + } ); + /** * Handle auth form submission. */ diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss index e02afd4ac4..ec16a4c383 100644 --- a/assets/reader-activation/auth.scss +++ b/assets/reader-activation/auth.scss @@ -34,10 +34,10 @@ } .form-actions { - display: flex; - justify-content: space-between; - flex-direction: row-reverse; - align-items: center; + button { + display: block; + width: 100%; + } a { font-size: 0.8em; } diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 0ed3a5f344..c7ca9d30c0 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -27,9 +27,8 @@ final class Reader_Activation { */ const AUTH_FORM_ACTION = 'reader-activation-auth-form'; const AUTH_FORM_OPTIONS = [ - 'auth', - 'auth-link', - 'reset-pwd', + 'pwd', + 'link', ]; /** @@ -334,21 +333,25 @@ public static function render_auth_form() { - +

    -

    - +

    -
    +

    -
    +

    - + +
    +
    @@ -414,8 +417,13 @@ public static function process_auth_form() { return self::send_auth_form_response( new \WP_Error( 'invalid_email', __( 'You must enter a valid email address.', 'newspack' ) ) ); } + $user = \get_user_by( 'email', $email ); + if ( ! $user || ! self::is_user_reader( $user ) ) { + return self::send_auth_form_response( new \WP_Error( 'invalid_account', __( 'Invalid account.', 'newspack' ) ) ); + } + switch ( $action ) { - case 'auth': + case 'pwd': if ( empty( $password ) ) { return self::send_auth_form_response( new \WP_Error( 'invalid_password', __( 'You must enter a valid password.', 'newspack' ) ) ); } @@ -429,8 +437,9 @@ public static function process_auth_form() { } \wp_set_auth_cookie( $user->ID, true ); return self::send_auth_form_response( [ 'email' => $email ], false, $redirect ); - case 'auth-link': - $result = self::register_reader( $email ); + case 'link': + self::set_auth_intention_cookie( $email ); + Magic_Link::send_email( $user ); return self::send_auth_form_response( [], __( 'We have sent you an authentication link.', 'newspack' ) ); } } From cdd17115287cf8401c8a6119617c252992e6bf90 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 7 Jul 2022 15:39:20 -0300 Subject: [PATCH 09/42] fix: ui tweaks --- assets/reader-activation/auth.js | 29 +++++++++++++++++++--------- assets/reader-activation/index.js | 25 ++++++++++++------------ includes/class-reader-activation.php | 4 ++-- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index 4a86ac7485..4929f02136 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -35,6 +35,13 @@ import './auth.scss'; const authLinkMessage = form.querySelector( '.auth-link-message' ); authLinkMessage.hidden = true; + /** + * Handle reader changes. + */ + readerActivation.on( 'reader', ( { email } ) => { + emailInput.value = email || ''; + } ); + /** * Handle account links. */ @@ -42,7 +49,7 @@ import './auth.scss'; accountLinks.forEach( menuItem => { menuItem.querySelector( 'a' ).addEventListener( 'click', function ( ev ) { const reader = readerActivation.getReader(); - /** If logged in, allow page redirection. */ + /** If logged in, bail and allow page redirection. */ if ( reader?.authenticated ) { return; } @@ -52,16 +59,15 @@ import './auth.scss'; } else { authLinkMessage.hidden = true; } - container.hidden = false; - container.style.display = 'flex'; - if ( emailInput ) { - emailInput.focus(); - emailInput.value = reader?.email || ''; - } - if ( passwordInput && emailInput.value ) { + emailInput.value = reader?.email || ''; + if ( emailInput.value ) { passwordInput.focus(); + } else { + emailInput.focus(); } redirectInput.value = ev.target.getAttribute( 'href' ); + container.hidden = false; + container.style.display = 'flex'; } ); } ); @@ -77,13 +83,18 @@ import './auth.scss'; item.hidden = false; } ); } - setFormAction( actionInput.value ); form.querySelectorAll( '[data-set-action]' ).forEach( item => { item.addEventListener( 'click', function ( ev ) { ev.preventDefault(); + const action = ev.target.getAttribute( 'data-set-action' ); setFormAction( ev.target.getAttribute( 'data-set-action' ) ); + if ( action === 'pwd' && emailInput.value ) { + passwordInput.focus(); + } else { + emailInput.focus(); + } } ); } ); diff --git a/assets/reader-activation/index.js b/assets/reader-activation/index.js index 0443f33fd7..6812e69e99 100644 --- a/assets/reader-activation/index.js +++ b/assets/reader-activation/index.js @@ -33,18 +33,6 @@ function getCookie( name ) { if ( parts.length === 2 ) return decodeURIComponent( parts.pop().split( ';' ).shift() ); } -/** - * Initialize store data. - */ -function init() { - const data = window.newspack_reader_activation_data; - const initialEmail = data?.authenticated_email || getCookie( data?.auth_intention_cookie ); - const authenticated = !! data?.authenticated_email; - store.reader = initialEmail ? { email: initialEmail, authenticated } : null; -} - -init(); - /** * Handling events. */ @@ -159,6 +147,19 @@ export function hasAuthLink() { return !! ( reader?.email && emailLinkSecret ); } +/** + * Initialize store data. + */ +function init() { + const data = window.newspack_reader_activation_data; + const initialEmail = data?.authenticated_email || getCookie( data?.auth_intention_cookie ); + const authenticated = !! data?.authenticated_email; + store.reader = initialEmail ? { email: initialEmail, authenticated } : null; + emit( EVENTS.reader, store.reader ); +} + +init(); + const readerActivation = { on, off, diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index c7ca9d30c0..61bcdfd1f7 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -335,8 +335,8 @@ public static function render_auth_form() {

    -

    From 19b63e0656c47c68371b2f0fa7d1f5d973e731e6 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 7 Jul 2022 15:40:09 -0300 Subject: [PATCH 10/42] fix: pharsing --- includes/class-reader-activation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 61bcdfd1f7..7ec55025ec 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -347,7 +347,7 @@ public static function render_auth_form() {

    - +
    $email, + 'authenticated' => 0, + ]; switch ( $action ) { case 'pwd': if ( empty( $password ) ) { return self::send_auth_form_response( new \WP_Error( 'invalid_password', __( 'You must enter a valid password.', 'newspack' ) ) ); } - $user = \get_user_by( 'email', $email ); - if ( ! $user || ! self::is_user_reader( $user ) ) { - return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid email or password.', 'newspack' ) ) ); - } $user = \wp_authenticate( $user->user_login, $password ); if ( \is_wp_error( $user ) ) { - return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid email or password.', 'newspack' ) ) ); + return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid credentials.', 'newspack' ) ) ); } - \wp_set_auth_cookie( $user->ID, true ); - return self::send_auth_form_response( [ 'email' => $email ], false, $redirect ); + $authenticated = self::set_current_reader( $user->ID ); + $payload['authenticated'] = \is_wp_error( $authenticated ) ? 0 : 1; + return self::send_auth_form_response( $payload, false, $redirect ); case 'link': - self::set_auth_intention_cookie( $email ); - Magic_Link::send_email( $user ); - return self::send_auth_form_response( [], __( 'We have sent you an authentication link.', 'newspack' ) ); + $sent = Magic_Link::send_email( $user ); + if ( true !== $sent ) { + return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid account.', 'newspack' ) ) ); + } + return self::send_auth_form_response( $payload, __( 'Check your email for an authentication link!', 'newspack' ), $redirect ); } } From 286b9593c541cb5ed3951662f2e76bae7ac9c75c Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 7 Jul 2022 16:11:28 -0300 Subject: [PATCH 13/42] fix: ui tweaks --- assets/reader-activation/auth.js | 10 ++++------ includes/class-reader-activation.php | 24 +++++++++++++----------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index 68fcdb2f31..0848fee929 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -45,8 +45,7 @@ import './auth.scss'; /** * Handle account links. */ - const accountLinks = [ ...document.querySelectorAll( '.newspack-reader-account-link' ) ]; - accountLinks.forEach( menuItem => { + document.querySelectorAll( '.newspack-reader-account-link' ).forEach( menuItem => { menuItem.querySelector( 'a' ).addEventListener( 'click', function ( ev ) { const reader = readerActivation.getReader(); /** If logged in, bail and allow page redirection. */ @@ -76,16 +75,15 @@ import './auth.scss'; */ function setFormAction( action ) { actionInput.value = action; - form.querySelectorAll( '.action-item ' ).forEach( item => { + container.querySelectorAll( '.action-item' ).forEach( item => { item.hidden = true; } ); - form.querySelectorAll( '.action-' + action ).forEach( item => { + container.querySelectorAll( '.action-' + action ).forEach( item => { item.hidden = false; } ); } setFormAction( actionInput.value ); - - form.querySelectorAll( '[data-set-action]' ).forEach( item => { + container.querySelectorAll( '[data-set-action]' ).forEach( item => { item.addEventListener( 'click', function ( ev ) { ev.preventDefault(); const action = ev.target.getAttribute( 'data-set-action' ); diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index e3680c5c7e..bc1f6be60a 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -335,10 +335,12 @@ public static function render_auth_form() {

    - -

    +

    + +

    @@ -349,16 +351,16 @@ public static function render_auth_form() {

    - -
    - -

    - -
    + +
    + +

    + +
    From 854f97f11fdc36ab0eed1a612ff54eb69e3d1338 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 7 Jul 2022 17:17:30 -0300 Subject: [PATCH 14/42] fix: allow native post form --- assets/reader-activation/auth.js | 2 +- includes/class-reader-activation.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index 0848fee929..842b6ea086 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -15,7 +15,7 @@ import './auth.scss'; const initialForm = container.querySelector( 'form' ); let form; - /** Temporary way around AMP's enforced XHR strategy. */ + /** Workaround AMP's enforced XHR strategy. */ if ( initialForm.getAttribute( 'action-xhr' ) ) { initialForm.removeAttribute( 'action-xhr' ); form = initialForm.cloneNode( true ); diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index bc1f6be60a..1812a8c800 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -46,6 +46,7 @@ public static function init() { \add_filter( 'wp_nav_menu_items', [ __CLASS__, 'nav_menu_items' ], 20, 2 ); \add_action( 'wp_footer', [ __CLASS__, 'render_auth_form' ] ); \add_action( 'template_redirect', [ __CLASS__, 'process_auth_form' ] ); + \add_filter( 'amp_native_post_form_allowed', '__return_true' ); } } @@ -335,7 +336,7 @@ public static function render_auth_form() {

    -

    From b9a8cce5091310e7cb777388257abcd268f61322 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 7 Jul 2022 17:38:20 -0300 Subject: [PATCH 15/42] fix: ui tweaks --- assets/reader-activation/auth.js | 7 +++--- assets/reader-activation/auth.scss | 32 +++++++++++++++++++++++++--- includes/class-reader-activation.php | 21 +++++++++--------- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index 842b6ea086..bdd963cdc7 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -30,15 +30,15 @@ import './auth.scss'; const redirectInput = form.querySelector( 'input[name="redirect"]' ); const submitButton = form.querySelector( '[type="submit"]' ); - const messageContainer = form.querySelector( '.form-response' ); + const messageContainer = container.querySelector( '.form-response' ); - const authLinkMessage = form.querySelector( '.auth-link-message' ); + const authLinkMessage = container.querySelector( '.auth-link-message' ); authLinkMessage.hidden = true; /** * Handle reader changes. */ - readerActivation.on( 'reader', ( { email } ) => { + readerActivation.on( 'reader', ( { detail: { email } } ) => { emailInput.value = email || ''; } ); @@ -108,6 +108,7 @@ import './auth.scss'; submitButton.disabled = true; messageContainer.innerHTML = ''; form.style.opacity = 0.5; + readerActivation.setReaderEmail( body.get( 'email' ) ); fetch( form.getAttribute( 'action' ) || window.location.pathname, { method: 'POST', headers: { diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss index 4f104053ad..07366eb67d 100644 --- a/assets/reader-activation/auth.scss +++ b/assets/reader-activation/auth.scss @@ -9,7 +9,12 @@ bottom: 0; justify-content: center; align-items: center; - z-index: 99999; + z-index: 999999; + font-size: 0.9em; + + @media screen and ( max-width: 744px ) { + align-items: flex-end; + } &.visible { display: flex; @@ -23,6 +28,9 @@ .form-content { padding: 64px; transition: opacity 0.2s ease-in-out; + @media screen and ( max-width: 744px ) { + padding: 5vw; + } p { margin: 0.5rem 0; } @@ -39,14 +47,32 @@ width: 100%; } a { - font-size: 0.8em; + font-size: 0.9em; } } .form-response { font-size: 0.8em; position: absolute; - bottom: 16px; + top: 16px; + left: 64px; + right: 64px; + text-align: center; + @media screen and ( max-width: 744px ) { + left: 32px; + right: 32px; + } + .message { + display: block; + width: 100%; + margin: 0; + padding: 0.25rem; + border: 1px solid #999; + &.status-400 { + color: rgb( 169, 45, 45 ); + border: 1px solid rgb( 169, 45, 45 ); + } + } } .form-close { diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 1812a8c800..25be861427 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -352,16 +352,16 @@ public static function render_auth_form() {

    + +
    + +

    + +
    - -
    - -

    - -
    @@ -421,11 +421,12 @@ public static function process_auth_form() { return self::send_auth_form_response( new \WP_Error( 'invalid_email', __( 'You must enter a valid email address.', 'newspack' ) ) ); } + self::set_auth_intention_cookie( $email ); + $user = \get_user_by( 'email', $email ); if ( ! $user || ! self::is_user_reader( $user ) ) { return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid account.', 'newspack' ) ) ); } - self::set_auth_intention_cookie( $email ); $payload = [ 'email' => $email, From 89047bb747de73af620aa4288562c94eddadf251 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 7 Jul 2022 17:45:45 -0300 Subject: [PATCH 16/42] fix: visibility and focus trigger --- assets/reader-activation/auth.js | 49 ++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index bdd963cdc7..5a1ca9080c 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -45,29 +45,34 @@ import './auth.scss'; /** * Handle account links. */ + function handleAccountLinkClick( ev ) { + const reader = readerActivation.getReader(); + /** If logged in, bail and allow page redirection. */ + if ( reader?.authenticated ) { + return; + } + ev.preventDefault(); + + if ( readerActivation.hasAuthLink() ) { + authLinkMessage.hidden = false; + } else { + authLinkMessage.hidden = true; + } + + emailInput.value = reader?.email || ''; + redirectInput.value = ev.target.getAttribute( 'href' ); + + container.hidden = false; + container.style.display = 'flex'; + + if ( emailInput.value ) { + passwordInput.focus(); + } else { + emailInput.focus(); + } + } document.querySelectorAll( '.newspack-reader-account-link' ).forEach( menuItem => { - menuItem.querySelector( 'a' ).addEventListener( 'click', function ( ev ) { - const reader = readerActivation.getReader(); - /** If logged in, bail and allow page redirection. */ - if ( reader?.authenticated ) { - return; - } - ev.preventDefault(); - if ( readerActivation.hasAuthLink() ) { - authLinkMessage.hidden = false; - } else { - authLinkMessage.hidden = true; - } - emailInput.value = reader?.email || ''; - if ( emailInput.value ) { - passwordInput.focus(); - } else { - emailInput.focus(); - } - redirectInput.value = ev.target.getAttribute( 'href' ); - container.hidden = false; - container.style.display = 'flex'; - } ); + menuItem.querySelector( 'a' ).addEventListener( 'click', handleAccountLinkClick ); } ); /** From 5a61daa76ad23ad3af30ecc1853d98c0996aab8d Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Thu, 7 Jul 2022 17:52:06 -0300 Subject: [PATCH 17/42] fix: mobile tweaks --- assets/reader-activation/auth.scss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss index 07366eb67d..f5f285fd83 100644 --- a/assets/reader-activation/auth.scss +++ b/assets/reader-activation/auth.scss @@ -58,11 +58,14 @@ left: 64px; right: 64px; text-align: center; + background: #fff; @media screen and ( max-width: 744px ) { - left: 32px; - right: 32px; + position: fixed; + top: 5vw; + left: 5vw; + right: 5vw; } - .message { + p.message { display: block; width: 100%; margin: 0; From 460e7d5792ae83e1b9b33883548cd24d307c2bc7 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Mon, 11 Jul 2022 14:30:33 -0300 Subject: [PATCH 18/42] fix: css tweaks --- assets/reader-activation/auth.scss | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss index f5f285fd83..d3debad9c9 100644 --- a/assets/reader-activation/auth.scss +++ b/assets/reader-activation/auth.scss @@ -27,9 +27,11 @@ position: relative; .form-content { padding: 64px; - transition: opacity 0.2s ease-in-out; @media screen and ( max-width: 744px ) { - padding: 5vw; + padding: 0 5vw 5vw 5vw; + } + form { + transition: opacity 0.2s ease-in-out; } p { margin: 0.5rem 0; @@ -42,12 +44,18 @@ } .form-actions { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + p { + flex: 1 1 100%; + } button { display: block; width: 100%; } a { - font-size: 0.9em; + font-size: 0.8em; } } @@ -57,9 +65,9 @@ top: 16px; left: 64px; right: 64px; - text-align: center; background: #fff; @media screen and ( max-width: 744px ) { + text-align: center; position: fixed; top: 5vw; left: 5vw; @@ -69,12 +77,7 @@ display: block; width: 100%; margin: 0; - padding: 0.25rem; - border: 1px solid #999; - &.status-400 { - color: rgb( 169, 45, 45 ); - border: 1px solid rgb( 169, 45, 45 ); - } + padding: 0.25rem 0; } } From c1c5a2bb2ba44078f29563fe3259a34bd10eaa49 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Mon, 11 Jul 2022 14:31:33 -0300 Subject: [PATCH 19/42] fix: use tertiary menu and custom labels --- assets/reader-activation/auth.js | 19 ++++-- includes/class-reader-activation.php | 94 +++++++++++++++++++++------- 2 files changed, 88 insertions(+), 25 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index 5a1ca9080c..4334b2736c 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -27,7 +27,6 @@ import './auth.scss'; const actionInput = form.querySelector( 'input[name="action"]' ); const emailInput = form.querySelector( 'input[name="email"]' ); const passwordInput = form.querySelector( 'input[name="password"]' ); - const redirectInput = form.querySelector( 'input[name="redirect"]' ); const submitButton = form.querySelector( '[type="submit"]' ); const messageContainer = container.querySelector( '.form-response' ); @@ -35,11 +34,23 @@ import './auth.scss'; const authLinkMessage = container.querySelector( '.auth-link-message' ); authLinkMessage.hidden = true; + const accountLinks = document.querySelectorAll( '.newspack-reader-account-link' ); + /** * Handle reader changes. */ - readerActivation.on( 'reader', ( { detail: { email } } ) => { + readerActivation.on( 'reader', ( { detail: { email, authenticated } } ) => { emailInput.value = email || ''; + if ( accountLinks?.length ) { + accountLinks.forEach( link => { + try { + const labels = JSON.parse( link.getAttribute( 'data-labels' ) ); + link.querySelector( 'a' ).innerHTML = authenticated + ? labels.signedin + : labels.signedout; + } catch {} + } ); + } } ); /** @@ -60,7 +71,6 @@ import './auth.scss'; } emailInput.value = reader?.email || ''; - redirectInput.value = ev.target.getAttribute( 'href' ); container.hidden = false; container.style.display = 'flex'; @@ -71,7 +81,7 @@ import './auth.scss'; emailInput.focus(); } } - document.querySelectorAll( '.newspack-reader-account-link' ).forEach( menuItem => { + accountLinks.forEach( menuItem => { menuItem.querySelector( 'a' ).addEventListener( 'click', handleAccountLinkClick ); } ); @@ -80,6 +90,7 @@ import './auth.scss'; */ function setFormAction( action ) { actionInput.value = action; + messageContainer.innerHTML = ''; container.querySelectorAll( '.action-item' ).forEach( item => { item.hidden = true; } ); diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 25be861427..c1033f33e8 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -43,7 +43,7 @@ public static function init() { \add_action( 'resetpass_form', [ __CLASS__, 'set_reader_verified' ] ); \add_action( 'password_reset', [ __CLASS__, 'set_reader_verified' ] ); \add_action( 'auth_cookie_expiration', [ __CLASS__, 'auth_cookie_expiration' ], 10, 3 ); - \add_filter( 'wp_nav_menu_items', [ __CLASS__, 'nav_menu_items' ], 20, 2 ); + \add_action( 'init', [ __CLASS__, 'setup_nav_menu' ] ); \add_action( 'wp_footer', [ __CLASS__, 'render_auth_form' ] ); \add_action( 'template_redirect', [ __CLASS__, 'process_auth_form' ] ); \add_filter( 'amp_native_post_form_allowed', '__return_true' ); @@ -261,6 +261,41 @@ public static function auth_cookie_expiration( $length, $user_id, $remember ) { return $length; } + /** + * Setup nav menu hooks. + */ + public static function setup_nav_menu() { + /** Always have location enabled for account link. */ + \add_filter( + 'has_nav_menu', + function( $has_nav_menu, $location ) { + if ( 'tertiary-menu' === $location ) { + $has_nav_menu = true; + } + return $has_nav_menu; + }, + 10, + 2 + ); + + /** Fallback location to always print nav menu args */ + $self = new self(); + \add_filter( + 'wp_nav_menu_args', + function( $args ) use ( $self ) { + if ( 'tertiary-menu' === $args['theme_location'] ) { + $args['fallback_cb'] = function( $args ) use ( $self ) { + $self->nav_menu_items( '', $args, true ); + }; + } + return $args; + } + ); + + /** Add as menu item */ + \add_filter( 'wp_nav_menu_items', [ __CLASS__, 'nav_menu_items' ], 20, 2 ); + } + /** * Setup nav menu items for reader account access. * @@ -269,43 +304,52 @@ public static function auth_cookie_expiration( $length, $user_id, $remember ) { * * @return string The HTML list content for the menu items. */ - public static function nav_menu_items( $output, $args ) { + public static function nav_menu_items( $output, $args = [], $echo = false ) { + $args = (object) $args; /** Do not alter items for authenticated non-readers */ if ( \is_user_logged_in() && ! self::is_user_reader( \wp_get_current_user() ) ) { return $output; } - $pre_items = ''; - $after_items = ''; - if ( empty( $output ) ) { - $output = ''; - $pre_items = '
      '; - $after_items = '
    '; - } - /** * Menu locations to add the account menu items to. */ - $menu_locations = [ 'social' ]; - if ( ! in_array( $args->theme_location, $menu_locations, true ) ) { + $locations = [ 'tertiary-menu' ]; + if ( ! in_array( $args->theme_location, $locations, true ) ) { return $output; } + $account_url = ''; if ( function_exists( 'wc_get_account_endpoint_url' ) ) { $account_url = \wc_get_account_endpoint_url( 'dashboard' ); } /** Do not render link for authenticated readers if account page doesn't exist. */ - if ( empty( $account_url ) && is_user_logged_in() ) { + if ( empty( $account_url ) && \is_user_logged_in() ) { return $output; } + + $labels = [ + 'signedin' => \__( 'Account', 'newspack' ), + 'signedout' => \__( 'Sign In', 'newspack' ), + ]; + $label = \is_user_logged_in() ? 'signedin' : 'signedout'; $classnames = [ 'menu-item', 'newspack-reader-account-link' ]; $item = ''; - $item .= '
  • '; - $item .= '' . \esc_html__( 'My Account', 'newspack' ) . ''; + $item .= '
  • '; + $item .= '' . \esc_html( $labels[ $label ] ) . ''; $item .= '
  • '; - $output = $item . $output; - return $pre_items . $output . $after_items; + + if ( empty( $output ) ) { + $output = sprintf( $args->items_wrap ?? '
      %3$s
    ', $args->menu_id, $args->menu_class, $item ); + } else { + $output = $output . $item; + } + if ( $echo ) { + echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } else { + return $output; + } } /** @@ -334,6 +378,7 @@ public static function render_auth_form() {
    +

    -

    @@ -350,11 +394,12 @@ public static function render_auth_form() {

    - + +
    @@ -401,13 +446,20 @@ private static function send_auth_form_response( $data = [], $message = false, $ * Process reader authentication form. */ public static function process_auth_form() { - if ( ! isset( $_POST[ self::AUTH_FORM_ACTION ] ) || ! \wp_verify_nonce( \sanitize_text_field( $_POST[ self::AUTH_FORM_ACTION ] ), self::AUTH_FORM_ACTION ) ) { + if ( \is_user_logged_in() ) { + return; + } + + // phpcs:disable WordPress.Security.NonceVerification.Missing + // Nonce not required for an authentication attempt. + if ( ! isset( $_POST[ self::AUTH_FORM_ACTION ] ) ) { return; } $action = isset( $_POST['action'] ) ? \sanitize_text_field( $_POST['action'] ) : ''; $email = isset( $_POST['email'] ) ? \sanitize_email( $_POST['email'] ) : ''; $password = isset( $_POST['password'] ) ? \sanitize_text_field( $_POST['password'] ) : ''; $redirect = isset( $_POST['redirect'] ) ? \esc_url_raw( $_POST['redirect'] ) : ''; + // phpcs:enable if ( ! in_array( $action, self::AUTH_FORM_OPTIONS, true ) ) { return self::send_auth_form_response( new \WP_Error( 'invalid_request', __( 'Invalid request.', 'newspack' ) ) ); From 8c898be275f83e9b72c6e944037c539ac673cfdf Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Mon, 11 Jul 2022 15:10:04 -0300 Subject: [PATCH 20/42] chore: doc --- includes/class-reader-activation.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index c1033f33e8..07485e336d 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -301,6 +301,7 @@ function( $args ) use ( $self ) { * * @param string $output The HTML for the menu items. * @param stdClass $args An object containing wp_nav_menu() arguments. + * @param bool $echo Whether to echo the HTML or return it. * * @return string The HTML list content for the menu items. */ From ad3d698931d3750f8b0741ffe009ceb8408f7294 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Tue, 12 Jul 2022 13:15:40 -0300 Subject: [PATCH 21/42] feat: ui updates; registration; mobile icon --- assets/reader-activation/auth.js | 24 ++++- assets/reader-activation/auth.scss | 108 +++++++++++++++++---- includes/class-reader-activation.php | 135 +++++++++++++++++++++------ 3 files changed, 214 insertions(+), 53 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index 4334b2736c..c7eb834a2a 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -27,7 +27,15 @@ import './auth.scss'; const actionInput = form.querySelector( 'input[name="action"]' ); const emailInput = form.querySelector( 'input[name="email"]' ); const passwordInput = form.querySelector( 'input[name="password"]' ); - const submitButton = form.querySelector( '[type="submit"]' ); + const submitButton = form.querySelectorAll( '[type="submit"]' ); + + /** Apply primary color */ + const primaryColor = container.getAttribute( 'data-primary-color' ); + if ( primaryColor ) { + submitButton.forEach( button => { + button.style.backgroundColor = primaryColor; + } ); + } const messageContainer = container.querySelector( '.form-response' ); @@ -45,12 +53,15 @@ import './auth.scss'; accountLinks.forEach( link => { try { const labels = JSON.parse( link.getAttribute( 'data-labels' ) ); - link.querySelector( 'a' ).innerHTML = authenticated + link.querySelector( '.newspack-reader-account-link-label' ).innerHTML = authenticated ? labels.signedin : labels.signedout; } catch {} } ); } + if ( authenticated ) { + container.hidden = true; + } } ); /** @@ -121,7 +132,9 @@ import './auth.scss'; if ( ! body.has( 'email' ) || ! body.get( 'email' ) ) { return; } - submitButton.disabled = true; + submitButton.forEach( button => { + button.disabled = true; + } ); messageContainer.innerHTML = ''; form.style.opacity = 0.5; readerActivation.setReaderEmail( body.get( 'email' ) ); @@ -142,7 +155,6 @@ import './auth.scss'; readerActivation.setReaderEmail( data.email ); } if ( data?.authenticated ) { - container.hidden = true; readerActivation.setAuthenticated(); if ( body.get( 'redirect' ) ) { window.location = body.get( 'redirect' ); @@ -160,7 +172,9 @@ import './auth.scss'; } ) .finally( () => { form.style.opacity = 1; - submitButton.disabled = false; + submitButton.forEach( button => { + button.disabled = false; + } ); } ); } ); }; diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss index d3debad9c9..fefe61ef55 100644 --- a/assets/reader-activation/auth.scss +++ b/assets/reader-activation/auth.scss @@ -1,3 +1,48 @@ +.newspack-reader-account-link a, +.menu-item .newspack-reader-account-link a { + display: flex; + justify-content: center; + align-items: center; + span { + display: block; + svg { + display: block; + } + } + .newspack-reader-account-link-icon { + height: 0; + margin-top: -24px; + } + .newspack-reader-account-link-label { + margin-left: 0.2rem; + @media screen and ( max-width: 959px ) { + border: 0; + clip: rect( 1px, 1px, 1px, 1px ); + clip-path: inset( 50% ); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute !important; + width: 1px; + word-wrap: normal !important; + } + } +} + +ul.newspack-reader-account-menu, +li.menu-item .newspack-reader-account-link { + @media screen and ( max-width: 959px ) { + display: none; + } +} +.mobile-account-link { + @media screen and ( min-width: 960px ) { + display: none; + } + margin-left: 5vw; +} + #newspack-reader-activation-auth-form { display: none; background: rgba( 0, 0, 0, 0.75 ); @@ -12,6 +57,19 @@ z-index: 999999; font-size: 0.9em; + a { + color: #36f; + text-decoration: underline; + } + + p.small { + margin: 0; + font-size: 0.8em; + a { + color: inherit; + } + } + @media screen and ( max-width: 744px ) { align-items: flex-end; } @@ -25,6 +83,22 @@ max-width: 544px; background: #fff; position: relative; + .form-header { + display: flex; + justify-content: space-between; + align-items: center; + h2 { + font-size: 1.3em; + } + a { + font-size: 0.8em; + color: #36f; + margin-left: 1rem; + } + @media screen and ( max-width: 744px ) { + justify-content: flex-start; + } + } .form-content { padding: 64px; @media screen and ( max-width: 744px ) { @@ -47,37 +121,31 @@ display: flex; justify-content: space-between; flex-wrap: wrap; - p { + p:not( .small ) { flex: 1 1 100%; } button { display: block; width: 100%; } - a { - font-size: 0.8em; - } } .form-response { font-size: 0.8em; - position: absolute; - top: 16px; - left: 64px; - right: 64px; - background: #fff; - @media screen and ( max-width: 744px ) { - text-align: center; - position: fixed; - top: 5vw; - left: 5vw; - right: 5vw; + } + + p.message { + display: block; + width: 100%; + margin: 0; + padding: 0.25rem 0; + &.status-400 { + color: #d94f4f; } - p.message { - display: block; - width: 100%; - margin: 0; - padding: 0.25rem 0; + &.status-200 { + background: rgba( 74, 184, 102, 0.1 ); + text-align: center; + padding: 1rem; } } diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 07485e336d..58f9e9a5d8 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -29,6 +29,7 @@ final class Reader_Activation { const AUTH_FORM_OPTIONS = [ 'pwd', 'link', + 'register', ]; /** @@ -265,6 +266,8 @@ public static function auth_cookie_expiration( $length, $user_id, $remember ) { * Setup nav menu hooks. */ public static function setup_nav_menu() { + $self = new self(); + /** Always have location enabled for account link. */ \add_filter( 'has_nav_menu', @@ -279,7 +282,6 @@ function( $has_nav_menu, $location ) { ); /** Fallback location to always print nav menu args */ - $self = new self(); \add_filter( 'wp_nav_menu_args', function( $args ) use ( $self ) { @@ -294,6 +296,18 @@ function( $args ) use ( $self ) { /** Add as menu item */ \add_filter( 'wp_nav_menu_items', [ __CLASS__, 'nav_menu_items' ], 20, 2 ); + + /** Add mobile icon */ + \add_action( + 'newspack_header_after_mobile_toggle', + function() use ( $self ) { + ?> + + \__( 'Account', 'newspack' ), - 'signedout' => \__( 'Sign In', 'newspack' ), - ]; - $label = \is_user_logged_in() ? 'signedin' : 'signedout'; - $classnames = [ 'menu-item', 'newspack-reader-account-link' ]; - $item = ''; - $item .= '
  • '; - $item .= '' . \esc_html( $labels[ $label ] ) . ''; - $item .= '
  • '; + $item = ''; if ( empty( $output ) ) { - $output = sprintf( $args->items_wrap ?? '
      %3$s
    ', $args->menu_id, $args->menu_class, $item ); + $menu_class = sprintf( '%s %s', $args->menu_class, 'newspack-reader-account-menu' ); + $output = sprintf( $args->items_wrap ?? '
      %3$s
    ', $args->menu_id, $menu_class, $item ); } else { $output = $output . $item; } @@ -353,6 +357,54 @@ public static function nav_menu_items( $output, $args = [], $echo = false ) { } } + /** + * Get the account icon SVG markup. + * + * @return string The account icon SVG markup. + */ + private static function get_account_icon() { + return ''; + } + + /** + * Get account link. + * + * @return string Account link HTML or empty string. + */ + private static function get_account_link() { + $account_url = ''; + if ( function_exists( 'wc_get_account_endpoint_url' ) ) { + $account_url = \wc_get_account_endpoint_url( 'dashboard' ); + } + + /** Do not render link for authenticated readers if account page doesn't exist. */ + if ( empty( $account_url ) && \is_user_logged_in() ) { + return ''; + } + + $labels = [ + 'signedin' => \__( 'Account', 'newspack' ), + 'signedout' => \__( 'Sign In', 'newspack' ), + ]; + $label = \is_user_logged_in() ? 'signedin' : 'signedout'; + + $link = ''; + + /** + * Filters the HTML for the reader account link. + * + * @param string $link HTML for the reader account link. + */ + return apply_filters( 'newspack_reader_account_link', $link ); + } + /** * Renders reader authentication form */ @@ -369,8 +421,9 @@ public static function render_auth_form() { $classnames[] = 'visible'; } // phpcs:enable + $primary_color = \get_theme_mod( 'primary_color_hex', '#36f' ); ?> -
    +

    - - +

    + +

    +

    + +

    -
    - -

    - +
    +

    +

    + +

    @@ -477,7 +546,7 @@ public static function process_auth_form() { self::set_auth_intention_cookie( $email ); $user = \get_user_by( 'email', $email ); - if ( ! $user || ! self::is_user_reader( $user ) ) { + if ( ( ! $user && 'register' !== $action ) || ( $user && ! self::is_user_reader( $user ) ) ) { return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid account.', 'newspack' ) ) ); } @@ -504,6 +573,16 @@ public static function process_auth_form() { return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid account.', 'newspack' ) ) ); } return self::send_auth_form_response( $payload, __( 'Check your email for an authentication link!', 'newspack' ), $redirect ); + case 'register': + $user_id = self::register_reader( $email ); + if ( false === $user_id ) { + return self::send_auth_form_response( $payload, __( 'Check your email for an authentication link!', 'newspack' ), $redirect ); + } + if ( \is_wp_error( $user_id ) ) { + return self::send_auth_form_response( $payload, $user_id->get_error_message(), $redirect ); + } + $payload['authenticated'] = (bool) absint( $user_id ); + return self::send_auth_form_response( $payload, false, $redirect ); } } From ea75b72ddc0674c22ae0db1d0dd852cd85f582ad Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Tue, 12 Jul 2022 13:20:33 -0300 Subject: [PATCH 22/42] fix: registration handling --- includes/class-reader-activation.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 58f9e9a5d8..d65d091670 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -579,9 +579,11 @@ public static function process_auth_form() { return self::send_auth_form_response( $payload, __( 'Check your email for an authentication link!', 'newspack' ), $redirect ); } if ( \is_wp_error( $user_id ) ) { - return self::send_auth_form_response( $payload, $user_id->get_error_message(), $redirect ); + return self::send_auth_form_response( + new \WP_Error( 'unauthorized', __( 'Unable to register your account. Try a different email.', 'newspack' ) ) + ); } - $payload['authenticated'] = (bool) absint( $user_id ); + $payload['authenticated'] = \absint( $user_id ) ? 1 : 0; return self::send_auth_form_response( $payload, false, $redirect ); } } From 742f2e4ba76d4369010f1fda6ff71f16c6317115 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Tue, 12 Jul 2022 15:30:54 -0300 Subject: [PATCH 23/42] fix: success message ui --- assets/reader-activation/auth.js | 9 ++++-- assets/reader-activation/auth.scss | 42 ++++++++++++++++++++++------ includes/class-reader-activation.php | 15 ++++++---- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index c7eb834a2a..2c2442f6af 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -147,10 +147,12 @@ import './auth.scss'; } ) .then( res => { res.json().then( ( { message, data } ) => { - const messageNode = document.createElement( 'p' ); - messageNode.innerHTML = message; + const messageNode = document.createElement( 'div' ); + messageNode.innerHTML = `

    ${ message }

    `; messageNode.className = `message status-${ res.status }`; if ( res.status === 200 ) { + container.classList.remove( 'error' ); + container.classList.add( 'success' ); if ( data?.email ) { readerActivation.setReaderEmail( data.email ); } @@ -160,9 +162,12 @@ import './auth.scss'; window.location = body.get( 'redirect' ); } } else { + messageNode.prepend( container.querySelector( '.success-icon' ) ); form.replaceWith( messageNode ); } } else { + container.classList.add( 'error' ); + container.classList.remove( 'success' ); messageContainer.appendChild( messageNode ); } } ); diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss index fefe61ef55..75e8ace312 100644 --- a/assets/reader-activation/auth.scss +++ b/assets/reader-activation/auth.scss @@ -62,6 +62,10 @@ li.menu-item .newspack-reader-account-link { text-decoration: underline; } + p { + margin: 0.5rem 0; + } + p.small { margin: 0; font-size: 0.8em; @@ -101,15 +105,15 @@ li.menu-item .newspack-reader-account-link { } .form-content { padding: 64px; + > .success-icon { + display: none !important; + } @media screen and ( max-width: 744px ) { padding: 0 5vw 5vw 5vw; } form { transition: opacity 0.2s ease-in-out; } - p { - margin: 0.5rem 0; - } input[type='email'], input[type='password'] { width: 100%; @@ -134,18 +138,40 @@ li.menu-item .newspack-reader-account-link { font-size: 0.8em; } - p.message { + .message { display: block; width: 100%; margin: 0; padding: 0.25rem 0; - &.status-400 { - color: #d94f4f; + p { + margin: 0; } - &.status-200 { + } + + .success-icon { + display: flex; + margin: 0 auto 2rem; + border-radius: 100%; + width: 50px; + height: 50px; + justify-content: center; + align-items: center; + color: #fff; + background: #4ab866; + } + + &.success { + .message { background: rgba( 74, 184, 102, 0.1 ); text-align: center; - padding: 1rem; + padding: 2.5rem; + margin: 1rem 0; + } + } + + &.error { + .message { + color: #d94f4f; } } diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index d65d091670..af62ec97ca 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -303,7 +303,7 @@ function( $args ) use ( $self ) { function() use ( $self ) { ?>
    -
    - +

    @@ -466,8 +466,8 @@ public static function render_auth_form() {
    @@ -477,6 +477,9 @@ public static function render_auth_form() {

    + + +
    @@ -572,7 +575,7 @@ public static function process_auth_form() { if ( true !== $sent ) { return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid account.', 'newspack' ) ) ); } - return self::send_auth_form_response( $payload, __( 'Check your email for an authentication link!', 'newspack' ), $redirect ); + return self::send_auth_form_response( $payload, __( "We've sent you an authentication link, please check your inbox", 'newspack' ), $redirect ); case 'register': $user_id = self::register_reader( $email ); if ( false === $user_id ) { From 64e5d1a89e48641921b76983f54cab90bf33c88b Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Tue, 12 Jul 2022 15:37:09 -0300 Subject: [PATCH 24/42] fix: handle async load --- assets/reader-activation/auth.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index 2c2442f6af..b699066bdc 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -4,10 +4,11 @@ import './auth.scss'; ( function ( readerActivation ) { - if ( ! readerActivation ) { - return; - } window.onload = function () { + if ( ! readerActivation ) { + return; + } + const container = document.querySelector( '#newspack-reader-activation-auth-form' ); if ( ! container ) { return; From b976af33a8039bfda614449f7f660fcba53c2eb0 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Tue, 12 Jul 2022 15:39:49 -0300 Subject: [PATCH 25/42] fix: success message phrasing --- includes/class-reader-activation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index af62ec97ca..dc114977be 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -575,11 +575,11 @@ public static function process_auth_form() { if ( true !== $sent ) { return self::send_auth_form_response( new \WP_Error( 'unauthorized', __( 'Invalid account.', 'newspack' ) ) ); } - return self::send_auth_form_response( $payload, __( "We've sent you an authentication link, please check your inbox", 'newspack' ), $redirect ); + return self::send_auth_form_response( $payload, __( "We've sent you an authentication link, please check your inbox.", 'newspack' ), $redirect ); case 'register': $user_id = self::register_reader( $email ); if ( false === $user_id ) { - return self::send_auth_form_response( $payload, __( 'Check your email for an authentication link!', 'newspack' ), $redirect ); + return self::send_auth_form_response( $payload, __( "We've sent you an authentication link, please check your inbox.", 'newspack' ), $redirect ); } if ( \is_wp_error( $user_id ) ) { return self::send_auth_form_response( From 8df663a8213494b8143a615f1e4a06818864bbcf Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Tue, 12 Jul 2022 16:03:50 -0300 Subject: [PATCH 26/42] fix: cleanup unnecessary parameter --- includes/class-reader-activation.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 4a55d3bc1e..72b3246b6a 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -297,7 +297,7 @@ function( $has_nav_menu, $location ) { function( $args ) use ( $self ) { if ( 'tertiary-menu' === $args['theme_location'] ) { $args['fallback_cb'] = function( $args ) use ( $self ) { - $self->nav_menu_items( '', $args, true ); + echo $self->nav_menu_items( '', $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped }; } return $args; @@ -325,11 +325,10 @@ function() use ( $self ) { * * @param string $output The HTML for the menu items. * @param stdClass $args An object containing wp_nav_menu() arguments. - * @param bool $echo Whether to echo the HTML or return it. * * @return string The HTML list content for the menu items. */ - public static function nav_menu_items( $output, $args = [], $echo = false ) { + public static function nav_menu_items( $output, $args = [] ) { $args = (object) $args; /** Do not alter items for authenticated non-readers */ @@ -360,11 +359,7 @@ public static function nav_menu_items( $output, $args = [], $echo = false ) { } else { $output = $output . $item; } - if ( $echo ) { - echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } else { - return $output; - } + return $output; } /** From 673d76352f8b1e8ea9323b5b8b046817097f975d Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Tue, 12 Jul 2022 16:08:21 -0300 Subject: [PATCH 27/42] fix: menu location declaration --- includes/class-reader-activation.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 72b3246b6a..a5dbeff0ae 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -25,8 +25,9 @@ final class Reader_Activation { /** * Auth form. */ - const AUTH_FORM_ACTION = 'reader-activation-auth-form'; - const AUTH_FORM_OPTIONS = [ + const ACCOUNT_LINK_MENU_LOCATIONS = [ 'tertiary-menu' ]; + const AUTH_FORM_ACTION = 'reader-activation-auth-form'; + const AUTH_FORM_OPTIONS = [ 'pwd', 'link', 'register', @@ -282,7 +283,7 @@ public static function setup_nav_menu() { \add_filter( 'has_nav_menu', function( $has_nav_menu, $location ) { - if ( 'tertiary-menu' === $location ) { + if ( in_array( $location, self::ACCOUNT_LINK_MENU_LOCATIONS, true ) ) { $has_nav_menu = true; } return $has_nav_menu; @@ -295,7 +296,7 @@ function( $has_nav_menu, $location ) { \add_filter( 'wp_nav_menu_args', function( $args ) use ( $self ) { - if ( 'tertiary-menu' === $args['theme_location'] ) { + if ( in_array( $args['theme_location'], self::ACCOUNT_LINK_MENU_LOCATIONS, true ) ) { $args['fallback_cb'] = function( $args ) use ( $self ) { echo $self->nav_menu_items( '', $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped }; @@ -339,8 +340,7 @@ public static function nav_menu_items( $output, $args = [] ) { /** * Menu locations to add the account menu items to. */ - $locations = [ 'tertiary-menu' ]; - if ( ! in_array( $args->theme_location, $locations, true ) ) { + if ( ! in_array( $args->theme_location, self::ACCOUNT_LINK_MENU_LOCATIONS, true ) ) { return $output; } From b8dafdf332df3396e31afcf5ac3e30e7783f25ac Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Fri, 15 Jul 2022 13:40:14 -0300 Subject: [PATCH 28/42] chore: lint --- assets/reader-activation/auth.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/reader-activation/auth.scss b/assets/reader-activation/auth.scss index 75e8ace312..5a11a513cd 100644 --- a/assets/reader-activation/auth.scss +++ b/assets/reader-activation/auth.scss @@ -195,7 +195,7 @@ li.menu-item .newspack-reader-account-link { width: 36px; svg { - fill: currentColor; + fill: currentcolor; flex: 0 0 24px; } From e93e5283be032a86c4b7981c580861b00efb6f0d Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Fri, 15 Jul 2022 14:07:44 -0300 Subject: [PATCH 29/42] fix: non-amp js --- assets/reader-activation/auth.js | 39 ++++++++++++++++------------ includes/class-reader-activation.php | 18 ++++++------- webpack.config.js | 2 +- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/assets/reader-activation/auth.js b/assets/reader-activation/auth.js index b699066bdc..bd3bd86986 100644 --- a/assets/reader-activation/auth.js +++ b/assets/reader-activation/auth.js @@ -28,12 +28,17 @@ import './auth.scss'; const actionInput = form.querySelector( 'input[name="action"]' ); const emailInput = form.querySelector( 'input[name="email"]' ); const passwordInput = form.querySelector( 'input[name="password"]' ); - const submitButton = form.querySelectorAll( '[type="submit"]' ); + const submitButtons = form.querySelectorAll( '[type="submit"]' ); + + container.querySelector( 'button[data-close]' ).addEventListener( 'click', function ( ev ) { + ev.preventDefault(); + container.style.display = 'none'; + } ); /** Apply primary color */ const primaryColor = container.getAttribute( 'data-primary-color' ); if ( primaryColor ) { - submitButton.forEach( button => { + submitButtons.forEach( button => { button.style.backgroundColor = primaryColor; } ); } @@ -61,7 +66,7 @@ import './auth.scss'; } ); } if ( authenticated ) { - container.hidden = true; + container.style.display = 'none'; } } ); @@ -77,9 +82,9 @@ import './auth.scss'; ev.preventDefault(); if ( readerActivation.hasAuthLink() ) { - authLinkMessage.hidden = false; + authLinkMessage.style.display = 'block'; } else { - authLinkMessage.hidden = true; + authLinkMessage.style.display = 'none'; } emailInput.value = reader?.email || ''; @@ -87,7 +92,7 @@ import './auth.scss'; container.hidden = false; container.style.display = 'flex'; - if ( emailInput.value ) { + if ( emailInput.value && 'pwd' === actionInput.value ) { passwordInput.focus(); } else { emailInput.focus(); @@ -104,23 +109,25 @@ import './auth.scss'; actionInput.value = action; messageContainer.innerHTML = ''; container.querySelectorAll( '.action-item' ).forEach( item => { - item.hidden = true; + if ( 'none' !== item.style.display ) { + item.prevDisplay = item.style.display; + } + item.style.display = 'none'; } ); container.querySelectorAll( '.action-' + action ).forEach( item => { - item.hidden = false; + item.style.display = item.prevDisplay; } ); + if ( action === 'pwd' && emailInput.value ) { + passwordInput.focus(); + } else { + emailInput.focus(); + } } setFormAction( actionInput.value ); container.querySelectorAll( '[data-set-action]' ).forEach( item => { item.addEventListener( 'click', function ( ev ) { ev.preventDefault(); - const action = ev.target.getAttribute( 'data-set-action' ); setFormAction( ev.target.getAttribute( 'data-set-action' ) ); - if ( action === 'pwd' && emailInput.value ) { - passwordInput.focus(); - } else { - emailInput.focus(); - } } ); } ); @@ -133,7 +140,7 @@ import './auth.scss'; if ( ! body.has( 'email' ) || ! body.get( 'email' ) ) { return; } - submitButton.forEach( button => { + submitButtons.forEach( button => { button.disabled = true; } ); messageContainer.innerHTML = ''; @@ -178,7 +185,7 @@ import './auth.scss'; } ) .finally( () => { form.style.opacity = 1; - submitButton.forEach( button => { + submitButtons.forEach( button => { button.disabled = false; } ); } ); diff --git a/includes/class-reader-activation.php b/includes/class-reader-activation.php index 2d3451cd30..ab6e62962e 100644 --- a/includes/class-reader-activation.php +++ b/includes/class-reader-activation.php @@ -16,6 +16,7 @@ final class Reader_Activation { const AUTH_INTENTION_COOKIE = 'np_auth_intention'; const SCRIPT_HANDLE = 'newspack-reader-activation'; + const AUTH_SCRIPT_HANDLE = 'newspack-reader-auth'; /** * Reader user meta keys. @@ -84,19 +85,18 @@ public static function enqueue_scripts() { /** * Nav menu items script. */ - $auth_handle = 'newspack-reader-activation-auth'; \wp_enqueue_script( - $auth_handle, - Newspack::plugin_url() . '/dist/reader-activation-auth.js', - [ $handle ], + self::AUTH_SCRIPT_HANDLE, + Newspack::plugin_url() . '/dist/reader-auth.js', + [ self::SCRIPT_HANDLE ], NEWSPACK_PLUGIN_VERSION, true ); - \wp_script_add_data( $auth_handle, 'async', true ); - \wp_script_add_data( $auth_handle, 'amp-plus', true ); + \wp_script_add_data( self::AUTH_SCRIPT_HANDLE, 'async', true ); + \wp_script_add_data( self::AUTH_SCRIPT_HANDLE, 'amp-plus', true ); \wp_enqueue_style( - $auth_handle, - Newspack::plugin_url() . '/dist/reader-activation-auth.css', + self::AUTH_SCRIPT_HANDLE, + Newspack::plugin_url() . '/dist/reader-auth.css', [], NEWSPACK_PLUGIN_VERSION ); @@ -433,7 +433,7 @@ public static function render_auth_form() { ?>
    - -
    +
    -
    +

    -

    -

    +

    -
    +

    -
    +

    -
    +

    @@ -471,21 +470,21 @@ public static function render_auth_form() {

    -