From 6415f2746e38ebe5cb328c2af94413a4d4e5da07 Mon Sep 17 00:00:00 2001 From: André Oliveira <37463445+SpecialAro@users.noreply.github.com> Date: Tue, 12 Jul 2022 17:59:43 +0100 Subject: Refactor the 'Welcome' screen and the 'SetupAssistant' for better UX (#472) * Change auth styling and add back button * Add Skip button on 'SetupAssistant' --- src/components/auth/AuthLayout.js | 118 ------------ src/components/auth/AuthLayout.jsx | 124 ++++++++++++ src/components/auth/ChangeServer.js | 141 -------------- src/components/auth/ChangeServer.jsx | 153 +++++++++++++++ src/components/auth/Login.js | 203 -------------------- src/components/auth/Login.jsx | 205 ++++++++++++++++++++ src/components/auth/SetupAssistant.js | 331 -------------------------------- src/components/auth/SetupAssistant.jsx | 336 +++++++++++++++++++++++++++++++++ src/components/auth/Signup.js | 191 ------------------- src/components/auth/Signup.jsx | 206 ++++++++++++++++++++ 10 files changed, 1024 insertions(+), 984 deletions(-) delete mode 100644 src/components/auth/AuthLayout.js create mode 100644 src/components/auth/AuthLayout.jsx delete mode 100644 src/components/auth/ChangeServer.js create mode 100644 src/components/auth/ChangeServer.jsx delete mode 100644 src/components/auth/Login.js create mode 100644 src/components/auth/Login.jsx delete mode 100644 src/components/auth/SetupAssistant.js create mode 100644 src/components/auth/SetupAssistant.jsx delete mode 100644 src/components/auth/Signup.js create mode 100644 src/components/auth/Signup.jsx (limited to 'src/components/auth') diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js deleted file mode 100644 index 41bda2f67..000000000 --- a/src/components/auth/AuthLayout.js +++ /dev/null @@ -1,118 +0,0 @@ -import { cloneElement, Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer } from 'mobx-react'; -import { TitleBar } from 'electron-react-titlebar/renderer'; - -import { injectIntl } from 'react-intl'; -import { mdiFlash } from '@mdi/js'; -import Link from '../ui/Link'; -import InfoBar from '../ui/InfoBar'; - -import { Component as PublishDebugInfo } from '../../features/publishDebugInfo'; - -import { - oneOrManyChildElements, - globalError as globalErrorPropType, -} from '../../prop-types'; -import globalMessages from '../../i18n/globalMessages'; - -import { isWindows } from '../../environment'; -import AppUpdateInfoBar from '../AppUpdateInfoBar'; -import { GITHUB_FERDIUM_URL } from '../../config'; -import Icon from '../ui/icon'; - -import { serverName } from '../../api/apiBase'; - -class AuthLayout extends Component { - static propTypes = { - children: oneOrManyChildElements.isRequired, - error: globalErrorPropType.isRequired, - isOnline: PropTypes.bool.isRequired, - isAPIHealthy: PropTypes.bool.isRequired, - retryHealthCheck: PropTypes.func.isRequired, - isHealthCheckLoading: PropTypes.bool.isRequired, - isFullScreen: PropTypes.bool.isRequired, - installAppUpdate: PropTypes.func.isRequired, - appUpdateIsDownloaded: PropTypes.bool.isRequired, - }; - - state = { - shouldShowAppUpdateInfoBar: true, - }; - - render() { - const { - children, - error, - isOnline, - isAPIHealthy, - retryHealthCheck, - isHealthCheckLoading, - isFullScreen, - installAppUpdate, - appUpdateIsDownloaded, - } = this.props; - - const { intl } = this.props; - - let serverNameParse = serverName(); - serverNameParse = - serverNameParse === 'Custom' ? 'your Custom Server' : serverNameParse; - - return ( - <> - {isWindows && !isFullScreen && ( - - )} -
- {!isOnline && ( - - - {intl.formatMessage(globalMessages.notConnectedToTheInternet)} - - )} - {appUpdateIsDownloaded && this.state.shouldShowAppUpdateInfoBar && ( - { - this.setState({ shouldShowAppUpdateInfoBar: false }); - }} - /> - )} - {isOnline && !isAPIHealthy && ( - - - {intl.formatMessage(globalMessages.APIUnhealthy, { serverNameParse })} - - )} -
- {/* Inject globalError into children */} - {cloneElement(children, { - error, - })} -
- {/*
*/} - - - - - - - ); - } -} - -export default injectIntl(observer(AuthLayout)); diff --git a/src/components/auth/AuthLayout.jsx b/src/components/auth/AuthLayout.jsx new file mode 100644 index 000000000..8a88cedb1 --- /dev/null +++ b/src/components/auth/AuthLayout.jsx @@ -0,0 +1,124 @@ +import { cloneElement, Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { TitleBar } from 'electron-react-titlebar/renderer'; + +import { injectIntl } from 'react-intl'; +import { mdiFlash } from '@mdi/js'; +import Link from '../ui/Link'; +import InfoBar from '../ui/InfoBar'; + +import { Component as PublishDebugInfo } from '../../features/publishDebugInfo'; + +import { + oneOrManyChildElements, + globalError as globalErrorPropType, +} from '../../prop-types'; +import globalMessages from '../../i18n/globalMessages'; + +import { isWindows } from '../../environment'; +import AppUpdateInfoBar from '../AppUpdateInfoBar'; +import { GITHUB_FERDIUM_URL } from '../../config'; +import Icon from '../ui/icon'; + +import { serverName } from '../../api/apiBase'; + +class AuthLayout extends Component { + static propTypes = { + children: oneOrManyChildElements.isRequired, + error: globalErrorPropType.isRequired, + isOnline: PropTypes.bool.isRequired, + isAPIHealthy: PropTypes.bool.isRequired, + retryHealthCheck: PropTypes.func.isRequired, + isHealthCheckLoading: PropTypes.bool.isRequired, + isFullScreen: PropTypes.bool.isRequired, + installAppUpdate: PropTypes.func.isRequired, + appUpdateIsDownloaded: PropTypes.bool.isRequired, + }; + + constructor() { + super(); + + this.state = { + shouldShowAppUpdateInfoBar: true, + }; + } + + render() { + const { + children, + error, + isOnline, + isAPIHealthy, + retryHealthCheck, + isHealthCheckLoading, + isFullScreen, + installAppUpdate, + appUpdateIsDownloaded, + } = this.props; + + const { intl } = this.props; + + let serverNameParse = serverName(); + serverNameParse = + serverNameParse === 'Custom' ? 'your Custom Server' : serverNameParse; + + return ( + <> + {isWindows && !isFullScreen && ( + + )} +
+ {!isOnline && ( + + + {intl.formatMessage(globalMessages.notConnectedToTheInternet)} + + )} + {appUpdateIsDownloaded && this.state.shouldShowAppUpdateInfoBar && ( + { + this.setState({ shouldShowAppUpdateInfoBar: false }); + }} + /> + )} + {isOnline && !isAPIHealthy && ( + + + {intl.formatMessage(globalMessages.APIUnhealthy, { + serverNameParse, + })} + + )} +
+ {/* Inject globalError into children */} + {cloneElement(children, { + error, + })} +
+ {/*
*/} + + + + + + + ); + } +} + +export default injectIntl(observer(AuthLayout)); diff --git a/src/components/auth/ChangeServer.js b/src/components/auth/ChangeServer.js deleted file mode 100644 index 8d0960843..000000000 --- a/src/components/auth/ChangeServer.js +++ /dev/null @@ -1,141 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer } from 'mobx-react'; -import { defineMessages, injectIntl } from 'react-intl'; -import Form from '../../lib/Form'; -import Input from '../ui/Input'; -import Select from '../ui/Select'; -import Button from '../ui/button'; -import Link from '../ui/Link'; -import Infobox from '../ui/Infobox'; -import { url, required } from '../../helpers/validation-helpers'; -import { LIVE_FERDIUM_API, LIVE_FRANZ_API } from '../../config'; -import globalMessages from '../../i18n/globalMessages'; -import { H1 } from '../ui/headline'; - -const messages = defineMessages({ - headline: { - id: 'changeserver.headline', - defaultMessage: 'Change server', - }, - label: { - id: 'changeserver.label', - defaultMessage: 'Server', - }, - warning: { - id: 'changeserver.warning', - defaultMessage: 'Extra settings offered by Ferdium will not be saved', - }, - customServerLabel: { - id: 'changeserver.customServerLabel', - defaultMessage: 'Custom server', - }, - urlError: { - id: 'changeserver.urlError', - defaultMessage: 'Enter a valid URL', - }, -}); - -class ChangeServer extends Component { - static propTypes = { - onSubmit: PropTypes.func.isRequired, - server: PropTypes.string.isRequired, - }; - - ferdiumServer = LIVE_FERDIUM_API; - - franzServer = LIVE_FRANZ_API; - - defaultServers = [ this.ferdiumServer, this.franzServer ]; - - form = (() => { - const { intl } = this.props; - return new Form({ - fields: { - server: { - label: intl.formatMessage(messages.label), - value: this.props.server, - options: [ - { value: this.ferdiumServer, label: 'Ferdium (Default)' }, - { value: this.franzServer, label: 'Franz' }, - { - value: this.defaultServers.includes(this.props.server) - ? '' - : this.props.server, - label: 'Custom', - }, - ], - }, - customServer: { - label: intl.formatMessage(messages.customServerLabel), - value: '', - validators: [url, required], - }, - }, - }, - intl, - ); - })(); - - componentDidMount() { - if (this.defaultServers.includes(this.props.server)) { - this.form.$('server').value = this.props.server; - } else { - this.form.$('server').value = ''; - this.form.$('customServer').value = this.props.server; - } - } - - submit(e) { - e.preventDefault(); - this.form.submit({ - onSuccess: form => { - if (!this.defaultServers.includes(form.values().server)) { - form.$('server').onChange(form.values().customServer); - } - this.props.onSubmit(form.values()); - }, - onError: form => { - if (this.defaultServers.includes(form.values().server)) { - this.props.onSubmit(form.values()); - } - }, - }); - } - - render() { - const { form } = this; - const { intl } = this.props; - return ( -
-
this.submit(e)}> - -

{intl.formatMessage(messages.headline)}

- {(form.$('server').value === this.franzServer) && ( - - {intl.formatMessage(messages.warning)} - - )} - { - this.form.$('customServer').value = this.form.$('customServer').value.replace(/\/$/, ""); - this.submit(e) - }} - field={form.$('customServer')} - /> - )} -
- ); - } -} - -export default injectIntl(observer(ChangeServer)); diff --git a/src/components/auth/ChangeServer.jsx b/src/components/auth/ChangeServer.jsx new file mode 100644 index 000000000..8f4b85fbb --- /dev/null +++ b/src/components/auth/ChangeServer.jsx @@ -0,0 +1,153 @@ +import { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, injectIntl } from 'react-intl'; +import { mdiArrowLeftCircle } from '@mdi/js'; +import Form from '../../lib/Form'; +import Input from '../ui/Input'; +import Select from '../ui/Select'; +import Button from '../ui/button'; +import Link from '../ui/Link'; +import Infobox from '../ui/Infobox'; +import { url, required } from '../../helpers/validation-helpers'; +import { LIVE_FERDIUM_API, LIVE_FRANZ_API } from '../../config'; +import globalMessages from '../../i18n/globalMessages'; +import { H1 } from '../ui/headline'; +import Icon from '../ui/icon'; + +const messages = defineMessages({ + headline: { + id: 'changeserver.headline', + defaultMessage: 'Change server', + }, + label: { + id: 'changeserver.label', + defaultMessage: 'Server', + }, + warning: { + id: 'changeserver.warning', + defaultMessage: 'Extra settings offered by Ferdium will not be saved', + }, + customServerLabel: { + id: 'changeserver.customServerLabel', + defaultMessage: 'Custom server', + }, + urlError: { + id: 'changeserver.urlError', + defaultMessage: 'Enter a valid URL', + }, +}); + +class ChangeServer extends Component { + static propTypes = { + onSubmit: PropTypes.func.isRequired, + server: PropTypes.string.isRequired, + }; + + ferdiumServer = LIVE_FERDIUM_API; + + franzServer = LIVE_FRANZ_API; + + defaultServers = [this.ferdiumServer, this.franzServer]; + + form = (() => { + const { intl } = this.props; + return new Form( + { + fields: { + server: { + label: intl.formatMessage(messages.label), + value: this.props.server, + options: [ + { value: this.ferdiumServer, label: 'Ferdium (Default)' }, + { value: this.franzServer, label: 'Franz' }, + { + value: this.defaultServers.includes(this.props.server) + ? '' + : this.props.server, + label: 'Custom', + }, + ], + }, + customServer: { + label: intl.formatMessage(messages.customServerLabel), + value: '', + validators: [url, required], + }, + }, + }, + intl, + ); + })(); + + componentDidMount() { + if (this.defaultServers.includes(this.props.server)) { + this.form.$('server').value = this.props.server; + } else { + this.form.$('server').value = ''; + this.form.$('customServer').value = this.props.server; + } + } + + submit(e) { + e.preventDefault(); + this.form.submit({ + onSuccess: form => { + if (!this.defaultServers.includes(form.values().server)) { + form.$('server').onChange(form.values().customServer); + } + this.props.onSubmit(form.values()); + }, + onError: form => { + if (this.defaultServers.includes(form.values().server)) { + this.props.onSubmit(form.values()); + } + }, + }); + } + + render() { + const { form } = this; + const { intl } = this.props; + return ( +
+
this.submit(e)}> + + + +

{intl.formatMessage(messages.headline)}

+ {form.$('server').value === this.franzServer && ( + + {intl.formatMessage(messages.warning)} + + )} + { + this.form.$('customServer').value = this.form + .$('customServer') + .value.replace(/\/$/, ''); + this.submit(e); + }} + field={form.$('customServer')} + /> + )} +
+ ); + } +} + +export default injectIntl(observer(ChangeServer)); diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js deleted file mode 100644 index a5893e181..000000000 --- a/src/components/auth/Login.js +++ /dev/null @@ -1,203 +0,0 @@ -/* eslint jsx-a11y/anchor-is-valid: 0 */ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer, inject } from 'mobx-react'; -import { defineMessages, injectIntl } from 'react-intl'; - -import { LIVE_FRANZ_API } from '../../config'; -import { API_VERSION } from '../../environment-remote'; -import { serverBase } from '../../api/apiBase'; // TODO: Remove this line after fixing password recovery in-app -import Form from '../../lib/Form'; -import { required, email } from '../../helpers/validation-helpers'; -import Input from '../ui/Input'; -import Button from '../ui/button'; -import Link from '../ui/Link'; - -import { globalError as globalErrorPropType } from '../../prop-types'; -import { H1 } from '../ui/headline'; - -const messages = defineMessages({ - headline: { - id: 'login.headline', - defaultMessage: 'Sign in', - }, - emailLabel: { - id: 'login.email.label', - defaultMessage: 'Email address', - }, - passwordLabel: { - id: 'login.password.label', - defaultMessage: 'Password', - }, - submitButtonLabel: { - id: 'login.submit.label', - defaultMessage: 'Sign in', - }, - invalidCredentials: { - id: 'login.invalidCredentials', - defaultMessage: 'Email or password not valid', - }, - customServerQuestion: { - id: 'login.customServerQuestion', - defaultMessage: 'Using a custom Ferdium server?', - }, - customServerSuggestion: { - id: 'login.customServerSuggestion', - defaultMessage: 'Try importing your Franz account', - }, - tokenExpired: { - id: 'login.tokenExpired', - defaultMessage: 'Your session expired, please login again.', - }, - serverLogout: { - id: 'login.serverLogout', - defaultMessage: 'Your session expired, please login again.', - }, - signupLink: { - id: 'login.link.signup', - defaultMessage: 'Create a free account', - }, - passwordLink: { - id: 'login.link.password', - defaultMessage: 'Reset password', - }, - backToWelcome: { - id: 'login.backToWelcome', - defaultMessage: 'Click the Ferdium icon to go back to the Welcome screen', - }, -}); - -class Login extends Component { - static propTypes = { - onSubmit: PropTypes.func.isRequired, - isSubmitting: PropTypes.bool.isRequired, - isTokenExpired: PropTypes.bool.isRequired, - isServerLogout: PropTypes.bool.isRequired, - signupRoute: PropTypes.string.isRequired, - // passwordRoute: PropTypes.string.isRequired, // TODO: Uncomment this line after fixing password recovery in-app - error: globalErrorPropType.isRequired, - }; - - form = (() => { - const { intl } = this.props; - return new Form({ - fields: { - email: { - label: intl.formatMessage(messages.emailLabel), - value: '', - validators: [required, email], - }, - password: { - label: intl.formatMessage(messages.passwordLabel), - value: '', - validators: [required], - type: 'password', - }, - }, - }, - intl, - )})(); - - submit(e) { - e.preventDefault(); - this.form.submit({ - onSuccess: form => { - this.props.onSubmit(form.values()); - }, - onError: () => {}, - }); - } - - render() { - const { form } = this; - const { intl } = this.props; - const { - isSubmitting, - isTokenExpired, - isServerLogout, - signupRoute, - // passwordRoute, // TODO: Uncomment this line after fixing password recovery in-app - error, - } = this.props; - - return ( -
-
this.submit(e)}> - -

