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 (
-
- );
- }
-}
-
-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 (
+
+ );
+ }
+}
+
+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 (
-
-
-
-
- {intl.formatMessage(messages.signupLink)}
-
-
- {intl.formatMessage(messages.passwordLink)}
-
-
-
-
- {intl.formatMessage(messages.backToWelcome)}
-
-
-
- );
- }
-}
-
-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 (
+
+
+
+
+ {intl.formatMessage(messages.signupLink)}
+
+
+ {intl.formatMessage(messages.passwordLink)}
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+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)}
-
-
- )}
-
-
![](./assets/images/logo.svg)
-
{intl.formatMessage(messages.headline)}
-
{intl.formatMessage(messages.subHeadline)}
-
- {Object.keys(services).map(id => {
- const service = services[id];
- return (
-
- );
- })}
-
-
this.setState({ isSlackModalOpen: false })}
- >
-
-
![]({`${CDN_URL}/recipes/dist/slack/src/icon.svg`})
-
Create your first Slack workspace
-
-
-
-
onSubmit(this.state.services)}
- busy={isSettingUpServices}
- disabled={isSettingUpServices || addedServices.length === 0}
- />
-
- );
- }
-}
-
-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)}
+
+
+ )}
+
+
![](./assets/images/logo.svg)
+
{intl.formatMessage(messages.headline)}
+
{intl.formatMessage(messages.subHeadline)}
+
+ {Object.keys(services).map(id => {
+ const service = services[id];
+ return (
+
s.id === id) !== -1,
+ })}
+ key={id}
+ onClick={() => {
+ const index = this.state.services.findIndex(s => s.id === id);
+ if (index === -1) {
+ if (id === SLACK_ID) {
+ this.setState({ isSlackModalOpen: true });
+ } else {
+ addedServices.push({ id });
+ }
+ } else {
+ addedServices.splice(index, 1);
+ if (id === SLACK_ID) {
+ this.setState({
+ slackWorkspace: '',
+ });
+ }
+ }
+
+ this.setState({ services: addedServices });
+ }}
+ type="button"
+ >
+
+ {service.name}
+ {id === SLACK_ID && slackWorkspace && (
+
+ {slackWorkspace}
+ {
+ this.setState({
+ slackWorkspace: '',
+ });
+ }}
+ >
+ x
+
+
+ )}
+
+ );
+ })}
+
+
this.setState({ isSlackModalOpen: false })}
+ >
+
+
![]({`${CDN_URL}/recipes/dist/slack/src/icon.svg`})
+
Create your first Slack workspace
+
+
+
+
onSubmit(this.state.services)}
+ busy={isSettingUpServices}
+ disabled={isSettingUpServices || addedServices.length === 0}
+ />
+ onSubmit([])}
+ buttonType="secondary"
+ />
+
+ );
+ }
+}
+
+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 (
-
-
-
-
-
- {intl.formatMessage(messages.loginLink)}
-
-
-
-
- );
- }
-}
-
-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 (
+
+
+
+
+
+ {intl.formatMessage(messages.loginLink)}
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default injectIntl(inject('actions')(observer(Signup)));
--
cgit v1.2.3-54-g00ecf