diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 3d1175654..41fdcaf92 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,5 +1,11 @@ # Changelog +## [v2.7.1] + +### Enhancements + +- Updated new account signup flow [#2695](https://github.com/Automattic/simplenote-electron/pull/2695) + ## [v2.7.0] ### Enhancements diff --git a/desktop/app.js b/desktop/app.js index 5018dfe93..f83e720e6 100644 --- a/desktop/app.js +++ b/desktop/app.js @@ -32,7 +32,11 @@ module.exports = function main() { setTimeout(updater.ping.bind(updater), config.updater.delay); app.on('open-url', function (event, url) { event.preventDefault(); - mainWindow.webContents.send('wpLogin', url); + if (url.startsWith('simplenote://auth')) { + mainWindow.webContents.send('wpLogin', url); + } else if (url.startsWith('simplenote://login')) { + mainWindow.webContents.send('tokenLogin', url); + } }); }); @@ -211,7 +215,11 @@ module.exports = function main() { // Protocol handler for platforms other than macOS // argv: An array of the second instance’s (command line / deep linked) arguments // The last index of argv is the full deeplink url (simplenote://SOME_URL) - mainWindow.webContents.send('wpLogin', argv[argv.length - 1]); + if (argv[argv.length - 1].startsWith('simplenote://auth')) { + mainWindow.webContents.send('wpLogin', argv[argv.length - 1]); + } else if (argv[argv.length - 1].startsWith('simplenote://login')) { + mainWindow.webContents.send('tokenLogin', argv[argv.length - 1]); + } }); if (!gotTheLock) { diff --git a/desktop/preload.js b/desktop/preload.js index 7e9df9f15..b61aa5a70 100644 --- a/desktop/preload.js +++ b/desktop/preload.js @@ -10,6 +10,7 @@ const validChannels = [ 'noteImportChannel', 'reallyCloseWindow', 'setAutoHideMenuBar', + 'tokenLogin', 'wpLogin', ]; diff --git a/lib/alternate-login-prompt/index.tsx b/lib/alternate-login-prompt/index.tsx new file mode 100644 index 000000000..dc26411d5 --- /dev/null +++ b/lib/alternate-login-prompt/index.tsx @@ -0,0 +1,103 @@ +import React, { FunctionComponent } from 'react'; +import classNames from 'classnames'; +import Modal from 'react-modal'; +import { connect } from 'react-redux'; + +import CrossIcon from '../icons/cross'; +import WarningIcon from '../icons/warning'; + +import actions from '../state/actions'; + +import * as selectors from '../state/selectors'; +import * as S from '../state'; + +type StateProps = { + alternateLoginEmail: string | null; + email: string | null; + theme: 'light' | 'dark'; +}; + +type DispatchProps = { + logout: () => any; + dismiss: () => any; +}; + +type Props = StateProps & DispatchProps; + +const AlternateLoginPrompt: FunctionComponent = ({ + alternateLoginEmail, + dismiss, + email, + logout, + theme, +}) => { + const displayClose = ( +
+ +
+ ); + + const displayAlternateLoginPrompt = ( + <> + {displayClose} + + + +

Logout?

+

You are already logged in as:

+

+ {email} +

+

You tried logging in with:

+

+ {alternateLoginEmail} +

+

Would you like to log out?

+ +
+ + + + + + +
+ + ); + + return ( + + {displayAlternateLoginPrompt} + + ); +}; + +const mapDispatchToProps: S.MapDispatch = { + dismiss: () => actions.ui.showAlternateLoginPrompt(false), + logout: actions.ui.logout, +}; + +const mapStateToProps: S.MapState = (state) => ({ + alternateLoginEmail: state.ui.alternateLoginEmail, + email: state.settings.accountName, + theme: selectors.getTheme(state), +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(AlternateLoginPrompt); diff --git a/lib/alternate-login-prompt/style.scss b/lib/alternate-login-prompt/style.scss new file mode 100644 index 000000000..596a1a1a1 --- /dev/null +++ b/lib/alternate-login-prompt/style.scss @@ -0,0 +1,81 @@ +.alternate-login__overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + background: rgba($studio-gray-20, 0.75); +} + +.alternate-login__dismiss { + display: flex; + height: 60px; + justify-content: flex-end; + align-items: center; + margin: 0 16px; + width: 100%; + + svg { + height: 16px; + width: 16px; + margin: auto 0; + } +} + +.alternate-login__email { + word-break: break-all; +} + +.alternate-login__modal { + align-items: center; + border-radius: 8px; + display: flex; + flex-direction: column; + justify-content: center; + margin: 20%; + max-width: 420px; + padding: 0 24px 16px; + font-size: 16px; + + p { + margin: 0 26px; + margin-block-start: 0; + } + + p:not(:last-of-type) { + padding-bottom: 20px; + } + + .alternate-login__button-row { + padding-top: 10px; + padding-bottom: 10px; + display: flex; + flex-direction: row; + justify-content: flex-end; + flex-wrap: wrap; + width: 100%; + + a { + padding-right: 12px; + } + .button-borderless { + color: $studio-simplenote-blue-50; + } + } + + &:focus { + outline: 0; + } +} + +.theme-dark { + .alternate-login__overlay { + background: rgba($studio-gray-100, 0.75); + } + .alternate-login__button-row .button-borderless { + color: $studio-simplenote-blue-30; + } +} diff --git a/lib/app.tsx b/lib/app.tsx index 013267122..de5eb664a 100644 --- a/lib/app.tsx +++ b/lib/app.tsx @@ -7,6 +7,7 @@ import AppLayout from './app-layout'; import DevBadge from './components/dev-badge'; import DialogRenderer from './dialog-renderer'; import EmailVerification from './email-verification'; +import AlternateLoginPrompt from './alternate-login-prompt'; import { isElectron, isMac } from './utils/platform'; import classNames from 'classnames'; import { @@ -34,6 +35,7 @@ type StateProps = { isSmallScreen: boolean; lineLength: T.LineLength; isSearchActive: boolean; + showAlternateLoginPrompt: boolean; showEmailVerification: boolean; showNavigation: boolean; showNoteInfo: boolean; @@ -162,6 +164,7 @@ class AppComponent extends Component { const { isDevConfig, lineLength, + showAlternateLoginPrompt, showEmailVerification, showNavigation, showNoteInfo, @@ -183,6 +186,7 @@ class AppComponent extends Component { return (
{showEmailVerification && } + {showAlternateLoginPrompt && } {isDevConfig && }
{showNavigation && } @@ -201,6 +205,7 @@ const mapStateToProps: S.MapState = (state) => ({ isSearchActive: !!state.ui.searchQuery.length, isSmallScreen: selectors.isSmallScreen(state), lineLength: state.settings.lineLength, + showAlternateLoginPrompt: state.ui.showAlternateLoginPrompt, showEmailVerification: selectors.shouldShowEmailVerification(state), showNavigation: state.ui.showNavigation, showNoteInfo: state.ui.showNoteInfo, diff --git a/lib/auth/index.tsx b/lib/auth/index.tsx index 544c7e245..96ae9e582 100644 --- a/lib/auth/index.tsx +++ b/lib/auth/index.tsx @@ -3,19 +3,21 @@ import classNames from 'classnames'; import cryptoRandomString from '../utils/crypto-random-string'; import { get } from 'lodash'; import getConfig from '../../get-config'; +import MailIcon from '../icons/mail'; import SimplenoteLogo from '../icons/simplenote'; import Spinner from '../components/spinner'; -import { validatePassword } from '../utils/validate-password'; import { isElectron, isMac } from '../utils/platform'; import { viewExternalUrl } from '../utils/url-utils'; type OwnProps = { + accountCreationRequested: boolean; authPending: boolean; + emailSentTo: string; hasInsecurePassword: boolean; hasInvalidCredentials: boolean; hasLoginError: boolean; login: (username: string, password: string) => any; - signup: (username: string, password: string) => any; + requestSignup: (username: string) => any; tokenLogin: (username: string, token: string) => any; resetErrors: () => any; }; @@ -60,14 +62,58 @@ export class Auth extends Component { const helpMessage = isCreatingAccount ? 'Already have an account?' : "Don't have an account?"; - const errorMessage = isCreatingAccount - ? 'Could not create account. Please try again.' - : 'Could not log in with the provided email address and password.'; + + const errorMessage = isCreatingAccount ? ( + <> + Could not request account creation. Please try again or + { + event.preventDefault(); + viewExternalUrl( + 'mailto:support@simplenote.com?subject=Simplenote%20Support' + ); + }} + > + contact us + + . + + ) : ( + 'Could not log in with the provided email address and password.' + ); const mainClasses = classNames('login', { 'is-electron': isElectron, }); + if (this.props.accountCreationRequested) { + return ( +
+ {isElectron && isMac &&
} +
+ +

+ We've sent an email to{' '} + {this.props.emailSentTo}. Please check your inbox + and follow the instructions. +

+

+ Didn't get an email? You may already have an account. Contact{' '} + support@simplenote.com{' '} + for help. +

+ +
+
+ ); + } + return (
{isElectron && isMac &&
} @@ -132,26 +178,29 @@ export class Auth extends Component { required autoFocus /> - - (this.passwordInput = ref)} - spellCheck={false} - type="password" - required - minLength="4" - /> - + {!isCreatingAccount && ( + <> + + (this.passwordInput = ref)} + spellCheck={false} + type="password" + required + minLength={4} + /> + + )}