{intl.formatMessage(messages.headline)}

- {isTokenExpired && ( -

- {intl.formatMessage(messages.tokenExpired)} -

- )} - {isServerLogout && ( -

- {intl.formatMessage(messages.serverLogout)} -

- )} - - - {error.code === 'invalid-credentials' && ( - <> -

- {intl.formatMessage(messages.invalidCredentials)} -

- {window['ferdium'].stores.settings.all.app.server !== - LIVE_FRANZ_API && ( -

- {intl.formatMessage(messages.customServerQuestion)}{' '} - - {intl.formatMessage(messages.customServerSuggestion)} - -

- )} - - )} - {isSubmitting ? ( -
- ); - } -} - -export default injectIntl(inject('actions')(observer(Login))); diff --git a/src/components/auth/Login.jsx b/src/components/auth/Login.jsx new file mode 100644 index 000000000..33b4d3e0d --- /dev/null +++ b/src/components/auth/Login.jsx @@ -0,0 +1,205 @@ +/* eslint jsx-a11y/anchor-is-valid: 0 */ +import { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer, inject } from 'mobx-react'; +import { defineMessages, injectIntl } from 'react-intl'; + +import { mdiArrowLeftCircle } from '@mdi/js'; +import Icon from '../ui/icon'; +import { LIVE_FRANZ_API } from '../../config'; +import { API_VERSION } from '../../environment-remote'; +import { serverBase } from '../../api/apiBase'; // TODO: Remove this line after fixing password recovery in-app +import Form from '../../lib/Form'; +import { required, email } from '../../helpers/validation-helpers'; +import Input from '../ui/Input'; +import Button from '../ui/button'; +import Link from '../ui/Link'; + +import { globalError as globalErrorPropType } from '../../prop-types'; +import { H1 } from '../ui/headline'; + +const messages = defineMessages({ + headline: { + id: 'login.headline', + defaultMessage: 'Sign in', + }, + emailLabel: { + id: 'login.email.label', + defaultMessage: 'Email address', + }, + passwordLabel: { + id: 'login.password.label', + defaultMessage: 'Password', + }, + submitButtonLabel: { + id: 'login.submit.label', + defaultMessage: 'Sign in', + }, + invalidCredentials: { + id: 'login.invalidCredentials', + defaultMessage: 'Email or password not valid', + }, + customServerQuestion: { + id: 'login.customServerQuestion', + defaultMessage: 'Using a custom Ferdium server?', + }, + customServerSuggestion: { + id: 'login.customServerSuggestion', + defaultMessage: 'Try importing your Franz account', + }, + tokenExpired: { + id: 'login.tokenExpired', + defaultMessage: 'Your session expired, please login again.', + }, + serverLogout: { + id: 'login.serverLogout', + defaultMessage: 'Your session expired, please login again.', + }, + signupLink: { + id: 'login.link.signup', + defaultMessage: 'Create a free account', + }, + passwordLink: { + id: 'login.link.password', + defaultMessage: 'Reset password', + }, +}); + +class Login extends Component { + static propTypes = { + onSubmit: PropTypes.func.isRequired, + isSubmitting: PropTypes.bool.isRequired, + isTokenExpired: PropTypes.bool.isRequired, + isServerLogout: PropTypes.bool.isRequired, + signupRoute: PropTypes.string.isRequired, + // passwordRoute: PropTypes.string.isRequired, // TODO: Uncomment this line after fixing password recovery in-app + error: globalErrorPropType.isRequired, + }; + + form = (() => { + const { intl } = this.props; + return new Form( + { + fields: { + email: { + label: intl.formatMessage(messages.emailLabel), + value: '', + validators: [required, email], + }, + password: { + label: intl.formatMessage(messages.passwordLabel), + value: '', + validators: [required], + type: 'password', + }, + }, + }, + intl, + ); + })(); + + submit(e) { + e.preventDefault(); + this.form.submit({ + onSuccess: form => { + this.props.onSubmit(form.values()); + }, + onError: () => {}, + }); + } + + render() { + const { form } = this; + const { intl } = this.props; + const { + isSubmitting, + isTokenExpired, + isServerLogout, + signupRoute, + // passwordRoute, // TODO: Uncomment this line after fixing password recovery in-app + error, + } = this.props; + + return ( +
+
this.submit(e)}> + + + +

{intl.formatMessage(messages.headline)}

+ {isTokenExpired && ( +

+ {intl.formatMessage(messages.tokenExpired)} +

+ )} + {isServerLogout && ( +

+ {intl.formatMessage(messages.serverLogout)} +

+ )} + + + {error.code === 'invalid-credentials' && ( + <> +

+ {intl.formatMessage(messages.invalidCredentials)} +

+ {window['ferdium'].stores.settings.all.app.server !== + LIVE_FRANZ_API && ( +

+ {intl.formatMessage(messages.customServerQuestion)}{' '} + + {intl.formatMessage(messages.customServerSuggestion)} + +

+ )} + + )} + {isSubmitting ? ( +
+ ); + } +} + +export default injectIntl(inject('actions')(observer(Login))); diff --git a/src/components/auth/SetupAssistant.js b/src/components/auth/SetupAssistant.js deleted file mode 100644 index e3d2d88d1..000000000 --- a/src/components/auth/SetupAssistant.js +++ /dev/null @@ -1,331 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer } from 'mobx-react'; -import { defineMessages, injectIntl } from 'react-intl'; -import injectSheet from 'react-jss'; -import classnames from 'classnames'; - -import Input from '../ui/input/index'; -import Button from '../ui/button'; -import Badge from '../ui/badge'; -import Modal from '../ui/Modal'; -import Infobox from '../ui/Infobox'; -import Appear from '../ui/effects/Appear'; -import globalMessages from '../../i18n/globalMessages'; - -import { CDN_URL } from '../../config'; -import { H1, H2 } from '../ui/headline'; - -const SLACK_ID = 'slack'; - -const messages = defineMessages({ - headline: { - id: 'setupAssistant.headline', - defaultMessage: "Let's get started", - }, - subHeadline: { - id: 'setupAssistant.subheadline', - defaultMessage: - 'Choose from our most used services and get back on top of your messaging now.', - }, - submitButtonLabel: { - id: 'setupAssistant.submit.label', - defaultMessage: "Let's go", - }, - inviteSuccessInfo: { - id: 'invite.successInfo', - defaultMessage: 'Invitations sent successfully', - }, -}); - -let transition = 'none'; - -if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { - transition = 'all 0.25s'; -} - -const styles = theme => ({ - root: { - width: '500px !important', - textAlign: 'center', - padding: 20, - - '& h1': {}, - }, - servicesGrid: { - display: 'flex', - flexWrap: 'wrap', - justifyContent: 'space-between', - }, - serviceContainer: { - background: theme.colorBackground, - position: 'relative', - width: '32%', - display: 'flex', - alignItems: 'center', - flexDirection: 'column', - justifyContent: 'center', - padding: 20, - borderRadius: theme.borderRadius, - marginBottom: 10, - opacity: 0.5, - transition, - border: [3, 'solid', 'transparent'], - - '& h2': { - margin: [10, 0, 0], - color: theme.colorText, - }, - - '&:hover': { - border: [3, 'solid', theme.brandPrimary], - '& $serviceIcon': {}, - }, - }, - selected: { - border: [3, 'solid', theme.brandPrimary], - background: `${theme.brandPrimary}47`, - opacity: 1, - }, - serviceIcon: { - width: 50, - transition, - }, - - slackModalContent: { - textAlign: 'center', - - '& img': { - width: 50, - marginBottom: 20, - }, - }, - modalActionContainer: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - }, - ctaCancel: { - background: 'none !important', - }, - slackBadge: { - position: 'absolute', - bottom: 4, - height: 'auto', - padding: '0px 4px', - borderRadius: theme.borderRadiusSmall, - margin: 0, - display: 'flex', - overflow: 'hidden', - }, - clearSlackWorkspace: { - background: theme.inputPrefixColor, - marginLeft: 5, - height: '100%', - color: theme.colorText, - display: 'inline-flex', - justifyContent: 'center', - alignItems: 'center', - marginRight: -4, - padding: [0, 5], - }, -}); - -class SetupAssistant extends Component { - static propTypes = { - classes: PropTypes.object.isRequired, - onSubmit: PropTypes.func.isRequired, - isInviteSuccessful: PropTypes.bool, - services: PropTypes.object.isRequired, - isSettingUpServices: PropTypes.bool.isRequired, - }; - - static defaultProps = { - isInviteSuccessful: false, - }; - - state = { - services: [ - { - id: 'whatsapp', - }, - { - id: 'messenger', - }, - { - id: 'gmail', - }, - ], - isSlackModalOpen: false, - slackWorkspace: '', - }; - - slackWorkspaceHandler() { - const { slackWorkspace = '', services } = this.state; - - const sanitizedWorkspace = slackWorkspace - .trim() - .replace(/^https?:\/\//, ''); - - if (sanitizedWorkspace) { - const index = services.findIndex(s => s.id === SLACK_ID); - - if (index === -1) { - const newServices = services; - newServices.push({ id: SLACK_ID, team: sanitizedWorkspace }); - this.setState({ services: newServices }); - } - } - - this.setState({ - isSlackModalOpen: false, - slackWorkspace: sanitizedWorkspace, - }); - } - - render() { - const { intl } = this.props; - const { - classes, - isInviteSuccessful, - onSubmit, - services, - isSettingUpServices, - } = this.props; - const { - isSlackModalOpen, - slackWorkspace, - services: addedServices, - } = this.state; - - return ( -
- {this.state.showSuccessInfo && isInviteSuccessful && ( - - - {intl.formatMessage(messages.inviteSuccessInfo)} - - - )} - - -

{intl.formatMessage(messages.headline)}

-

{intl.formatMessage(messages.subHeadline)}

-
- {Object.keys(services).map(id => { - const service = services[id]; - return ( - - - )} - - ); - })} -
- this.setState({ isSlackModalOpen: false })} - > -
- -

Create your first Slack workspace

-
{ - e.preventDefault(); - this.slackWorkspaceHandler(); - }} - > - - this.setState({ slackWorkspace: e.target.value }) - } - value={slackWorkspace} - /> -
-
-
-
-
-
- ); - } -} - -export default injectIntl( - injectSheet(styles, { injectTheme: true })(observer(SetupAssistant)), -); diff --git a/src/components/auth/SetupAssistant.jsx b/src/components/auth/SetupAssistant.jsx new file mode 100644 index 000000000..8d15e36d1 --- /dev/null +++ b/src/components/auth/SetupAssistant.jsx @@ -0,0 +1,336 @@ +import { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, injectIntl } from 'react-intl'; +import injectSheet from 'react-jss'; +import classnames from 'classnames'; + +import Input from '../ui/input/index'; +import Button from '../ui/button'; +import Badge from '../ui/badge'; +import Modal from '../ui/Modal'; +import Infobox from '../ui/Infobox'; +import Appear from '../ui/effects/Appear'; +import globalMessages from '../../i18n/globalMessages'; + +import { CDN_URL } from '../../config'; +import { H1, H2 } from '../ui/headline'; + +const SLACK_ID = 'slack'; + +const messages = defineMessages({ + headline: { + id: 'setupAssistant.headline', + defaultMessage: "Let's get started", + }, + subHeadline: { + id: 'setupAssistant.subheadline', + defaultMessage: + 'Choose from our most used services and get back on top of your messaging now.', + }, + submitButtonLabel: { + id: 'setupAssistant.submit.label', + defaultMessage: "Let's go", + }, + skipButtonLabel: { + id: 'setupAssistant.skip.label', + defaultMessage: 'Skip', + }, + inviteSuccessInfo: { + id: 'invite.successInfo', + defaultMessage: 'Invitations sent successfully', + }, +}); + +let transition = 'none'; + +if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { + transition = 'all 0.25s'; +} + +const styles = theme => ({ + root: { + width: '500px !important', + textAlign: 'center', + padding: 20, + + '& h1': {}, + }, + servicesGrid: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-between', + }, + serviceContainer: { + background: theme.colorBackground, + position: 'relative', + width: '32%', + display: 'flex', + alignItems: 'center', + flexDirection: 'column', + justifyContent: 'center', + padding: 20, + borderRadius: theme.borderRadius, + marginBottom: 10, + opacity: 0.5, + transition, + border: [3, 'solid', 'transparent'], + + '& h2': { + margin: [10, 0, 0], + color: theme.colorText, + }, + + '&:hover': { + border: [3, 'solid', theme.brandPrimary], + '& $serviceIcon': {}, + }, + }, + selected: { + border: [3, 'solid', theme.brandPrimary], + background: `${theme.brandPrimary}47`, + opacity: 1, + }, + serviceIcon: { + width: 50, + transition, + }, + + slackModalContent: { + textAlign: 'center', + + '& img': { + width: 50, + marginBottom: 20, + }, + }, + modalActionContainer: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + ctaCancel: { + background: 'none !important', + }, + slackBadge: { + position: 'absolute', + bottom: 4, + height: 'auto', + padding: '0px 4px', + borderRadius: theme.borderRadiusSmall, + margin: 0, + display: 'flex', + overflow: 'hidden', + }, + clearSlackWorkspace: { + background: theme.inputPrefixColor, + marginLeft: 5, + height: '100%', + color: theme.colorText, + display: 'inline-flex', + justifyContent: 'center', + alignItems: 'center', + marginRight: -4, + padding: [0, 5], + }, +}); + +class SetupAssistant extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + onSubmit: PropTypes.func.isRequired, + isInviteSuccessful: PropTypes.bool, + services: PropTypes.object.isRequired, + isSettingUpServices: PropTypes.bool.isRequired, + }; + + static defaultProps = { + isInviteSuccessful: false, + }; + + constructor() { + super(); + + this.state = { + services: [], + isSlackModalOpen: false, + slackWorkspace: '', + }; + } + + slackWorkspaceHandler() { + const { slackWorkspace = '', services } = this.state; + + const sanitizedWorkspace = slackWorkspace + .trim() + .replace(/^https?:\/\//, ''); + + if (sanitizedWorkspace) { + const index = services.findIndex(s => s.id === SLACK_ID); + + if (index === -1) { + const newServices = services; + newServices.push({ id: SLACK_ID, team: sanitizedWorkspace }); + this.setState({ services: newServices }); + } + } + + this.setState({ + isSlackModalOpen: false, + slackWorkspace: sanitizedWorkspace, + }); + } + + render() { + const { intl } = this.props; + const { + classes, + isInviteSuccessful, + onSubmit, + services, + isSettingUpServices, + } = this.props; + const { + isSlackModalOpen, + slackWorkspace, + services: addedServices, + } = this.state; + + return ( +
+ {this.state.showSuccessInfo && isInviteSuccessful && ( + + + {intl.formatMessage(messages.inviteSuccessInfo)} + + + )} + + +

{intl.formatMessage(messages.headline)}

+

{intl.formatMessage(messages.subHeadline)}

+
+ {Object.keys(services).map(id => { + const service = services[id]; + return ( + + + )} + + ); + })} +
+ this.setState({ isSlackModalOpen: false })} + > +
+ +

Create your first Slack workspace

+
{ + e.preventDefault(); + this.slackWorkspaceHandler(); + }} + > + + this.setState({ slackWorkspace: e.target.value }) + } + value={slackWorkspace} + /> +
+
+
+
+
+
+ ); + } +} + +export default injectIntl( + injectSheet(styles, { injectTheme: true })(observer(SetupAssistant)), +); diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js deleted file mode 100644 index eaa68f918..000000000 --- a/src/components/auth/Signup.js +++ /dev/null @@ -1,191 +0,0 @@ -/* eslint jsx-a11y/anchor-is-valid: 0 */ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer, inject } from 'mobx-react'; -import { defineMessages, injectIntl } from 'react-intl'; - -import Form from '../../lib/Form'; -import { required, email, minLength } from '../../helpers/validation-helpers'; -import Input from '../ui/Input'; -import Button from '../ui/button'; -import Link from '../ui/Link'; - -import { globalError as globalErrorPropType } from '../../prop-types'; -import { serverBase } from '../../api/apiBase'; -import { H1 } from '../ui/headline'; - -const messages = defineMessages({ - headline: { - id: 'signup.headline', - defaultMessage: 'Sign up', - }, - firstnameLabel: { - id: 'signup.firstname.label', - defaultMessage: 'First Name', - }, - lastnameLabel: { - id: 'signup.lastname.label', - defaultMessage: 'Last Name', - }, - emailLabel: { - id: 'signup.email.label', - defaultMessage: 'Email address', - }, - // companyLabel: { - // id: 'signup.company.label', - // defaultMessage: 'Company', - // }, - passwordLabel: { - id: 'signup.password.label', - defaultMessage: 'Password', - }, - legalInfo: { - id: 'signup.legal.info', - defaultMessage: 'By creating a Ferdium account you accept the', - }, - terms: { - id: 'signup.legal.terms', - defaultMessage: 'Terms of service', - }, - privacy: { - id: 'signup.legal.privacy', - defaultMessage: 'Privacy Statement', - }, - submitButtonLabel: { - id: 'signup.submit.label', - defaultMessage: 'Create account', - }, - loginLink: { - id: 'signup.link.login', - defaultMessage: 'Already have an account, sign in?', - }, - emailDuplicate: { - id: 'signup.emailDuplicate', - defaultMessage: 'A user with that email address already exists', - }, -}); - -class Signup extends Component { - static propTypes = { - onSubmit: PropTypes.func.isRequired, - isSubmitting: PropTypes.bool.isRequired, - loginRoute: PropTypes.string.isRequired, - error: globalErrorPropType.isRequired, - }; - - form = (() => { - const { intl } = this.props; - return new Form({ - fields: { - firstname: { - label: intl.formatMessage(messages.firstnameLabel), - value: '', - validators: [required], - }, - lastname: { - label: intl.formatMessage(messages.lastnameLabel), - value: '', - validators: [required], - }, - email: { - label: intl.formatMessage(messages.emailLabel), - value: '', - validators: [required, email], - }, - password: { - label: intl.formatMessage(messages.passwordLabel), - value: '', - validators: [required, minLength(6)], - type: 'password', - }, - }, - }, - intl, - )})(); - - submit(e) { - e.preventDefault(); - this.form.submit({ - onSuccess: form => { - this.props.onSubmit(form.values()); - }, - onError: () => {}, - }); - } - - render() { - const { form } = this; - const { intl } = this.props; - const { isSubmitting, loginRoute, error } = this.props; - - return ( -
-
-
this.submit(e)} - > - -

{intl.formatMessage(messages.headline)}

-
- - -
- - - {error.code === 'email-duplicate' && ( -

- {intl.formatMessage(messages.emailDuplicate)} -

- )} - {isSubmitting ? ( -
-
- ); - } -} - -export default injectIntl(inject('actions')(observer(Signup))); diff --git a/src/components/auth/Signup.jsx b/src/components/auth/Signup.jsx new file mode 100644 index 000000000..e0337656c --- /dev/null +++ b/src/components/auth/Signup.jsx @@ -0,0 +1,206 @@ +/* eslint jsx-a11y/anchor-is-valid: 0 */ +import { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer, inject } from 'mobx-react'; +import { defineMessages, injectIntl } from 'react-intl'; + +import { mdiArrowLeftCircle } from '@mdi/js'; +import Form from '../../lib/Form'; +import { required, email, minLength } from '../../helpers/validation-helpers'; +import Input from '../ui/Input'; +import Button from '../ui/button'; +import Link from '../ui/Link'; +import Icon from '../ui/icon'; + +import { globalError as globalErrorPropType } from '../../prop-types'; +import { serverBase } from '../../api/apiBase'; +import { H1 } from '../ui/headline'; + +const messages = defineMessages({ + headline: { + id: 'signup.headline', + defaultMessage: 'Sign up', + }, + firstnameLabel: { + id: 'signup.firstname.label', + defaultMessage: 'First Name', + }, + lastnameLabel: { + id: 'signup.lastname.label', + defaultMessage: 'Last Name', + }, + emailLabel: { + id: 'signup.email.label', + defaultMessage: 'Email address', + }, + // companyLabel: { + // id: 'signup.company.label', + // defaultMessage: 'Company', + // }, + passwordLabel: { + id: 'signup.password.label', + defaultMessage: 'Password', + }, + legalInfo: { + id: 'signup.legal.info', + defaultMessage: 'By creating a Ferdium account you accept the', + }, + terms: { + id: 'signup.legal.terms', + defaultMessage: 'Terms of service', + }, + privacy: { + id: 'signup.legal.privacy', + defaultMessage: 'Privacy Statement', + }, + submitButtonLabel: { + id: 'signup.submit.label', + defaultMessage: 'Create account', + }, + loginLink: { + id: 'signup.link.login', + defaultMessage: 'Already have an account, sign in?', + }, + emailDuplicate: { + id: 'signup.emailDuplicate', + defaultMessage: 'A user with that email address already exists', + }, +}); + +class Signup extends Component { + static propTypes = { + onSubmit: PropTypes.func.isRequired, + isSubmitting: PropTypes.bool.isRequired, + loginRoute: PropTypes.string.isRequired, + error: globalErrorPropType.isRequired, + }; + + form = (() => { + const { intl } = this.props; + return new Form( + { + fields: { + firstname: { + label: intl.formatMessage(messages.firstnameLabel), + value: '', + validators: [required], + }, + lastname: { + label: intl.formatMessage(messages.lastnameLabel), + value: '', + validators: [required], + }, + email: { + label: intl.formatMessage(messages.emailLabel), + value: '', + validators: [required, email], + }, + password: { + label: intl.formatMessage(messages.passwordLabel), + value: '', + validators: [required, minLength(6)], + type: 'password', + }, + }, + }, + intl, + ); + })(); + + submit(e) { + e.preventDefault(); + this.form.submit({ + onSuccess: form => { + this.props.onSubmit(form.values()); + }, + onError: () => {}, + }); + } + + render() { + const { form } = this; + const { intl } = this.props; + const { isSubmitting, loginRoute, error } = this.props; + + return ( +
+
+
this.submit(e)} + > + + + +

{intl.formatMessage(messages.headline)}

+
+ + +
+ + + {error.code === 'email-duplicate' && ( +

+ {intl.formatMessage(messages.emailDuplicate)} +

+ )} + {isSubmitting ? ( +
+
+ ); + } +} + +export default injectIntl(inject('actions')(observer(Signup))); -- cgit v1.2.3-54-g00ecf